<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Tim Poulsen</title><link href="https://www.timpoulsen.com/" rel="alternate"></link><link href="https://www.timpoulsen.com/feeds/all.atom.xml" rel="self"></link><id>https://www.timpoulsen.com/</id><updated>2024-09-19T00:00:00-04:00</updated><subtitle>Me geeking out about random things</subtitle><entry><title>Shelly Home Assistant-compatible relay</title><link href="https://www.timpoulsen.com/2024/shelly-home-assistant-compatible-relay.html" rel="alternate"></link><published>2024-09-19T00:00:00-04:00</published><updated>2024-09-19T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2024-09-19:/2024/shelly-home-assistant-compatible-relay.html</id><summary type="html">&lt;h1&gt;Using a Shelly 1 Mini Gen 3 as an MQTT-controllable relay&lt;/h1&gt;
&lt;p&gt;For my Christmas lights, I've been using homemade MQTT-controllable relays. I've been wiring up an ESP2866, a relay, and a AC-DC converter. While this works, it's a bit of a rat's nest of wiring. So I decided to try …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Using a Shelly 1 Mini Gen 3 as an MQTT-controllable relay&lt;/h1&gt;
&lt;p&gt;For my Christmas lights, I've been using homemade MQTT-controllable relays. I've been wiring up an ESP2866, a relay, and a AC-DC converter. While this works, it's a bit of a rat's nest of wiring. So I decided to try using a Shelly 1 Mini Gen 3 relay instead. These package basically the same components I've been using into a small, self-contained little box with convenient wiring connections. In my bench tests so far, I think this will work great.&lt;/p&gt;
&lt;p&gt;In this post, I'll explain how I wired up the Shelly and how I can control it using MQTT.&lt;/p&gt;
&lt;h2&gt;Wiring it up&lt;/h2&gt;
&lt;p&gt;First up, I needed to wire up a Shelly. All of the diagrams I could find assumed you were using these to control a light and you'd have a light switch in the system. I will not be using them that way. After some reading and tinkering, this is the way I wired up my Shelly without a light switch involved:&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2024/shelly_switch.png" title="click for larger size"&gt;&lt;img src="../images/2024/shelly_switch.png" width="480" title="Wiring diagram for Shelly relay"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here's a picture of it on my bench with some short extension cord pigtails:&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2024/shelly.jpeg" title="click for larger size"&gt;&lt;img src="../images/2024/shelly.jpeg" width="480" title="Wiring diagram for Shelly relay"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This should be much neater and easier to package in a 3D printed case. Next up was getting MQTT to work.&lt;/p&gt;
&lt;h2&gt;Enable MQTT and get details about your Shelly&lt;/h2&gt;
&lt;p&gt;You'll need various details about your Shelly, such as the IP address, ID, and other information. The easiest way to find this info is to use the Shelly mobile app. With your Shelly wired up and powered on, it should show up in the app. You can tap into it to get these details. You'll need the ID to use with MQTT or the IP address to use with HTTP URLs to control the relay.&lt;/p&gt;
&lt;p&gt;You'll also need to enable MQTT. I'd swear I did this from the app. But that was a while ago and other blogs say you have to do this via the web interface. In your browser, enter the IP address you got from the app. This will bring up the adminstration pages for your Shelly. The option to enable MQTT will be under the Internet &amp;amp; Security &amp;gt; Advanced - Developer Settings option.&lt;/p&gt;
&lt;p&gt;(If you don't have or want to install the Shelly app, you can check your router to find the IP address the Shelly is using. Then, use the administration page as described above to find the Shelly ID.)&lt;/p&gt;
&lt;h2&gt;Controlling via MQTT&lt;/h2&gt;
&lt;p&gt;I found some web pages describing how to control the Shelly over MQTT. However, they were apparently all for older models because most of the directions in those guides failed to work for me. Here's what I was able to figure out on my own.&lt;/p&gt;
&lt;p&gt;First, to make the later commands a bit easier, let's set some shell variables. This will work on Linux or Mac. I'm not sure about Windows, though perhaps if you use the Linux-like shell rather than the Command Prompt or PowerShell.&lt;/p&gt;
&lt;p&gt;It's handy to open two terminal windows so that you can subscribe to the Shelly's topic and get error information in one and issue commands to the Shelly in another. So, open two terminals and enter the following in both.&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;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MQTT_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;your_mqtt_server.local&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MQTT_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1883&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;SHELLY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shelly1minig3-dcda0ce69534&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Subscribe to the Shelly&lt;/h3&gt;
&lt;p&gt;In one of the terminals, subscribe to the Shelly's RPC topic like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mosquitto_sub&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MQTT_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MQTT_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SHELLY_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/rpc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or subscribe to all topics, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mosquitto_sub&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MQTT_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MQTT_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-v
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Any error messages will be output to this terminal, and you can see the commands that have been issued to the Shelly. This isn't required, but it can be helpful for debugging.&lt;/p&gt;
&lt;h3&gt;Get status info&lt;/h3&gt;
&lt;p&gt;In the second terminal, enter the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mosquitto_pub&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MQTT_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MQTT_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SHELLY_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/rpc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;quot;id&amp;quot;:&amp;quot;Shelly.GetStatus&amp;quot;, &amp;quot;src&amp;quot;:&amp;quot;devices/&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SHELLY_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/messages/events&amp;quot;, &amp;quot;method&amp;quot;:&amp;quot;Shelly.GetStatus&amp;quot;}&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;method&lt;/code&gt; parameter specifies which command you're executing on the Shelly. There are many you could use. Check the Shelly docs, but be aware that the docs cover all the Shelly models so some of the pages won't apply to the Mini 1 Gen 3 (or whatever you're using).&lt;/p&gt;
&lt;p&gt;This will return a bunch of information about your Shelly. In fact, all the same info you were able to get from the app.&lt;/p&gt;
&lt;h3&gt;Turn on the relay&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mosquitto_pub&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MQTT_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MQTT_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SHELLY_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/rpc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;quot;method&amp;quot;: &amp;quot;Switch.Set&amp;quot;, &amp;quot;params&amp;quot;:{&amp;quot;id&amp;quot;:0,&amp;quot;on&amp;quot;:true}}&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The method in this case is &lt;code&gt;Switch.Set&lt;/code&gt;. There's a &lt;code&gt;Light.Set&lt;/code&gt; command as well, but it's not used or supported by the Mini 1 Gen 3. Next note the &lt;code&gt;params&lt;/code&gt; key that has two additional sub-keys. The &lt;code&gt;id&lt;/code&gt; parameter is required. Some Shelly models come with multiple relays, and this identifies which relay you're controlling. The Mini 1 Gen 3 has a single relay, but you still have to tell the Shelly that you're addressing the first one.&lt;/p&gt;
&lt;p&gt;Finally, the &lt;code&gt;on&lt;/code&gt; parameter specifies whether the relay should end up being on or off &amp;mdash; &lt;code&gt;true&lt;/code&gt; for on, &lt;code&gt;false&lt;/code&gt; for off.&lt;/p&gt;
&lt;h3&gt;Turn off the relay&lt;/h3&gt;
&lt;p&gt;No surprise then that this command turns off the Shelly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mosquitto_pub&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MQTT_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MQTT_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SHELLY_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/rpc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;quot;method&amp;quot;: &amp;quot;Switch.Set&amp;quot;, &amp;quot;params&amp;quot;:{&amp;quot;id&amp;quot;:0,&amp;quot;on&amp;quot;:false}}&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Controlling via HTTP&lt;/h3&gt;
&lt;p&gt;You can also control the Shelly over HTTP, in other words with your browser. Let's say your Shelly is at &lt;code&gt;192.168.0.122&lt;/code&gt;. You can visit these URLs to control your Shelly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;http://192.168.0.122/ &amp;mdash; access the management interface&lt;/li&gt;
&lt;li&gt;http://192.168.0.122/relay/0?turn=on &amp;mdash; turn on the Shelly&lt;/li&gt;
&lt;li&gt;http://192.168.0.122/relay/0?turn=off &amp;mdash; turn off the Shelly&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Additional Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;https://shelly.guide/webhooks-https-requests/&lt;/li&gt;
&lt;li&gt;https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/Mqtt&lt;/li&gt;
&lt;li&gt;https://kb.shelly.cloud/knowledge-base/shelly-1-mini-gen3#Shelly1MiniGen3-Basicwiringdiagrams&lt;/li&gt;
&lt;li&gt;https://sequr.be/blog/2020/10/getting-started-with-mqtt-and-home-assistant-and-shelly/ (This is one of the blogs that had info that didn't work for me. Still, you'll find some useful information here, plus Home Assistant integration info.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;i class="fa fa-robot red"&gt;&lt;/i&gt;&lt;/p&gt;</content><category term="Making"></category><category term="electronics"></category><category term="xmas"></category></entry><entry><title>Pi Zero Birdcam</title><link href="https://www.timpoulsen.com/2024/pi-zero-birdcam.html" rel="alternate"></link><published>2024-06-15T00:00:00-04:00</published><updated>2024-06-15T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2024-06-15:/2024/pi-zero-birdcam.html</id><summary type="html">&lt;h1&gt;Nestbox camera with a Pi Zero W and PiCamera2&lt;/h1&gt;
&lt;p&gt;In an early post on this blog, I detailed my steps for creating a nestbox streaming camera. I've updated my setup so I thought a new post on the topic would be in order.&lt;/p&gt;
&lt;p&gt;A Raspberry Pi Zero W and its …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Nestbox camera with a Pi Zero W and PiCamera2&lt;/h1&gt;
&lt;p&gt;In an early post on this blog, I detailed my steps for creating a nestbox streaming camera. I've updated my setup so I thought a new post on the topic would be in order.&lt;/p&gt;
&lt;p&gt;A Raspberry Pi Zero W and its camera are small enough to fit inside a nestbox without taking up too much room from the birds. You do need to supply good power to the Zero, so you'll have one cord running out of the box. Make sure to run the cable in such a way that it doesn't cause water to drip into the nestbox. Also, make sure a snake can't use the cord as a means to scale their way up and into the box. Snakes will eat bird eggs if they can get to them.&lt;/p&gt;
&lt;h2&gt;Pi setup and configuration&lt;/h2&gt;
&lt;p&gt;It's pretty easy to install all the needed software to a headless Raspberry Pi Zero W. There's a good &lt;a href="https://www.tomshardware.com/reviews/raspberry-pi-headless-setup-how-to,6028.html" target="_blank"&gt;article over at Tom's Hardware&lt;/a&gt; that describes the process, plus gives you tips for accessing the Pi Zero when it's plugged into your computer's USB port. Install the "Lite" version, which doesn't include the GUI. During the installation, make sure to set a unique hostname for your Pi Zero.&lt;/p&gt;
&lt;p&gt;I'm using the newer &lt;a href="https://github.com/raspberrypi/picamera2" target="_blank"&gt;PiCamera2 library&lt;/a&gt; for my streaming server. It does not support the legacy Pi camera interface. So, if you're running Bullseye or another non-current OS version, make sure to use raspi-config to disable the legacy camera interface. Bookworm has removed the legacy interface altogether, so you're set if you're using Bookworm.&lt;/p&gt;
&lt;p&gt;Speaking of Bullseye, if you're running that older version of the Raspberry Pi OS, you'll need to tweak the /boot/config.txt file. You need to set the &lt;code&gt;dtoverlay&lt;/code&gt; to &lt;code&gt;ov5647&lt;/code&gt; in order for the camera to be detected on the new camera interface. You can add this line at the end of the config.txt file. If a dtoverlay line already exists, as shown here, comment it out by adding a &lt;code&gt;#&lt;/code&gt; to the beginning of the line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# dtoverlay=vc4-kms-v3d&lt;/span&gt;
&lt;span class="nv"&gt;dtoverlay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ov5647
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With those configurations out of the way, reboot your Pi Zero. Then, ssh into it in order to perform the remaining configurations.&lt;/p&gt;
&lt;h2&gt;PiCamera2 set up&lt;/h2&gt;
&lt;p&gt;Install PiCamera2 if it's not already installed. On my Bullseye-based Pi, it was already installed. But it was not installed on the newer Bookworm Lite version. The &lt;code&gt;--no-install-recommends&lt;/code&gt; argument specifies to not install various helper apps, which are mostly GUI-based. You won't need them, and if you installed the Lite OS version, you wouldn't be able to run them anyway.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;python3-picamera2&lt;span class="w"&gt; &lt;/span&gt;--no-install-recommends
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you'll be installing any other Python libraries, I recommend you create a virtual environment. However, you'll need to include the system packages in this environment or you won't have access to PiCamera2 that you just installed. Use this command to create the virtual environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;venv&lt;span class="w"&gt; &lt;/span&gt;--system-site-packages&lt;span class="w"&gt; &lt;/span&gt;venv

&lt;span class="c1"&gt;# then activate the virtual environment&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;venv/bin/activate

&lt;span class="c1"&gt;# now you can pip install packages without interfering&lt;/span&gt;
&lt;span class="c1"&gt;# with other Python setups on your system&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;The streaming server&lt;/h2&gt;
&lt;p&gt;The PiCamera2 project provides various example scripts, including a handy &lt;a href="https://github.com/raspberrypi/picamera2/blob/main/examples/mjpeg_server.py" target="_blank"&gt;streaming MJPG script&lt;/a&gt;. It's long, so I won't include a copy here. Grab it from the link provided. I'm using the script mostly unmodified. I named my file &lt;strong&gt;rpi_cam.py&lt;/strong&gt;. I updated the title and H1 text to be a bit more applicable to my use-case. I also increased the stream dimensions from 640 x 480 to 800 x 600 (there are two spots in the file to change that).&lt;/p&gt;
&lt;h2&gt;Getting it all to run at boot&lt;/h2&gt;
&lt;p&gt;Of course, you'll want the script to run when you boot your Pi Zero. Create a shell script, named &lt;strong&gt;rpi_cam.sh&lt;/strong&gt;, with the following contents:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c1"&gt;# activate the virtual environment if you created one&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/pi/venv/bin/activate
&lt;span class="c1"&gt;# run the camera script&lt;/span&gt;
python&lt;span class="w"&gt; &lt;/span&gt;/home/pi/rpi_cam.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You need to use the full path to the virtual environment and script if you'll be using the crontab technique I describe next. Don't forget to make the script executable with &lt;code&gt;chmod +x rpi_cam.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To run the script automatically when the PiZero boots, run &lt;code&gt;sudo crontab -e&lt;/code&gt; and add this line at the end:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;@reboot&lt;span class="w"&gt; &lt;/span&gt;/home/pi/rpi_cam.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You need the full path to the shell script. The crontab will be executed as a system user that won't otherwise "know" about your home directory path. The &lt;code&gt;&amp;amp;&lt;/code&gt; at the end runs the script as a detached process (i.e. in the background) so that it will keep running when the crontab process finishes.&lt;/p&gt;
&lt;h2&gt;Loading the resulting page&lt;/h2&gt;
&lt;p&gt;Once you've done everything above, reboot the Pi Zero one more time. Once it has booted, use your browser to visit &lt;code&gt;http://the_hostname.local:8000/&lt;/code&gt; and you should see the streaming output from your camera!&lt;/p&gt;
&lt;p&gt;We had a pair of house finches build a nest in a wreath hanging on our front porch. Here's a shot of the camera rigged up to watch the nest, and a picture of the nest itself.&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2024/pizero_cam.jpeg" title="click for larger size"&gt;&lt;img src="../images/2024/pizero_cam.jpeg" width="480" title="Showing how the camera pointing at the nest"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2024/nest_in_wreath.jpeg" title="click for larger size"&gt;&lt;img src="../images/2024/nest_in_wreath.jpeg" width="480" title="A view of the nest"/&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Making"></category><category term="python"></category><category term="raspberry pi"></category></entry><entry><title>Weather display on an LED matrix</title><link href="https://www.timpoulsen.com/2024/weather-display-on-an-led-matrix.html" rel="alternate"></link><published>2024-04-24T00:00:00-04:00</published><updated>2024-04-24T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2024-04-24:/2024/weather-display-on-an-led-matrix.html</id><summary type="html">&lt;h1&gt;Using CircuitPython on a HUB75 LED matrix&lt;/h1&gt;
&lt;p&gt;I bought a couple of surplus LED matrix panels a while back, and I finally got around to playing with them. My panels are 16x32 HUB75-compatible panels. I planned to "stack" them to create an effective 32x32 matrix. Adafruit makes a neat little …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Using CircuitPython on a HUB75 LED matrix&lt;/h1&gt;
&lt;p&gt;I bought a couple of surplus LED matrix panels a while back, and I finally got around to playing with them. My panels are 16x32 HUB75-compatible panels. I planned to "stack" them to create an effective 32x32 matrix. Adafruit makes a neat little ESP32-based microcontroller, the MatrixPortal that features HUB75 output and built-in WiFi support. I picked up one of them and then had everything I needed.&lt;/p&gt;
&lt;p&gt;I wanted to do something useful with the matrix. Eventually I settled on showing the current weather conditions on it. I have an Ambient Weather weather station. It uploads data to the Ambient Weather site. Nicely, they provide a simple API where I can pull my data back down.&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2024/weather_matrix.jpg" title="click for larger size"&gt;&lt;img src="../images/2024/weather_matrix.jpg" width="480" title="My matrix showing the weather conditions"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Given the really low resolution of my matrix, I planned to show one weather stat at a time, then cycle to the next. For example, show the temperature, then a moment later show the wind speed, and so forth. I planned to poll the API every few minutes to get updated data.&lt;/p&gt;
&lt;h2&gt;CircuitPython&lt;/h2&gt;
&lt;p&gt;While I could program the MatrixPortal in C, I'm a lot more comfortable with Python. Adafruit publishes a version of &lt;a href="https://docs.circuitpython.org/en/latest/docs/index.html" target="_blank"&gt;CircuitPython&lt;/a&gt; for this board. CircuitPython is a slimmed-down version of Python meant to run on microcontrollers. I downloaded the version for my board. The install process is pretty simple. You plug the board into a USB port and copy the CircuitPython files to the board. That's it.&lt;/p&gt;
&lt;p&gt;It's important to note again that CircuitPython is a subset of Python. You could probably install some Python (PyPI) libraries, but more commonly you'll use the libraries that Adafruit publishes. These are provided as files that you copy as needed to the lib folder on the MatrixPortal (or other board you're using).&lt;/p&gt;
&lt;p&gt;The MatrixPortal has pretty limited memory and storage. You need to be careful to install only the libraries you need to reduce the chance of running out of memory for your code. Make sure to delete any libraries you're not using off the board, too.&lt;/p&gt;
&lt;p&gt;There's no compile step with CircuitPython. Simply copy your code.py file to your microcontroller. It will reboot and load your new version.&lt;/p&gt;
&lt;h2&gt;Challenges&lt;/h2&gt;
&lt;p&gt;Like Python, CircuitPython includes an automatic garbage collector to free memory when it's no longer used. However, the MatrixPortal was so limited in memory, I found I needed to force collection regularly in my program. Without that, the program would crash with an out of memory error.&lt;/p&gt;
&lt;p&gt;Liberal use of &lt;code&gt;gc.collect()&lt;/code&gt; helped a lot, but didn't fully resolve the issue. For a while, I was convinced that the requests library was too heavy for the MatrixPortal. I even considered giving up on the project till I realized the silly error I was making. By default, the Ambient Weather API call I was making was returning dozens of data readings when I wanted only the most recent one. Once I added the &lt;code&gt;&amp;amp;limit=1&lt;/code&gt; param to the URL, I was able to get the program working.&lt;/p&gt;
&lt;p&gt;The other challenge I had to overcome was how to handle the two panels I was trying to use as if it were one larger panel. The trick turned out to be the &lt;code&gt;serpentine&lt;/code&gt; parameter. In the following call, &lt;code&gt;tile_rows&lt;/code&gt; specifies I have two panels. &lt;code&gt;serpentine=False&lt;/code&gt; indicates I want them laid out serially, not in a serpentine fashion. You'd think that wouldn't matter with just two panels. But without that, I could not get the orientation of the two panels to work correctly.&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;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tile_rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serpentine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Adding a background graphic&lt;/h2&gt;
&lt;p&gt;Once I had the basic text version working, I decided to get fancy and show a GIF as a background. I used a graphics editor to create a 32x32 pixel image (to match my matrix size). The &lt;code&gt;gifio&lt;/code&gt; built-in library can be used to show static or animated GIFs. Note that the &lt;code&gt;OnDiskGif()&lt;/code&gt; function loads the GIF, but not the image within it until you call the &lt;code&gt;next_frame()&lt;/code&gt; method. (That's the method you'd call to load subsequent frames in an animated GIF.)&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;odg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gifio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnDiskGif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/background.gif&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;odg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next_frame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# critical or no image will appear&lt;/span&gt;
&lt;span class="c1"&gt;# Depending on your display the next line may need Colorspace.RGB565&lt;/span&gt;
&lt;span class="c1"&gt;#   instead of Colorspace.RGB565_SWAPPED. If the colors are wonky,&lt;/span&gt;
&lt;span class="c1"&gt;#   try changing this.&lt;/span&gt;
&lt;span class="n"&gt;mygif&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;displayio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TileGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;odg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;pixel_shader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;displayio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ColorConverter&lt;/span&gt;
                        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_colorspace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;displayio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Colorspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RGB565_SWAPPED&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mygif&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;My GIF is pretty simple. Some blue at the top to represent the sky. Some green at the bottom to represent grass. It's simple, but it made me happy to have it there.&lt;/p&gt;
&lt;h2&gt;Full code&lt;/h2&gt;
&lt;p&gt;Below is the full contents of my code.py file. The secrets.py file contains a simple dict named &lt;code&gt;secrets&lt;/code&gt; whose keys are my SSID, WiFi password, Ambient Weather keys, and so forth.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;span class="normal"&gt; 39&lt;/span&gt;
&lt;span class="normal"&gt; 40&lt;/span&gt;
&lt;span class="normal"&gt; 41&lt;/span&gt;
&lt;span class="normal"&gt; 42&lt;/span&gt;
&lt;span class="normal"&gt; 43&lt;/span&gt;
&lt;span class="normal"&gt; 44&lt;/span&gt;
&lt;span class="normal"&gt; 45&lt;/span&gt;
&lt;span class="normal"&gt; 46&lt;/span&gt;
&lt;span class="normal"&gt; 47&lt;/span&gt;
&lt;span class="normal"&gt; 48&lt;/span&gt;
&lt;span class="normal"&gt; 49&lt;/span&gt;
&lt;span class="normal"&gt; 50&lt;/span&gt;
&lt;span class="normal"&gt; 51&lt;/span&gt;
&lt;span class="normal"&gt; 52&lt;/span&gt;
&lt;span class="normal"&gt; 53&lt;/span&gt;
&lt;span class="normal"&gt; 54&lt;/span&gt;
&lt;span class="normal"&gt; 55&lt;/span&gt;
&lt;span class="normal"&gt; 56&lt;/span&gt;
&lt;span class="normal"&gt; 57&lt;/span&gt;
&lt;span class="normal"&gt; 58&lt;/span&gt;
&lt;span class="normal"&gt; 59&lt;/span&gt;
&lt;span class="normal"&gt; 60&lt;/span&gt;
&lt;span class="normal"&gt; 61&lt;/span&gt;
&lt;span class="normal"&gt; 62&lt;/span&gt;
&lt;span class="normal"&gt; 63&lt;/span&gt;
&lt;span class="normal"&gt; 64&lt;/span&gt;
&lt;span class="normal"&gt; 65&lt;/span&gt;
&lt;span class="normal"&gt; 66&lt;/span&gt;
&lt;span class="normal"&gt; 67&lt;/span&gt;
&lt;span class="normal"&gt; 68&lt;/span&gt;
&lt;span class="normal"&gt; 69&lt;/span&gt;
&lt;span class="normal"&gt; 70&lt;/span&gt;
&lt;span class="normal"&gt; 71&lt;/span&gt;
&lt;span class="normal"&gt; 72&lt;/span&gt;
&lt;span class="normal"&gt; 73&lt;/span&gt;
&lt;span class="normal"&gt; 74&lt;/span&gt;
&lt;span class="normal"&gt; 75&lt;/span&gt;
&lt;span class="normal"&gt; 76&lt;/span&gt;
&lt;span class="normal"&gt; 77&lt;/span&gt;
&lt;span class="normal"&gt; 78&lt;/span&gt;
&lt;span class="normal"&gt; 79&lt;/span&gt;
&lt;span class="normal"&gt; 80&lt;/span&gt;
&lt;span class="normal"&gt; 81&lt;/span&gt;
&lt;span class="normal"&gt; 82&lt;/span&gt;
&lt;span class="normal"&gt; 83&lt;/span&gt;
&lt;span class="normal"&gt; 84&lt;/span&gt;
&lt;span class="normal"&gt; 85&lt;/span&gt;
&lt;span class="normal"&gt; 86&lt;/span&gt;
&lt;span class="normal"&gt; 87&lt;/span&gt;
&lt;span class="normal"&gt; 88&lt;/span&gt;
&lt;span class="normal"&gt; 89&lt;/span&gt;
&lt;span class="normal"&gt; 90&lt;/span&gt;
&lt;span class="normal"&gt; 91&lt;/span&gt;
&lt;span class="normal"&gt; 92&lt;/span&gt;
&lt;span class="normal"&gt; 93&lt;/span&gt;
&lt;span class="normal"&gt; 94&lt;/span&gt;
&lt;span class="normal"&gt; 95&lt;/span&gt;
&lt;span class="normal"&gt; 96&lt;/span&gt;
&lt;span class="normal"&gt; 97&lt;/span&gt;
&lt;span class="normal"&gt; 98&lt;/span&gt;
&lt;span class="normal"&gt; 99&lt;/span&gt;
&lt;span class="normal"&gt;100&lt;/span&gt;
&lt;span class="normal"&gt;101&lt;/span&gt;
&lt;span class="normal"&gt;102&lt;/span&gt;
&lt;span class="normal"&gt;103&lt;/span&gt;
&lt;span class="normal"&gt;104&lt;/span&gt;
&lt;span class="normal"&gt;105&lt;/span&gt;
&lt;span class="normal"&gt;106&lt;/span&gt;
&lt;span class="normal"&gt;107&lt;/span&gt;
&lt;span class="normal"&gt;108&lt;/span&gt;
&lt;span class="normal"&gt;109&lt;/span&gt;
&lt;span class="normal"&gt;110&lt;/span&gt;
&lt;span class="normal"&gt;111&lt;/span&gt;
&lt;span class="normal"&gt;112&lt;/span&gt;
&lt;span class="normal"&gt;113&lt;/span&gt;
&lt;span class="normal"&gt;114&lt;/span&gt;
&lt;span class="normal"&gt;115&lt;/span&gt;
&lt;span class="normal"&gt;116&lt;/span&gt;
&lt;span class="normal"&gt;117&lt;/span&gt;
&lt;span class="normal"&gt;118&lt;/span&gt;
&lt;span class="normal"&gt;119&lt;/span&gt;
&lt;span class="normal"&gt;120&lt;/span&gt;
&lt;span class="normal"&gt;121&lt;/span&gt;
&lt;span class="normal"&gt;122&lt;/span&gt;
&lt;span class="normal"&gt;123&lt;/span&gt;
&lt;span class="normal"&gt;124&lt;/span&gt;
&lt;span class="normal"&gt;125&lt;/span&gt;
&lt;span class="normal"&gt;126&lt;/span&gt;
&lt;span class="normal"&gt;127&lt;/span&gt;
&lt;span class="normal"&gt;128&lt;/span&gt;
&lt;span class="normal"&gt;129&lt;/span&gt;
&lt;span class="normal"&gt;130&lt;/span&gt;
&lt;span class="normal"&gt;131&lt;/span&gt;
&lt;span class="normal"&gt;132&lt;/span&gt;
&lt;span class="normal"&gt;133&lt;/span&gt;
&lt;span class="normal"&gt;134&lt;/span&gt;
&lt;span class="normal"&gt;135&lt;/span&gt;
&lt;span class="normal"&gt;136&lt;/span&gt;
&lt;span class="normal"&gt;137&lt;/span&gt;
&lt;span class="normal"&gt;138&lt;/span&gt;
&lt;span class="normal"&gt;139&lt;/span&gt;
&lt;span class="normal"&gt;140&lt;/span&gt;
&lt;span class="normal"&gt;141&lt;/span&gt;
&lt;span class="normal"&gt;142&lt;/span&gt;
&lt;span class="normal"&gt;143&lt;/span&gt;
&lt;span class="normal"&gt;144&lt;/span&gt;
&lt;span class="normal"&gt;145&lt;/span&gt;
&lt;span class="normal"&gt;146&lt;/span&gt;
&lt;span class="normal"&gt;147&lt;/span&gt;
&lt;span class="normal"&gt;148&lt;/span&gt;
&lt;span class="normal"&gt;149&lt;/span&gt;
&lt;span class="normal"&gt;150&lt;/span&gt;
&lt;span class="normal"&gt;151&lt;/span&gt;
&lt;span class="normal"&gt;152&lt;/span&gt;
&lt;span class="normal"&gt;153&lt;/span&gt;
&lt;span class="normal"&gt;154&lt;/span&gt;
&lt;span class="normal"&gt;155&lt;/span&gt;
&lt;span class="normal"&gt;156&lt;/span&gt;
&lt;span class="normal"&gt;157&lt;/span&gt;
&lt;span class="normal"&gt;158&lt;/span&gt;
&lt;span class="normal"&gt;159&lt;/span&gt;
&lt;span class="normal"&gt;160&lt;/span&gt;
&lt;span class="normal"&gt;161&lt;/span&gt;
&lt;span class="normal"&gt;162&lt;/span&gt;
&lt;span class="normal"&gt;163&lt;/span&gt;
&lt;span class="normal"&gt;164&lt;/span&gt;
&lt;span class="normal"&gt;165&lt;/span&gt;
&lt;span class="normal"&gt;166&lt;/span&gt;
&lt;span class="normal"&gt;167&lt;/span&gt;
&lt;span class="normal"&gt;168&lt;/span&gt;
&lt;span class="normal"&gt;169&lt;/span&gt;
&lt;span class="normal"&gt;170&lt;/span&gt;
&lt;span class="normal"&gt;171&lt;/span&gt;
&lt;span class="normal"&gt;172&lt;/span&gt;
&lt;span class="normal"&gt;173&lt;/span&gt;
&lt;span class="normal"&gt;174&lt;/span&gt;
&lt;span class="normal"&gt;175&lt;/span&gt;
&lt;span class="normal"&gt;176&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;board&lt;/span&gt;  &lt;span class="c1"&gt;# for pin definitions&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;busio&lt;/span&gt;  &lt;span class="c1"&gt;# to access SPI bus&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;displayio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;gc&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;gifio&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;adafruit_esp32spi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;adafruit_esp32spi_wifimanager&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;adafruit_display_text.label&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Label&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;adafruit_esp32spi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;adafruit_esp32spi&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;adafruit_matrixportal.matrix&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Matrix&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;digitalio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DigitalInOut&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rtc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RTC&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;terminalio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FONT&lt;/span&gt;  &lt;span class="c1"&gt;# Provides the font we use&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;

&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Get wifi details and more from a secrets.py file&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;secrets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Free memory (start): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem_free&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://rt.ambientweather.net/v1/devices/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;device_mac&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?applicationKey=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;awn_application_key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;apiKey=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;awn_api_key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;limit=1&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;esp32_cs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DigitalInOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ESP_CS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;esp32_ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DigitalInOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ESP_BUSY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;esp32_reset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DigitalInOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ESP_RESET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;spi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;busio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SCK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MOSI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MISO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;esp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adafruit_esp32spi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ESP_SPIcontrol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;esp32_cs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;esp32_ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;esp32_reset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adafruit_esp32spi_wifimanager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ESPSPI_WiFiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&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;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Connected to&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;RSSI:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;esp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rssi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;My IP address is&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;esp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pretty_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip_address&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Free memory (network connected): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem_free&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tile_rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serpentine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;

&lt;span class="c1"&gt;# --- Drawing setup ---&lt;/span&gt;
&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;displayio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Create a Group&lt;/span&gt;
&lt;span class="n"&gt;bitmap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;displayio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&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;# Create a bitmap object,width, height, bit depth&lt;/span&gt;
&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;displayio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Palette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Create a color palette&lt;/span&gt;

&lt;span class="c1"&gt;# Create a TileGrid using the Bitmap and Palette&lt;/span&gt;
&lt;span class="n"&gt;tile_grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;displayio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TileGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pixel_shader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tile_grid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Add the TileGrid to the Group&lt;/span&gt;
&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;

&lt;span class="n"&gt;odg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gifio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnDiskGif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/background2.gif&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;odg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next_frame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Depending on your display the next line may need Colorspace.RGB565&lt;/span&gt;
&lt;span class="c1"&gt;#   instead of Colorspace.RGB565_SWAPPED&lt;/span&gt;
&lt;span class="n"&gt;mygif&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;displayio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TileGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;odg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;pixel_shader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;displayio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ColorConverter&lt;/span&gt;
                        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_colorspace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;displayio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Colorspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RGB565_SWAPPED&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mygif&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Text area 1&lt;/span&gt;
&lt;span class="n"&gt;label_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FONT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;label_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;LOADING&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;label_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xFF0000&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bby&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bbwidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bbh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;label_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounding_box&lt;/span&gt;  &lt;span class="c1"&gt;# _ is bbx&lt;/span&gt;
&lt;span class="n"&gt;label_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# round(display.width / 2 - bbwidth / 2)&lt;/span&gt;
&lt;span class="n"&gt;label_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;bbh&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&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;bby&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# # Text area 2&lt;/span&gt;
&lt;span class="n"&gt;label_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FONT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;label_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;##&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;label_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xFF0000&lt;/span&gt;

&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bby2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bbwidth2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bbh2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;label_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounding_box&lt;/span&gt;
&lt;span class="n"&gt;label_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# round(display.width / 2 - bbwidth2 / 2)&lt;/span&gt;
&lt;span class="n"&gt;label_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;bbh2&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&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;bby2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&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;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;twoDigits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;d2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="n"&gt;d1&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;n&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_weather_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Free memory (parse_weather_data): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem_free&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;updated_weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;updated_weather&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;current_weather&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_weather_conditions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Free memory (get_weather_conditions): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem_free&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;response code: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;


&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;0xFF0000  # red&lt;/span&gt;
&lt;span class="sd"&gt;0x00FF00  # green&lt;/span&gt;
&lt;span class="sd"&gt;0x0000FF  # blue&lt;/span&gt;
&lt;span class="sd"&gt;0x9933FF  # purple&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;tempf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;feelsLike&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;humidity&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;windspeedmph&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;windgustmph&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;winddir&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;dailyrainin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;tempf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Temp.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF0000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;feelsLike&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Feels&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF0000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;humidity&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Humid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x0000FF&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;windspeedmph&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Wind&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x9933FF&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;windgustmph&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Gust&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x9933FF&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;winddir&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Direc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00FF00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;dailyrainin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Rain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x0000FF&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;current_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;current_weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse_weather_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_weather_conditions&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish_weather_conditions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_weather&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_key&lt;/span&gt;&lt;span class="p"&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;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;current_key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_weather&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;value_int&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="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;value_int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&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;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;label_1&lt;/span&gt;&lt;span class="o"&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;label&lt;/span&gt;
    &lt;span class="n"&gt;label_2&lt;/span&gt;&lt;span class="o"&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;value&lt;/span&gt;
    &lt;span class="n"&gt;label_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;
    &lt;span class="n"&gt;label_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;value_int&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# if the temp is below 40, blue instead of red for Temp&lt;/span&gt;
        &lt;span class="n"&gt;label_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x0000FF&lt;/span&gt;
        &lt;span class="n"&gt;label_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x0000FF&lt;/span&gt;


&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;current_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RTC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;
    &lt;span class="n"&gt;mins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;twoDigits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tm_min&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;secs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;twoDigits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tm_sec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&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;mins&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="ow"&gt;and&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;secs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# retrieve data every 5 mins&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Retrieving fresh data &amp;amp; updating display...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;current_weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse_weather_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_weather_conditions&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;publish_weather_conditions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_weather&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&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;secs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# update display every 3 seconds&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;updating display...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;publish_weather_conditions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_weather&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;current_key&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;sleep&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;</content><category term="Making"></category><category term="electronics"></category><category term="circuitpython"></category><category term="python"></category></entry><entry><title>How to find outdated Python requirements</title><link href="https://www.timpoulsen.com/2024/finding-outdated-python-packages.html" rel="alternate"></link><published>2024-04-07T00:00:00-04:00</published><updated>2024-04-07T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2024-04-07:/2024/finding-outdated-python-packages.html</id><summary type="html">&lt;h1&gt;Finding outdated Python packages&lt;/h1&gt;
&lt;p&gt;A quick Python technique this time. You can get a list of outdated Python packages installed on your system with a simple command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;--outdated
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That will list all the packages, including their dependencies. That's nice, but sometimes it makes it hard to know what …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Finding outdated Python packages&lt;/h1&gt;
&lt;p&gt;A quick Python technique this time. You can get a list of outdated Python packages installed on your system with a simple command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;--outdated
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That will list all the packages, including their dependencies. That's nice, but sometimes it makes it hard to know what to update first. So instead, use this command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;--outdated&lt;span class="w"&gt; &lt;/span&gt;--not-required
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will list only top-level packages, not their dependencies.&lt;/p&gt;
&lt;p&gt;Do you use developer-only ("dev dependencies") packages, such as unit testing packages? There does not seem to be a simple command to find outdated dev-only packages. The best I can come up with is to install the dev dependencies, then use &lt;code&gt;pip list&lt;/code&gt;, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;venv&lt;span class="w"&gt; &lt;/span&gt;temp_venv&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# create a temp virtual environment&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;temp_venv/bin/activate
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;requirements-dev.txt&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# install the dev dependencies&lt;/span&gt;
pip&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;--outdated&lt;span class="w"&gt; &lt;/span&gt;--not-required
&lt;span class="c1"&gt;# then deactivate and delete the temp_venv folder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Without the &lt;code&gt;--outdated&lt;/code&gt; flag, the &lt;code&gt;pip list&lt;/code&gt; command will list the packages installed in your current environment. You can use the &lt;code&gt;--not-required&lt;/code&gt; flag to again list only top-level packages.&lt;/p&gt;
&lt;p&gt;You can use the &lt;code&gt;--format json&lt;/code&gt; flag to output the list of packages as JSON. This could be useful if you're going to parse the list programatically. As long as you're not using the &lt;code&gt;--outdated&lt;/code&gt; flag, you can use &lt;code&gt;--format freeze&lt;/code&gt; option to output the list of packages in the &lt;code&gt;pip freeze&lt;/code&gt; style.&lt;/p&gt;
&lt;p&gt;You can learn about these and other pip list flags at &lt;a href="https://pip.pypa.io/en/stable/cli/pip_list/" target="_blank"&gt;the Pip documentation site&lt;/a&gt;.&lt;/p&gt;</content><category term="Python"></category><category term="python"></category></entry><entry><title>Pixels part 3: FastLED</title><link href="https://www.timpoulsen.com/2024/pixels-part-3-fastled.html" rel="alternate"></link><published>2024-01-30T00:00:00-05:00</published><updated>2024-01-30T00:00:00-05:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2024-01-30:/2024/pixels-part-3-fastled.html</id><summary type="html">&lt;p&gt;In the first post in this series, I introduced RGB pixels, discussed the various types, and compared analog and true RGB pixels. In the second part, I covered wiring and shared my tips for reliably connecting to your microcontroller. In this third part, I will discuss the FastLED library, one …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the first post in this series, I introduced RGB pixels, discussed the various types, and compared analog and true RGB pixels. In the second part, I covered wiring and shared my tips for reliably connecting to your microcontroller. In this third part, I will discuss the FastLED library, one of the most popular libraries for programming pixels.&lt;/p&gt;
&lt;h2&gt;FastLED&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://fastled.io/" target="_blank"&gt;FastLED&lt;/a&gt; is a hugely popular library for programming all sorts of RGB pixels. The intro on that linked page summarizes the library nicely: fast, easy to use, widely used, and under active development. See the &lt;a href="https://github.com/FastLED/FastLED" target="_blank"&gt;GitHub page&lt;/a&gt; for code and an excellent wiki. Get help in the &lt;a href="https://www.reddit.com/r/FastLED/" target="_blank"&gt;FastLED Reddit sub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's start with the basics. For this, I think it will help to see code. The comments inline explain what's going on.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&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="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;FastLED.h&amp;quot;&lt;/span&gt;&lt;span class="c1"&gt;           // include the FastLED library&lt;/span&gt;
&lt;span class="cp"&gt;#define LED_TYPE      WS2812B  &lt;/span&gt;&lt;span class="c1"&gt;// specify which type of pixels you&amp;#39;re using&lt;/span&gt;
&lt;span class="cp"&gt;#define DATA_PIN      5        &lt;/span&gt;&lt;span class="c1"&gt;// which GPIO pin is outputting the data signal&lt;/span&gt;
&lt;span class="cp"&gt;#define NUM_LEDS      32       &lt;/span&gt;&lt;span class="c1"&gt;// how many LEDs in your setup&lt;/span&gt;
&lt;span class="cp"&gt;#define COLOR_ORDER   RGB      &lt;/span&gt;&lt;span class="c1"&gt;// the order of red-green-blue values in the data&lt;/span&gt;
&lt;span class="cp"&gt;#define BRIGHTNESS    128      &lt;/span&gt;&lt;span class="c1"&gt;// from 0 - 255&lt;/span&gt;

&lt;span class="n"&gt;CRGB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NUM_LEDS&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;// create an array to hold LED values&lt;/span&gt;

&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;startIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// a couple of variables for tracking info&lt;/span&gt;
&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;colorIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&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="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Set up our LED configuration, specifying the type, data pin, and color order&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Then we pass in the array of values and the number of LEDs. Finally, and this&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// is optional, we apply a color correction to help account for color variations&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// across LED strips.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;FastLED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addLeds&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LED_TYPE&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_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLOR_ORDER&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NUM_LEDS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;setCorrection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TypicalLEDStrip&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Next we set the brightness level&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;FastLED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setBrightness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BRIGHTNESS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&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="n"&gt;colorIndex&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;startIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// this is a counter value, we&amp;#39;re setting a start point&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&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;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;i&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="n"&gt;NUM_LEDS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&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="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// for each LED in the array, set its color to a value picked from the&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// predefined (by FastLED) palette of colors -- in this case a rainbow&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// The `colorIndex` specifies which color to grab from the palette.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// The function also expects a brightness value, and a way to blend&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// colors from one pixel to the next.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;ColorFromPalette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RainbowColors_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;colorIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BRIGHTNESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LINEARBLEND&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;colorIndex&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;colorIndex&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;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// increment our index&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="c1"&gt;// You have to call show() to actually write the values to the physical LEDs and&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// light them up&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;FastLED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// For delays, best to use the FastLED-provided functions instead of built-ins&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;FastLED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Finally, increment our counter so that the next time through the loop, the&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// colors will be assigned one LED ahead of where they were this time.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;startIndex&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;startIndex&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;1&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Phew, that was a lot. I hope you read through the comments and they were helpful. I'll expand on a few points. Starting from the top, the &lt;code&gt;LED_TYPE&lt;/code&gt; is a constant representing the type of pixels you're using. FastLED supports many, though not all types. The supported pixels are &lt;a href="https://github.com/FastLED/FastLED/blob/9307a2926e66dd2d4707315057d1de7f2bb3ed0b/keywords.txt#L360" target="_blank"&gt;listed here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next is the &lt;code&gt;COLOR_ORDER&lt;/code&gt;. This varies by pixel type. Some expect the data to have the red color bits first, followed by green, then blue. But others will be in BGR or GRB or &lt;a href="https://github.com/FastLED/FastLED/blob/9307a2926e66dd2d4707315057d1de7f2bb3ed0b/keywords.txt#L408" target="_blank"&gt;whatever order&lt;/a&gt;. I'm sure there's a better way, but when I don't know, I'll set all the LEDs to be red. If they show up blue or green, I know to start with a different order. Repeat for green and blue till you get the order correct.&lt;/p&gt;
&lt;p&gt;Next I'll jump down to the &lt;code&gt;FastLED.addLeds&lt;/code&gt; call. There are two primary versions of this function: one for 3-wire pixels and the other for 4-wire pixels. Shown above is the 3-wire variant, where there is no dedicated clock line. If you're using 4-wire pixels, the function you'll call is &lt;code&gt;FastLED.addLeds&amp;lt;LED_TYPE, DATA_PIN, CLOCK_PIN, COLOR_ORDER&amp;gt;(leds, NUM_LEDS)&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Colors and palettes&lt;/h2&gt;
&lt;p&gt;In the example above, I demonstrated using one of &lt;a href="https://github.com/FastLED/FastLED/blob/9307a2926e66dd2d4707315057d1de7f2bb3ed0b/keywords.txt#L461" target="_blank"&gt;the pre-defined palettes of color&lt;/a&gt;. These define a set of colors that you can apply across the array of LEDs to get a pleasing mix of colors, such as a rainbow or set of ocean (blue-green) tones. That might be considered a bit more advanced than setting a specific color.&lt;/p&gt;
&lt;p&gt;FastLED gives you many ways to specify a color for an LED. You can do so using one of &lt;a href="https://github.com/FastLED/FastLED/blob/9307a2926e66dd2d4707315057d1de7f2bb3ed0b/keywords.txt#L201" target="_blank"&gt;the pre-defined named colors&lt;/a&gt;, by RGB value, HSV value, and more. For example, to set an LED to display 100% red, you could use any of 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="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;CRGB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="mh"&gt;0xFF0000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;CRGB&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;setRGB&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;CHSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HUE_RED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// HUE_RED happens to be 0 on the color wheel&lt;/span&gt;
&lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;setHSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HUE_RED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// or&lt;/span&gt;
&lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;red&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;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;green&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;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;blue&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;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ... and probably more!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There's one more trick up the sleeve ... let's say you wanted to set all of the LEDs to red. You could loop through, set the color using one of the above techniques, then call &lt;code&gt;FastLED.show()&lt;/code&gt;. Or, you could call &lt;code&gt;FastLED.setColor(CRGB::Red)&lt;/code&gt; (or specify a hex value, HSV value, etc.). With &lt;code&gt;setColor()&lt;/code&gt; you &lt;em&gt;do not&lt;/em&gt; call &lt;code&gt;.show()&lt;/code&gt;!&lt;/p&gt;
&lt;h2&gt;Fading out&lt;/h2&gt;
&lt;p&gt;I'll end with two techniques for fading to black. In both cases, you would put the call to the function inside the &lt;code&gt;loop()&lt;/code&gt; function so that it's called with each iteration of the program loop.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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;// Technique 1: brute force&lt;/span&gt;
&lt;span class="cp"&gt;#define BRIGHTNESS    255&lt;/span&gt;

&lt;span class="c1"&gt;// Assumes there&amp;#39;s something that sets all the LEDs to some color, like:&lt;/span&gt;
&lt;span class="n"&gt;FastLED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CRGB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Red&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;fade_to_black&lt;/span&gt;&lt;span class="p"&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="c1"&gt;// put the call to this function inside loop()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;brightness&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;BRIGHTNESS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fadeAmount&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;2&lt;/span&gt;&lt;span class="p"&gt;;&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="n"&gt;brightness&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&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="n"&gt;brightness&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;fadeAmount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;FastLED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setBrightness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brightness&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Or:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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;// Technique 2: use one of the built-ins&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;fade_to_black&lt;/span&gt;&lt;span class="p"&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="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&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;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;i&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="n"&gt;NUM_LEDS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&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="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Dim a color by 25% (64/256ths)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// eventually fading to full black&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;fadeToBlackBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64&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="c1"&gt;// Or, reduce color to 75% (192/256ths) of its previous value&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// eventually fading to full black&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// leds[i].nscale8( 192);&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;FastLED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There's a ton more to explore in the FastLED library. There's support for matrices, controlling multiple strips (even of different types), built-in fast math functions for varying pixels by sine waves and such, and so much more. I hope this gets you off to a good start. Happy exploring.&lt;/p&gt;</content><category term="Making"></category><category term="leds"></category><category term="electronics"></category></entry><entry><title>Pixels part 2: the hardware</title><link href="https://www.timpoulsen.com/2023/pixels-part-2-the-hardware.html" rel="alternate"></link><published>2023-12-07T00:00:00-05:00</published><updated>2023-12-07T00:00:00-05:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2023-12-07:/2023/pixels-part-2-the-hardware.html</id><summary type="html">&lt;p&gt;In the first post in this series, I introduced RGB pixels. I discussed the various types and compared analog and true RGB pixels. I also covered power requirements. In this post, I'll cover wiring and share my tips for reliably connecting to your microcontroller. I also include a basic sketch …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the first post in this series, I introduced RGB pixels. I discussed the various types and compared analog and true RGB pixels. I also covered power requirements. In this post, I'll cover wiring and share my tips for reliably connecting to your microcontroller. I also include a basic sketch for displaying some colorful blinkies.&lt;/p&gt;
&lt;h2&gt;Microcontrollers&lt;/h2&gt;
&lt;p&gt;I personally prefer to use ESP32 or ESP8266 microcontrollers. They're small, cheap, and readily available from Amazon or Aliexpress. They even feature built-in wifi and Bluetooth connectivity. But any Arduino-compatible microcontroller will work.&lt;/p&gt;
&lt;p&gt;I recommend powering your pixels directly. Don't pull power from a 5v pin on the microcontroller. As discussed in the previous post, pixels draw a lot of power so you could easily burn out your microcontroller by drawing too much power through it.&lt;/p&gt;
&lt;p&gt;Another consideration is powering the microcontroller itself. You could power it through its USB port. Most microcontrollers do just fine running off any old 5v power brick you have laying around. But, wiring that way means you'll essentially need two power sources: one for the microcontroller and another for the pixels. So, instead of powering over USB, you can supply power and ground directly to the Vin and Gnd pins on your microcontroller.&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2023/pixel_schematic.png" title="click for larger size"&gt;&lt;img src="../images/2023/pixel_schematic.png" width="480" title="Wiring schematic for an ESP32 dev board and 3-wire pixel strip"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Finally, there's wiring up the data (signal) line. Wiring 3-wire or 4-wire pixels is essentially identical. With 3-wire pixels, you'll run a wire from a digital output pin to the Di (data in) pad on the pixel. For 4-wire pixels, you'll also need to run another wire to the Clk (clock) pad.&lt;/p&gt;
&lt;p&gt;Consult the pinout documentation for your specific microcontroller. You want to choose pins that are not pulled high or low during boot (i.e. "strapped" pins). If you do, you might have odd difficulties like not being able to flash your microcontroller or having it not boot. Also, don't choose the pin whose GPIO port corresponds to an on-board LED. If you do, you may experience some flashing during boot or other odd lighting behavior as general operations light up the on-board LED.&lt;/p&gt;
&lt;p&gt;In the diagram above, I illustrate connecting pin D1 to the data line. This pin on the ESP32s I use corresponds to GPIO pin 5 which is an unused GPIO port on that particular board. Again, consult the documentation for your microcontroller.&lt;/p&gt;
&lt;h2&gt;Soldering the strip&lt;/h2&gt;
&lt;p&gt;I have never found quick-connectors to work reliably with pixel strips. For that reason, I always solder connections to the pads. Here's a tip: tin the pads first, then solder on the wires. By "tinning" I mean put solder on the pad. Clean the pads or dab a small amount of liquid flux on the pad. Heat it carefully, quickly, though you don't need to rush. Add a small bead of solder once the pad is hot.&lt;/p&gt;
&lt;p&gt;Next, tin the wire. Again, flux it, heat it, tin it. Once that's done, you can more easily connect the two. Heat the pad and when the solder melts, bring the wire to the pad. Its solder will melt quickly, fusing the wire to the pad.&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2023/tinned_pixels.jpeg" title="click for larger size"&gt;&lt;img src="../images/2023/tinned_pixels.jpeg" width="480" title="Solder bead on pixel strip, ready to have the wire soldered to it"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There's no easy way to recover if you destroy a pad, say by over-heating or scraping off the thin copper coating. In those cases, you'll need to cut off that pixel and work with a slightly shorter strip.&lt;/p&gt;
&lt;p&gt;If your microcontroller has header pins soldered to it already, I recommend you use jumper wires with Dupont connectors. Connect the female end to the microcontroller and cut off the other end and solder that to the pixel strip. Soldering to a header pin is tough, plus once you do you'll never be able to insert the pins into a breadboard or socket. You can super-glue the connector to the board. If you ever need to remove it, you can dissolve the glue with acetone. If your microcontroller has through-board holes or pads, you can solder the other end of the wire directly to your microcontroller.&lt;/p&gt;
&lt;p&gt;I went so far as to create a &lt;a href="https://www.timpoulsen.com/2020/my-first-custom-pcb#my-first-custom-pcb" title="Earlier post on creating my custom carrier board"&gt;custom PCB carrier board&lt;/a&gt;. I can solder the ESP8266/ESP32 and a few components to the board then have easy screw-connect headers to make connecting the wires easy and reliable.&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2020/custom_pcb_assembled.jpg" title="click for larger size"&gt;&lt;img src="../images/2020/custom_pcb_assembled.jpg" width="480" title="My custom carrier board, assembled"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As you see on my board, I typically wire in a capacitor (500–1000 µF at 6.3V or higher) across the + and – terminals. &lt;a href="https://learn.adafruit.com/adafruit-neopixel-uberguide/best-practices" target="_blank"&gt;Adafruit also recommends&lt;/a&gt; a small resistor, 300-500 Ohms, between the output pin and the LED strip. My board above shows that resister. However, in general I don't include such a resistor. I have not found it necessary and sometimes my strips don't light if I include one. Your mileage may vary.&lt;/p&gt;
&lt;h2&gt;Simple blinky script&lt;/h2&gt;
&lt;p&gt;Now that you have your LED strip connected to your microcontroller, you need to flash some code to get the lights blinking. A very popular library for working with pixels is &lt;a href="https://github.com/FastLED/FastLED" target="_blank"&gt;FastLED&lt;/a&gt;. It contains all the core functionality you need plus many helpful functions and constants. In a future article, I will dive more deeply into FastLED. Here we'll go with a simple example from the &lt;a href=" In a future article, I will dive more deeply into FastLED." target="_blank"&gt;FastLED_examples repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Below is a simple example of a FastLED script that will show a moving rainbow palette of colors. This script is for the 3-wire WS2812B type pixels, with the data line connected to GPIO 5. In essence, each time through the loop, the script assigns a color to each LED in the strip from a rainbow palette. After showing the colors, it delays 10 milliseconds, advances the starting point in the palette for the colors, and repeats.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="o"&gt;//***************************************************************&lt;/span&gt;
&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Basic&lt;/span&gt; &lt;span class="n"&gt;palette&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;FastLED&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s palettes.&lt;/span&gt;
&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;From&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;marmilicious&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;FastLED_examples&lt;/span&gt;
&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Marc&lt;/span&gt; &lt;span class="n"&gt;Miller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;March&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt;
&lt;span class="o"&gt;//***************************************************************&lt;/span&gt;

&lt;span class="c1"&gt;#include &amp;quot;FastLED.h&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;#define LED_TYPE      WS2812B&lt;/span&gt;
&lt;span class="c1"&gt;#define DATA_PIN      5&lt;/span&gt;
&lt;span class="c1"&gt;#define NUM_LEDS      32&lt;/span&gt;
&lt;span class="c1"&gt;#define COLOR_ORDER   RGB&lt;/span&gt;
&lt;span class="c1"&gt;#define BRIGHTNESS    128&lt;/span&gt;

&lt;span class="n"&gt;CRGB&lt;/span&gt; &lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NUM_LEDS&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="n"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;startIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;colorIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;void&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;FastLED&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addLeds&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LED_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DATA_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;COLOR_ORDER&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NUM_LEDS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setCorrection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TypicalLEDStrip&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;FastLED&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setBrightness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BRIGHTNESS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;void&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;colorIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;startIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NUM_LEDS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;leds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ColorFromPalette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;RainbowColors_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;colorIndex&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="n"&gt;LINEARBLEND&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;colorIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;colorIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;FastLED&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;FastLED&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;startIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;startIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That should get your lights blinking!&lt;/p&gt;</content><category term="Making"></category><category term="leds"></category><category term="electronics"></category></entry><entry><title>An intro to RGB pixel LEDs</title><link href="https://www.timpoulsen.com/2023/pixels-intro.html" rel="alternate"></link><published>2023-11-27T00:00:00-05:00</published><updated>2023-11-27T00:00:00-05:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2023-11-27:/2023/pixels-intro.html</id><summary type="html">&lt;p&gt;RGB LEDs, sometimes called pixels, are lots of fun. Who doesn't like blinky lights?! They're not hard to use, though there's enough complexity to make these LEDs seem a bit daunting. In this post, I'll go through the basics to get you started on your blinky fun.&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2023/pixels.jpeg" title="click for larger size"&gt;&lt;img src="../images/2023/pixels.jpeg" width="480" title="RGB pixels, image courtesy of KnowledgeOfThings.com"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Traditional LEDs display …&lt;/p&gt;</summary><content type="html">&lt;p&gt;RGB LEDs, sometimes called pixels, are lots of fun. Who doesn't like blinky lights?! They're not hard to use, though there's enough complexity to make these LEDs seem a bit daunting. In this post, I'll go through the basics to get you started on your blinky fun.&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2023/pixels.jpeg" title="click for larger size"&gt;&lt;img src="../images/2023/pixels.jpeg" width="480" title="RGB pixels, image courtesy of KnowledgeOfThings.com"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Traditional LEDs display a single color. Bicolor and tricolor variations can show multiple colors. RGB LEDs, on the other hand, let you show any color in the rainbow (ignoring some color theory stuff that limits their actual gamut). Traditional LEDs need just the right polarity power to the appropriate pins in order to light up. RGB pixels require some sort of microcontroller to specify what color to show and to specify when to show it.&lt;/p&gt;
&lt;h2&gt;Addressable vs non-addressable&lt;/h2&gt;
&lt;p&gt;To be clear, in this article I'm talking about addressable pixels. With this type, the microcontroller can specify a different color for every LED in the set. There are also non-addressable LEDs, sometimes called analog LEDs. With those, you can display any color but all of the LEDs in the set will show the same color.&lt;/p&gt;
&lt;p&gt;With non-addressable LEDs, a separate signal is fed to red, green, and blue LEDs (tiny ones, built into a single integrated circuit component). By varying the power sent to each, you can represent any color in the rainbow. But again, keep in mind that all the LEDs in the set will show the same color.&lt;/p&gt;
&lt;p&gt;You can identify analog, non-addressable LEDs by their inputs -- if you see connectors for R, G, B, and a voltage (typically 12v), then you're dealing with a non-addressable LEDs set. As shown here, sometimes these strips also include a white channel (W) to give brighter, purer white output.&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2023/analog_leds.jpeg" title="click for larger size"&gt;&lt;img src="../images/2023/analog_leds.jpeg" width="480" title="Analog LEDs, image courtesy of Alibaba"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In contrast, addressable LEDs will have pins for voltage, ground, and data (or signal). Sometimes, an additional clock pin is used. Addressable LEDs come in 5v and 12v varieties, and sometimes even in other voltages. So, the typical inputs will be labeled +5, D, and GND; or +5, D, C, and GND. In and out matter. So, you will probably see labels like DI and DO for data in and data out. Sometimes, you'll just see an arrow on the strip. The arrow points to the out side. You must put your inputs into the in side or the strip will not light.&lt;/p&gt;
&lt;h2&gt;WS2812B vs APA102C vs ... oh my&lt;/h2&gt;
&lt;p&gt;You will see many different identifiers that describe the type of addressable LEDs. Most times, these are product numbers, such as WS2812B. Some vendors have assigned their own names, like neopixels or dotstar, which in my opinion just adds confusion.&lt;/p&gt;
&lt;p&gt;In broad terms, all the addressable LEDs can be broken down into roughly two camps: 3-wire vs 4-wire. 3-wire pixels required power, data, and ground connections. Common examples are the WS2812B and SK6812. Those are 5-volt examples, though 12v versions, such as the WS2811 are also available. Adafruit calls their 5 volt, 3-wire pixels "neopixels" (SK6812).&lt;/p&gt;
&lt;p&gt;4-wire pixels need power, data, clock, and ground connections. The most common are SK9822 and APA102C. Again, there are 5v and 12v versions. Adafruit calls their 5 volt, 4-wire pixels "dotstar" (SK9822). Because of the clock signal, 4-wire pixels can change colors more rapidly and more uniformly.&lt;/p&gt;
&lt;p&gt;In essence, with 3-wire pixels, the data sent down the line is a &lt;a href="http://cdn.sparkfun.com/datasheets/BreakoutBoards/WS2812B.pdf" title="WS2812B data sheet"&gt;series of values for each pixel&lt;/a&gt; in line. As the signal arrives at the IC, the chip "takes off" the value at the top of the list, lights up according to that signal, then passes the remaining signal down the line. Thus, each pixel in the strip lights up at a slight delay after the LEDs that precedes it. (The delay is barely noticeable on even the longest strips.)&lt;/p&gt;
&lt;p&gt;With 4-wire pixels, again the data is &lt;a href="https://www.digikey.co.th/htmldatasheets/production/1876293/0/0/1/apa102c.html" title="APA102C data sheet"&gt;sent as a series of values&lt;/a&gt; and each pixel takes off its value and passes the remainder down the line. However, the pixels don't show the new color value until the clock signal line switches. In this way, the pixels all change in unison. The real benefit of this technique is that you can more rapidly change colors since the timing is closely controlled by the clock signal.&lt;/p&gt;
&lt;p&gt;While you can mix and match types controlled by a single microcontroller, it's easiest to stick with a single type for a given project.&lt;/p&gt;
&lt;h2&gt;More power!&lt;/h2&gt;
&lt;p&gt;Pixels draw a lot of power. For example, the WS2812B draws 50 milliamps per pixel at full brightness. That adds up. A strip of 30 pixels will draw as much as 1.5 amps. Your Arduino might run off some random phone charger but since the typical phone brick puts out 1-2 amps, you won't be driving too many pixels off of it. At the extreme, 5 meters of 144 pixel per meter strip will need as much as 36 amps at 5v! For that, you'll need a specialty power supply.&lt;/p&gt;
&lt;p&gt;With longer strips, you'll also want to consider power injection. In addition to supplying power at the beginning of the strip, with power injection you supply the same voltage farther down the strip. For example, you might connect your power source to the +5 line every meter on a long strip.&lt;/p&gt;
&lt;p&gt;You can buy high amperage power supplies off Amazon or other online retailers. Another handy way is to repurpose an old laptop power supply. They typically output some higher voltage, say 19 volts. You can use a DC to DC converter to step down the power to the voltage you need. Amazon, Adafruit, and others sell DC-DC step-down converters in various form factors.&lt;/p&gt;
&lt;h2&gt;Coming up&lt;/h2&gt;
&lt;p&gt;This post is getting too long so I'll end it here. In the next post, I'll cover wiring, connecting to your microcontroller, and show a basic sketch for displaying some colorful blinkies. I'm planning a third post in the series to cover the libraries -- FastLED in particular -- commonly used with pixels. Stay tuned!&lt;/p&gt;</content><category term="Making"></category><category term="arduino"></category><category term="leds"></category></entry><entry><title>This code is crap</title><link href="https://www.timpoulsen.com/2023/this_code_is_crap.html" rel="alternate"></link><published>2023-08-31T00:00:00-04:00</published><updated>2023-08-31T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2023-08-31:/2023/this_code_is_crap.html</id><summary type="html">&lt;p&gt;Throughout my career, I've frequently heard developers lament that "&lt;em&gt;this code is crap!&lt;/em&gt;" (well, they usually say &lt;i class="fa-solid fa-poo"&gt;&lt;/i&gt;). It's usually some mid or senior level engineer looking at the existing codebase whining about how "bad" the old code is. To those situations, I say there's &lt;strong&gt;never a valid time&lt;/strong&gt; to say …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Throughout my career, I've frequently heard developers lament that "&lt;em&gt;this code is crap!&lt;/em&gt;" (well, they usually say &lt;i class="fa-solid fa-poo"&gt;&lt;/i&gt;). It's usually some mid or senior level engineer looking at the existing codebase whining about how "bad" the old code is. To those situations, I say there's &lt;strong&gt;never a valid time&lt;/strong&gt; to say the code is "crap."&lt;/p&gt;
&lt;p&gt;Code represents the sum of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the acceptance criteria, goals, and deadlines that inspired the code to be written.&lt;/li&gt;
&lt;li&gt;the knowledge of the problem domain at the time it was written.&lt;/li&gt;
&lt;li&gt;the balance between Done &amp;amp; Good-enough at the time it was written. Put another way, the balance between get it out the door versus make it perfect.&lt;/li&gt;
&lt;li&gt;the knowledge, skill, attention, and diligence of the &lt;strong&gt;reviewers&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;the knowledge and skill of the author. (This is &lt;em&gt;intentionally&lt;/em&gt; last.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ultimately, code reflects the team of people who specified it, wrote it, reviewed it, and tested it, as well as the priorities, deadlines, and understanding at the time it was written. Attacking code is attacking people, many of whom may still be your fellow employees.&lt;/p&gt;
&lt;p&gt;So, don’t say “this code is crap.” Instead, say given what we know now, given the people we have to work on it, and the time we have to work on it now, we can make it better.&lt;/p&gt;
&lt;h2&gt;An anecdote&lt;/h2&gt;
&lt;p&gt;"&lt;em&gt;This code is crap&lt;/em&gt;" said the reviewer to my face at my first ever professional code review. What a way to start a career! Now, he was right. I was new and had tons to learn. To his credit, he showed me the many ways my code could be improved. In the end, what was merged into the codebase was much better code. I learned a lot. And despite dealing with a rather harsh personality, I learned to appreciate what a code review can do for me and the team.&lt;/p&gt;
&lt;p&gt;Still, don't ever say that to a new engineer!&lt;/p&gt;</content><category term="Software Engineering"></category><category term="engineering"></category></entry><entry><title>Dad Jokes As a Service</title><link href="https://www.timpoulsen.com/2023/dad-jokes.html" rel="alternate"></link><published>2023-07-24T00:00:00-04:00</published><updated>2023-07-24T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2023-07-24:/2023/dad-jokes.html</id><summary type="html">&lt;h1&gt;Everyone loves a good dad joke&lt;/h1&gt;
&lt;p&gt;... now, if only there were some &lt;em&gt;good&lt;/em&gt; dad jokes!&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://icanhazdadjoke.com/"&gt;ICanHazDadJoke site&lt;/a&gt; claims to maintain the internet's largest collection of dad jokes. GitHub user &lt;a href="https://github.com/crossnox/dadjokes"&gt;CrossNox&lt;/a&gt; has made a nice wrapper around their API. Together, this lets you retrieve a random dad joke programmatically.&lt;/p&gt;
&lt;p&gt;Why …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Everyone loves a good dad joke&lt;/h1&gt;
&lt;p&gt;... now, if only there were some &lt;em&gt;good&lt;/em&gt; dad jokes!&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://icanhazdadjoke.com/"&gt;ICanHazDadJoke site&lt;/a&gt; claims to maintain the internet's largest collection of dad jokes. GitHub user &lt;a href="https://github.com/crossnox/dadjokes"&gt;CrossNox&lt;/a&gt; has made a nice wrapper around their API. Together, this lets you retrieve a random dad joke programmatically.&lt;/p&gt;
&lt;p&gt;Why, you ask, would you want to get a dad joke programmatically? You could include it in your web site or app, showing random dad jokes to delight your users. You could write a simple script to retrieve and show you the joke from the command line. You could even prank someone and have their computer speak a dad joke every day.&lt;/p&gt;
&lt;p&gt;You'll need the library. A simple &lt;code&gt;pip install dadjokes&lt;/code&gt; will take care of that.&lt;/p&gt;
&lt;p&gt;Then, you'll need this simple script:&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;from&lt;/span&gt; &lt;span class="nn"&gt;dadjokes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dadjoke&lt;/span&gt;
&lt;span class="n"&gt;dadjoke&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dadjoke&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dadjoke&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;joke&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Whenever you need a laugh, from the directory where you saved that script, run &lt;code&gt;python dadjoke.py&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Some simple enhancements (for *nix systems)&lt;/h2&gt;
&lt;p&gt;You can make this file executable, so you can run it with having to put the &lt;code&gt;python&lt;/code&gt; part on the command. First, let's find out where your &lt;code&gt;python&lt;/code&gt; executable is located. Enter &lt;code&gt;which python3&lt;/code&gt; and note the path that is output. Next, edit your script. As the very first line, you'll add a "hashbang" that points to your python executable. 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="ch"&gt;#!/path/to/your/python&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dadjokes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dadjoke&lt;/span&gt;
&lt;span class="n"&gt;dadjoke&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dadjoke&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dadjoke&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;joke&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once you've saved your changes, you can mark the file as executable with &lt;code&gt;chmod +x dadjoke.py&lt;/code&gt; From now on, you can type &lt;code&gt;./dadjoke.py&lt;/code&gt; to run the script. Wow, that saved you from typing like 5 whole characters!&lt;/p&gt;
&lt;p&gt;For you Mac users, you can use the built-in &lt;code&gt;say&lt;/code&gt; program and have your system speak the dad joke. Enter &lt;code&gt;./dadjoke.py | say&lt;/code&gt; (If you can believe it, the jokes are even less funny when spoken by the monotone computerized voice.)&lt;/p&gt;
&lt;p&gt;It would be unprofessional of me to suggest you sneak over to a coworkers computer when they leave it unlocked, and set up a cron job to have a dad joke spoken to them throughout the day. So, I won't suggest that.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://icanhazdadjoke.com/static/smile.svg" alt="icanhazdadjoke logo" style="width:1.5em;margin-top:0.25em;"&gt;&lt;/p&gt;</content><category term="Python"></category><category term="python"></category></entry><entry><title>Getting a Boolean from any value in Python</title><link href="https://www.timpoulsen.com/2023/python-bool-from-any.html" rel="alternate"></link><published>2023-07-14T00:00:00-04:00</published><updated>2023-07-14T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2023-07-14:/2023/python-bool-from-any.html</id><summary type="html">&lt;h1&gt;Getting a Boolean value from any data type&lt;/h1&gt;
&lt;p&gt;Recently, I was building a webhook that needed to accept and operate on varied data from outside sources. Included in that data were values meant to be Booleans. However, some callers were sending values like &lt;code&gt;"Yes"&lt;/code&gt; or &lt;code&gt;"no"&lt;/code&gt; or even &lt;code&gt;"-1"&lt;/code&gt;. I …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Getting a Boolean value from any data type&lt;/h1&gt;
&lt;p&gt;Recently, I was building a webhook that needed to accept and operate on varied data from outside sources. Included in that data were values meant to be Booleans. However, some callers were sending values like &lt;code&gt;"Yes"&lt;/code&gt; or &lt;code&gt;"no"&lt;/code&gt; or even &lt;code&gt;"-1"&lt;/code&gt;. I needed to convert each of those to a Boolean value based on their semantic meaning. In other words, we humans know that the string "false" should represent a &lt;code&gt;False&lt;/code&gt; value. But on it's own, Python would treat the non-empty string as a &lt;code&gt;True&lt;/code&gt; value.&lt;/p&gt;
&lt;p&gt;I'll give background and explanation below. But for the impatient readers out there, here's the code I came up with.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;ast&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;numbers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Real&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_bool_from_any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# CC0 license https://creativecommons.org/publicdomain/zero/1.0/&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# First, try treating the input as a number so that we can&lt;/span&gt;
        &lt;span class="c1"&gt;# handle values such as 0, 1, -1, 0.1, etc.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Converting to a float failed, so, let&amp;#39;s see if it&amp;#39;s a string&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Use ast.literal_eval() rather than eval() to safely evaluate the input string&lt;/span&gt;
                &lt;span class="c1"&gt;# lowercase/capitalize to handle strings that aren&amp;#39;t lead-capitalized&lt;/span&gt;
                &lt;span class="c1"&gt;# This statement will handle strings like True, true, False, FALSE, etc.&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;literal_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capitalize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# String value isn&amp;#39;t parseable by ast, so check for custom falsey strings&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;no&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;none&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;null&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;SyntaxError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# empty strings and multi-word strings will raise a syntax error, treat that as False&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Finally, it&amp;#39;s not a number or string so rely on Python&amp;#39;s built-in coercion rules&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&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;In the actual project, I added a suite of tests around this function. For simplicity in this post, let's just manually check some values.&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;truthy_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;True&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;TRUE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Yes&amp;quot;&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="s2"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="n"&gt;falsey_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;False&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;false&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;FALSE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;No&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;To be or not to be&amp;quot;&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="s2"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&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;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;truthy_values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;`&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;` is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;get_bool_from_any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&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;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;falsey_values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;`&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;` is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;get_bool_from_any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Changing data types&lt;/h2&gt;
&lt;p&gt;To understand what's going on in that function, we need to consider data types. Python is not a strongly-typed language like C or Java. However, the language does define data types, and generally requires you to operate on one type at a time. For example, you can't add an integer and a list.&lt;/p&gt;
&lt;p&gt;When data types don't match, the Python interpreter will do its best to convert values so they do match. However, its rules for this &lt;em&gt;coercion&lt;/em&gt; don't always match what you might expect.&lt;/p&gt;
&lt;p&gt;Originally, Python did not have a Boolean data type. One was added in &lt;a href="https://peps.python.org/pep-0285/"&gt;PEP 285&lt;/a&gt; (way back in 2002!). In Python, &lt;code&gt;bool&lt;/code&gt; inherits from &lt;code&gt;int&lt;/code&gt; in order to maintain compatibility with code that was written before this type was added. This is why &lt;code&gt;1 == True&lt;/code&gt; evaluates to be a true statement. It also means you can add &lt;code&gt;1 + True&lt;/code&gt; to get &lt;code&gt;2&lt;/code&gt;. Weird.&lt;/p&gt;
&lt;h2&gt;Coercion rules in Python&lt;/h2&gt;
&lt;p&gt;That PEP and its implementation defined how the interpreter will coerce values into Booleans, which is summarized in this table.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input value&lt;/th&gt;
&lt;th&gt;Coerced to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;An empty string&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The string &lt;code&gt;"False"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any other non-empty string&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The number &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any other number, positive or negative&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An empty list, object, or set&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A list, object, or set with members&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For most situations, those rules are great. However, you'd probably expect &lt;code&gt;bool("false")&lt;/code&gt; to return &lt;code&gt;False&lt;/code&gt; when in fact it returns &lt;code&gt;True&lt;/code&gt;. Lots of situations treat &lt;code&gt;-1&lt;/code&gt; as a falsey value. And silly humans use strings like "Yes", "No", and so forth to represent Boolean-like values. We can do better!&lt;/p&gt;
&lt;h2&gt;Digging into get_bool_from_any()&lt;/h2&gt;
&lt;p&gt;We finally have the background we need to understand the &lt;code&gt;get_bool_from_any()&lt;/code&gt; function above. The &lt;code&gt;try&lt;/code&gt; block that starts on line 7 handles numbers, as well as strings that can be converted to numbers like "0.1". For my purposes, it made sense that any value greater than 0 would be &lt;code&gt;True&lt;/code&gt; and the rest &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;float(val)&lt;/code&gt; call will raise an exception if &lt;code&gt;val&lt;/code&gt; can't be converted to a number. So the next thing the function handles is strings. As the comment says, I use &lt;code&gt;ast.literal_eval()&lt;/code&gt; rather than plain &lt;code&gt;eval()&lt;/code&gt; since it's less vulnerable to injection attacks. (This function comes from the &lt;code&gt;ast&lt;/code&gt; or Abstract Syntax Tree built-in library.) To handle upper- and lowercase variations, I convert to lowercase, then lead-capitalize the string. In this way, &lt;code&gt;"false"&lt;/code&gt; will be parsed and converted to &lt;code&gt;False&lt;/code&gt; as we'd expect.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ast.literal_eval()&lt;/code&gt; function will raise an exception in a couple of cases. If it can't parse the word &amp;mdash; in other words, it's not "False" or "True" &amp;mdash; I catch the &lt;code&gt;ValueError&lt;/code&gt; and test to see if the string is in my short list of falsey words. If so, the function returns &lt;code&gt;False&lt;/code&gt; otherwise it returns &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The other &lt;code&gt;except&lt;/code&gt; block takes care of empty strings and multi-word strings. On its own, &lt;code&gt;ast.literal_eval()&lt;/code&gt; raises a &lt;code&gt;SyntaxError&lt;/code&gt; in those situations. I just treat those cases as falsey values and return &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, I handle input values that are something other than a string or number. Line 27 uses Python's &lt;code&gt;bool()&lt;/code&gt; function to explicitly convert the input value to a Boolean. Following the rules described in the table above, this handles cases like empty or populated lists, objects, and so forth.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I really didn't need a function that was this comprehensive. The callback payloads I was handling always contained strings, and the values were always one of a short list of variations. I mean, my function could have been as simple as:&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;def&lt;/span&gt; &lt;span class="nf"&gt;is_it_true&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;True&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Yes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But, where's the fun in that? With my function, I handle just about any data type as an input value. I handle human-intuitive values like &lt;code&gt;-1&lt;/code&gt; being a falsey value. And, I got to explore some library functions I would rarely use otherwise. Figuring out the logic of the if-else and try-except blocks gave my brain a little exercise too.&lt;/p&gt;
&lt;p&gt;I hope you find the function useful, or you at least learned something from this post. If you find an error or an edge case, hit me up on Mastodon or put in a PR on this blog's repo; links are in the sidebar.&lt;/p&gt;</content><category term="Python"></category><category term="python"></category></entry><entry><title>Mr. Huntley's 1914 Chandler Light Six</title><link href="https://www.timpoulsen.com/2023/mr-huntleys-1914-chandler-light-six.html" rel="alternate"></link><published>2023-03-09T00:00:00-05:00</published><updated>2023-03-09T00:00:00-05:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2023-03-09:/2023/mr-huntleys-1914-chandler-light-six.html</id><summary type="html">&lt;h1&gt;Identifying the car in an old photo&lt;/h1&gt;
&lt;p&gt;One of the greatest parts of living in an old house is identifying the history of its occupants. In this photo, we see A.E. Huntley, the original owner of our house, looking rather dapper in his car. His grandaughter shared the photo …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Identifying the car in an old photo&lt;/h1&gt;
&lt;p&gt;One of the greatest parts of living in an old house is identifying the history of its occupants. In this photo, we see A.E. Huntley, the original owner of our house, looking rather dapper in his car. His grandaughter shared the photo with us a few years after we bought the house. While she knew who was in the photo, and where it was taken, she had no other information. I thought it would be great to identify the car, and therefore the age of the photo. But, the picture didn't give me much to go on.&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2023/huntley_car.jpg" title="click for larger size"&gt;&lt;img src="../images/2023/huntley_car.jpg" width="480" title="Mr. Huntley in his car"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Guessing a date based on the age of our house, a rough age for Mr. Huntley, and the general styling of the car, I figured the photo would have been taken sometime in the early 1900s, most likely in the teens or early 20s. Even under a magnifying glass, I could not make out any identifying marks on the car. There are no medallions, name plates, or really hardly any identifying features to the car. The shape of the doors is perhaps the most distinctive feature.&lt;/p&gt;
&lt;p&gt;I started searching with the most well-known cars of the era -- Ford, Chevrolet, etc. Nothing looked close. While Mr. Huntley was a successful dairy farmer, he was not wealthy. He certainly could not have afforded a Cord, Duesenberg, Pierce Arrow, etc. (Of course, it was fun looking at photos of those gorgeous vehicles.) Eventually I moved on to lesser-known and regional makers, of which there were many.&lt;/p&gt;
&lt;p&gt;I found out that a few car makers were based in the Rochester area in the early 1900s, including &lt;a href="https://www.libraryweb.org/~rochhist/v43_1981/v43i2-3.pdf" target="_blank" title="Rochester History, 1981"&gt;Seldon (PDF)&lt;/a&gt;, Foster, Cunningham, and Jenkins. One very promising candidate was the &lt;a href="http://www.coachbuilt.com/bui/m/mora/mora.htm" target="_blank" title="Coachbuilt.com: Mora Motor Car Co."&gt;Mora&lt;/a&gt;, built not too far down the road in Newark, NY.&lt;/p&gt;
&lt;p&gt;&lt;a href="../images/2023/o1907_Mora_ad_4.jpg" title="click for larger size"&gt;&lt;img src="../images/2023/o1907_Mora_ad_4.jpg" width="480" title="1907 Mora"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Look at those doors! But the overall shape was not correct. Also, the model year of that car is 1907, which was about a decade older than my guess on the photo's age. Still, I kept on researching Mora cars. There's not a lot of info or photos online. From what I found though, they made cars that looked like Mr. Huntley's.&lt;/p&gt;
&lt;p&gt;I tried asking friends and fellow-car enthusiasts, including co-workers at an automotive tech business I used to work for. My posts on Facebook and Reddit got me various answers, but none were very helpful (or correct). I didn't give up, but I did put the search on the back-burnder. The other day, someone replied to one of my rather old Reddit posts and pointed me to the Chandler. A little googling and bingo!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hallbergauction.hibid.com/lot/50903331/1914-chandler/"&gt;&lt;img src="../images/2023/hallbergaution_chandler.jpg" width="480" title="Hallberg Auction listing for a 1914 Chandler"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://hallbergauction.hibid.com/lot/50903331/1914-chandler/"  title="Hallberg Auction listing for a 1914 Chandler" target="_blank"&gt;1914 Chandler Light Six&lt;/a&gt; is a dead-ringer for Mr. Huntley's car. Compare the vents on the side of the hood, shape of the seats, spoke patterns on the wheels, the running board shape, and of course, those doors.&lt;/p&gt;
&lt;h2&gt;The Chandler Motor Car Company&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://www.hemmings.com/stories/article/1923-chandler-seven-passenger-sedan" target="_blank"&gt;Chandler Motor Car Company&lt;/a&gt; was located in Cleveland, OH. It was formed in 1913 by executives and other employees of the Lozier Motor Company. &lt;a href="https://www.hagerty.com/media/car-profiles/chandler-standard/" target="_blank"&gt;Lozier made high-end cars&lt;/a&gt;, in fact, some of the most expensive US-made cars of their day. Frederick C. Chandler, former-chief designer at Lozier, wanted to build quality mid-priced cars. So he left Lozier and started his own company.&lt;/p&gt;
&lt;p&gt;Chandler sold a 1914 six-cylinder touring car, probably what Mr. Huntley owned, for $1,785. According to &lt;a href="https://www.in2013dollars.com/us/inflation/1914?amount=1785" target="_blank"&gt;the CPI inflation calculator&lt;/a&gt;, that would be approximately $53,700 in 2023 dollars. I have no idea if he bought it new or used, though the car looks to be in great condition in the photo. I think it's very interesting that he bought a car from an Ohio manufacturer when there were quite a few makers of similar-class cars much closer to home.&lt;/p&gt;
&lt;p&gt;Chandler had a decent run, and by 1919 was &lt;a href="https://www.hemmings.com/stories/article/chandlers-fate" target="_blank"&gt;outselling Nash and Oldsmobile&lt;/a&gt;. They ranked seventh in export sales among U.S. automakers that year. Unfortunately, they expanded too quickly, ran out of money, and sold to Hupp Motors. While Hupp promised to keep the Chandler lines going, about a year later they shut down Chandler and instead used their factories to produce Hupp vehicles.&lt;/p&gt;
&lt;p&gt;I'll probably never know what happened to this car. He probably sold it or traded it in. Maybe it was lost in the fire that destroyed the barn. There are just a few Chandlers known to still exist. If this one were still around, it would be worth more than our house.&lt;/p&gt;
&lt;p&gt;&lt;i class="fa fa-wind red rotate180"&gt;&lt;/i&gt; &lt;i class="fa fa-car-side red"&gt;&lt;/i&gt;&lt;/p&gt;</content><category term="cars"></category><category term="cars"></category></entry><entry><title>My first custom PCB</title><link href="https://www.timpoulsen.com/2020/my-first-custom-pcb.html" rel="alternate"></link><published>2020-03-25T00:00:00-04:00</published><updated>2020-03-25T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2020-03-25:/2020/my-first-custom-pcb.html</id><summary type="html">&lt;p&gt;I had many troubles this year with my Christmas light electronics. Solder connections went bad multiple times. Wires got snagged and tangled during setup. It was tough re-soldering connections while sitting in a snowbank with the circuit hanging by its wires from a yard decoration.&lt;/p&gt;
&lt;p&gt;I decided I could fix …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I had many troubles this year with my Christmas light electronics. Solder connections went bad multiple times. Wires got snagged and tangled during setup. It was tough re-soldering connections while sitting in a snowbank with the circuit hanging by its wires from a yard decoration.&lt;/p&gt;
&lt;p&gt;I decided I could fix these problems by using a custom printed circuit board. With a custom PCB, components would be securely soldered to the board; there would be no wires to snag or pull free; and the final product would be easier to package in a waterproof container. Of course, I've never created a PCB before.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/custom_pcb_assembled.jpg" width="600" title="Assembled custom PCB"/&gt;&lt;br/&gt;&lt;span class="imgcaption"&gt;My custom PCB with components mounted&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;There are quite a few PCB design apps, many of them free or free for personal use. After checking out &lt;a href="http://pcb.geda-project.org/"&gt;gEDA&lt;/a&gt;, &lt;a href="https://kicad-pcb.org/"&gt;KiCAD&lt;/a&gt;, and a few others, I settled on &lt;a href="https://easyeda.com/"&gt;EasyEDA&lt;/a&gt;. It seemed to be fairly simple to use. Conveniently, they are affiliated with a PCB prototyping company. So, right from EasyEDA, you can upload your finished file to place an order (more on that later).&lt;/p&gt;
&lt;p&gt;Since this was my first PCB, I'm not the best person to teach how to design PCBs or even use EasyEDA. So, I'll point you to the tutorials that got me started.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://www.youtube.com/watch?v=MdcnkaAoDTE"&gt;Beginners guide to PCB design with EasyEDA Part 1&lt;/a&gt; (and parts 2 and 3) YouTube videos were probably the most helpful.&lt;/li&gt;
&lt;li&gt;EasyEDA has some videos on their site. They vary in quality. Check them out on the &lt;a href="https://www.youtube.com/channel/UCRoMhHNzl7tMW8pFsdJGUIA/videos"&gt;EasyEDA YouTube channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;EasyEDA also has many &lt;a href="https://docs.easyeda.com/en/Introduction/Introduction-to-EasyEDA/index.html"&gt;written tutorials&lt;/a&gt; that explain a lot of the terminology and back-details that you'll probably need to know. I read through many of these as the need arose.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Prototype first&lt;/h2&gt;
&lt;p&gt;It really helps to know what circuit you're going to create before you start designing your PCB. I'm sure expert circuit designers could start from scratch. But, I suggest you use a breadboard and jumpers to make sure your circuit design will work as intended before you try to turn it into a PCB. In my case, I had working prototypes of my circuit.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/pixel_node.jpg" width="600" title="My pixel controller prototype"/&gt;&lt;br/&gt;&lt;span class="imgcaption"&gt;My pixel controller prototype&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;EasyEDA&lt;/h2&gt;
&lt;p&gt;This software is available as either a download or a web app. Either way, you need to create a free account on their site because all the data is stored on their systems. The bright side of this is that you can switch back and forth between the installed app and the web app without issues.&lt;/p&gt;
&lt;p&gt;Working from your prototype, you draw your circuit using tools fairly similar to a normal drawing program. To start, you place components that you find in their library of items onto a schematic view (shown below). At this stage, you don't need to worry about arranging components as you would want them on the final PCB. Instead, you put them wherever and connect them via "net ports." These are like labeled flags specifying which parts will be electrically connected. For example, you would tag anything connected to ground with the GND net port.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/easy_eda_schematic.png" width="600" title="Schematic view of EasyEDA"/&gt;&lt;br/&gt;&lt;span class="imgcaption"&gt;Schematic view of EasyEDA&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Once you have all the components on the schematic, and all your net ports defined, EasyEDA creates the PCB view. This is the stage where you arrange components in a functional manner. You want to make sure "traces" (the electrical connections) can be neatly and logically drawn. You can't have traces cross, though you can draw traces on different layers to make connections where otherwise they'd have to cross. (Traces connect from one layer to another with a "via" basically a wire between layers.)&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/easy_eda_pcb.png" width="600" title="PCB designer view of EasyEDA"/&gt;&lt;br/&gt;&lt;span class="imgcaption"&gt;PCB designer view of EasyEDA&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The basic PCB purchase includes a two layer board. To save money, you'll want to stick to just two layers if you can. I spent a bunch of time moving components around so that my traces wouldn't need more than two layers.&lt;/p&gt;
&lt;p&gt;There are actually more layers that come with all boards. These include the top and bottom silk layers, which is where you can add labels and drawn features that are silk screened onto your PCB. There are others too, that are part of the PCB manufacturing process and which you probably don't need to worry about, such as the top and bottom paste layers. You can see all of these in the EasyEDA PCB view.&lt;/p&gt;
&lt;p&gt;The other cost constraint is that the cheapest board prices are for a 100mm by 100mm PCB. Conveniently, you can actually fit multiple PCBs in that space in what's called a "panelized layout." In my case, I fit all the components and traces in a roughly 50mm by 100mm area so I could get two PCBs in the allotted space. I paid for 10 PCBs, but since I got two boards in each, I ended up with 20 actual boards to play with. Note that you need to leave about 3mm dead space between the panels to allow space for the V-grooves (where the two PCBs snap apart.)&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/easy_eda_panelized_pcb.jpg" width="600" title="Panelized finished board"/&gt;&lt;br/&gt;&lt;span class="imgcaption"&gt;Panelized finished board&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Check and check again&lt;/h2&gt;
&lt;p&gt;I spent a bunch of time checking and re-checking my layout. You can hide various layers to help focus on certain connections. For example, I'd hide all but the top-layer traces to be sure each connected the pins I needed connected. Then I'd turn on other layers and re-check. Once your PCB is fabricated, you can't change the traces. So if you get it wrong, you'll be adding jumper wires or scraping off traces to break unwanted connections.&lt;/p&gt;
&lt;p&gt;When you're ready to go, you click an upload button which shows a screen like the following.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/easy_eda_upload_gerber.png" width="600" title="Export/Upload design to JLCPCB"/&gt;&lt;br/&gt;&lt;span class="imgcaption"&gt;Export/Upload design to JLCPCB&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;They make the "Order at JLCPCB" button extra obvious, and it is convenient. But if you want to use a different fab house, click the inconspicuous Generate Gerber button. Gerber files are the standard that most fabricators will need to create your PCB.&lt;/p&gt;
&lt;p&gt;In my case, I clicked that big blue order button. This uploads your gerber file to the JLCPCB web site. At that site, you choose options like board color, number to order, trace and over-solder materials to be used, and so forth. They offer a pretty generous discount price on your first order. Frankly, for a hobbyist like me, their normal prices are also quite reasonable.&lt;/p&gt;
&lt;h2&gt;The Audit&lt;/h2&gt;
&lt;p&gt;Once you've submitted your order, JLCPCB will review your circuit. They're not going to do any sort of in-depth checking of your traces or whether the PCB will function (at all, let alone how you want it to). Their audit is simply looking to confirm your PCB can be produced. In my case, my original upload failed the audit. I had panelized by design but failed to leave the required minimum space between panels. In a day or so, I got the following note:&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/easy_eda_audit.png" width="600" title="JLCPCB's audit says I failed :("/&gt;&lt;br/&gt;&lt;span class="imgcaption"&gt;JLCPCB's audit says I failed :(&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;If this happens to you, don't place a new order like I did. By doing so, you're no longer placing your "first order" and won't get that same discount. This time, instead of uploading from the EasyEDA app, export the gerber file. Then, go directly to the JLCPCB web site. There will be a "replace file" option under your failed order. Use it and re-submit. You should get the order at the original price using that method.&lt;/p&gt;
&lt;p&gt;Their shipping options aren't great. DHL is probably your best bet. It's not particularly cheap, or fast. Still, even during the early Coronavirus/Covid-19 disruption timeframe, my order showed up in roughly two weeks.&lt;/p&gt;
&lt;h2&gt;It's Alive!&lt;/h2&gt;
&lt;p&gt;The most important thing, of course, is that the board worked as intended (well, almost). I soldered on the components &amp;mdash; in my case, that was the NodeMCU Lolin board, a level shifter, capacitor, resistor, and three header connectors. I wired it to some NeoPixels and powered it up ... nothing. Turns out that there's nothing wrong with the board other than the resister in-line with the data out connection. I threw that in at the last minute after reading a resistor was necessary to prevent damage to the pixels. I've never used one before, and I don't think I will in the future. I soldered a shunt (wire) across the resister and it works just fine.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/easy_eda_in_use.gif" width="480" title="In use -- it works!"/&gt;&lt;br/&gt;&lt;span class="imgcaption"&gt;In use &amp;mdash; it works!&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Looking ahead&lt;/h2&gt;
&lt;p&gt;My custom PCB does exactly what I wanted it to do. Still, I may refine this board in a future order. Next time, I won't include the resistor in the design. I'll make the pin holes for the ESP8266 module larger (it was tough getting the pins through the holes) and those for the capacitor smaller (they're huuuge).&lt;/p&gt;
&lt;p&gt;I already have other boards in mind to create &amp;mdash; relay boards, similar boards for other chips, like the ESP32, and more. This was fun and uber-geeky to have created my own PCB.&lt;/p&gt;</content><category term="Making"></category><category term="electronics"></category><category term="pcb"></category></entry><entry><title>Remote work</title><link href="https://www.timpoulsen.com/2020/remote-work.html" rel="alternate"></link><published>2020-03-15T00:00:00-04:00</published><updated>2020-03-15T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2020-03-15:/2020/remote-work.html</id><summary type="html">&lt;p&gt;As I write this, Coronavirus / Covid-19 is on everyone's mind. For many, social distancing and self-quarantines will mean working from home for perhaps the first time. I've worked remotely for over 20 years. Let me share some of what has worked, and what hasn't for me.&lt;/p&gt;
&lt;p&gt;I'll start with some …&lt;/p&gt;</summary><content type="html">&lt;p&gt;As I write this, Coronavirus / Covid-19 is on everyone's mind. For many, social distancing and self-quarantines will mean working from home for perhaps the first time. I've worked remotely for over 20 years. Let me share some of what has worked, and what hasn't for me.&lt;/p&gt;
&lt;p&gt;I'll start with some general recommendations that concern mental and physical health as well as motivation. Then, I'll touch on some very specific tactics for remote working and teleconferencing. I'll finish up with some further tips to follow once life returns to a more normal (or "new normal") routine.&lt;/p&gt;
&lt;h2&gt;Health &amp;amp; well-being&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Keep a regular routine&lt;/strong&gt; &amp;mdash; Remember, you're still working even if you're doing it at home. Get up, shower and get dressed, then get started on work just like you would if you were going into the office. Try to maintain consistent start and stop times for work. Take regular breaks, at least as often as you would at the office. Eat lunch on a regular schedule. Stop work when the day is done. Of course, you can be a little more flexible, but you'll keep up your motivation and focus if you keep a regular work routine.&lt;/p&gt;
&lt;p&gt;Keeping a routine helps maintain a separation between work and personal time. Office workers get that separation thanks to the commute and physical separation between the office and home. You are not your job. Be sure to live your life.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Have a dedicated work space&lt;/strong&gt; &amp;mdash; Another way to maintain work/life separation is to have a dedicated work area in your home. It's best to have a separate room, with a door, especially if you live with family or others. Of course, not everyone has a spare room they can work in. Set up a dedicated area, a desk or table as separated from your relaxing spaces as you can manage. My preference is to not work from a bedroom or family room. Make sure to communicate to your family that when you're in that work spot, you shouldn't be disturbed because you're "at work."&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maintain your physical and mental health&lt;/strong&gt; &amp;mdash; Exercise, sleep right, eat well, and so forth. Get outside, even if for just a short time on your lunch break. Maintain your body, feed your soul.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make time for socializing&lt;/strong&gt; &amp;mdash; Going hand-in-hand with the previous, socialize as much as you can, at least like you would have at the office. Chat with your co-workers, have virtual lunches (over a video link), set up a watercool chat mechanism, and so forth. I'm in multiple Slack groups &amp;mdash; my work channel, a community group of fellow techies, a dedicated group for the robotics team I mentor, and so forth. Across those groups, I have access to channels to discuss hobbies, tech topics, and yes, even politics. These conversation channels help me maintain personal connections and not feel isolated.&lt;/p&gt;
&lt;p&gt;If your job typically involves face-to-face interactions (meetings, one-on-ones, even office socializing) bias towards video conferencing over voice or chat. Even if you normally just hunker down in your cube, get in some face time (over video) with co-workers. Video conferencing helps maintain the personal connections you have with your co-workers, customers, and vendors.&lt;/p&gt;
&lt;h2&gt;Tactical tips&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Mute yourself&lt;/strong&gt; &amp;mdash; Regardless of whether you're using audio or video channels, mute yourself if you're not talking to the group! As soon as you're done speaking, mute yourself again. Most of the videoconferencing systems enable you to mute attendees by default as they join the call. Use it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use a good headset&lt;/strong&gt; &amp;mdash; with a good mic, preferably a noise-canceling mic, for audio &amp;amp; video calls. Your co-workers will appreciate being able to hear you clearly and not hearing a ton of background noise and static. On the other hand, I like listening to music as I work. Specifically, I like to use speakers not headphones for this. YMMV.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Document and share&lt;/strong&gt; &amp;mdash; Document all decisions and take &amp;amp; share meeting minutes. It's harder to maintain focus and keep track of everything that goes on in a virtual meeting than an in-person meeting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start meetings on time&lt;/strong&gt; &amp;mdash; Whether you’re onsite or remote, show up on time. Attending meetings remotely is confusing enough already without the mess of random start times and late joiners.&lt;/p&gt;
&lt;h2&gt;When life returns to normal&lt;/h2&gt;
&lt;p&gt;Eventually, life will return to something close to its old routines. I hope this pandemic will foster more remote-friendly workplaces. With that in mind, you'll probably end up in a situation with a mix of on-site and remote workers. Here are some suggestions for when that time comes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get good A/V equipment for conference rooms &amp;mdash; the mic on your laptop doesn't cut it. You need a proper room mic.&lt;/li&gt;
&lt;li&gt;Make sure everyone in the office has proper headsets for those times when they have to participate in calls/meetings from their desks.&lt;/li&gt;
&lt;li&gt;If you're the organizer or sitting closest to the room mic, use the good presenter technique of repeating questions so that those on the call can hear.&lt;/li&gt;
&lt;li&gt;Avoid "hallway meetings" as a way of making decisions. And when that happens, make sure to inform your remote employees. Try to work in ways that include the remote employees (call them, quick grab a conference room and start a video chat, etc.)&lt;/li&gt;
&lt;li&gt;Conduct all meetings as if everyone is remote. Try to not all gather in one room (meet from your desks).&lt;/li&gt;
&lt;li&gt;Regularly check in with remote employees to make sure they know what tasks they're assigned, what roadblocks they've hit, etc.&lt;/li&gt;
&lt;li&gt;Whatever you use for video conferencing, use it consistently. I worked at one place where it was random, sometimes Slack calls, sometimes Hangouts, sometimes Zoom, sometimes whatever freebie service the organizer had discovered someplace. That confusion sucks for everyone involved.&lt;/li&gt;
&lt;li&gt;Practice good video conference hygiene — avoid stray noises, background conversations, mute yourself when not talking, let the remote person get a word in, ask them if they have input/questions. And for blessed sake, figure out connectivity and tech ahead of time so that not every call begins with “can you hear me?” and fumbling to get sound/video working. If a meeting must start late or early, make sure to let the remote folks know (and why).&lt;/li&gt;
&lt;li&gt;If the office gets together for socializing, be sure to do something nice for the remotes. For example, send them a dinner gift certificate or movie tickets.&lt;/li&gt;
&lt;li&gt;Avoid creating or working on teams where only one person is remote. It's too easy to leave them out or forget them, or bias your processes against remote employees.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are literally whole books about working remotely and fostering remote-friendly workplaces. So, I'll end this here. Working remotely can be great. But it can also suck if it's not done right. Good luck! If you get totally down on this temporary situation, reach out. My social links are in the sidebar. We might be "socially distanced" but we're not alone.&lt;/p&gt;</content><category term="Software Engineering"></category><category term="engineering"></category></entry><entry><title>Dark mode with CSS variables</title><link href="https://www.timpoulsen.com/2020/dark-mode-with-css-variables.html" rel="alternate"></link><published>2020-02-09T00:00:00-05:00</published><updated>2020-02-09T00:00:00-05:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2020-02-09:/2020/dark-mode-with-css-variables.html</id><summary type="html">&lt;p&gt;Dark mode is the hot schnitzel these days. Not wanting to be left behind, I recently updated my site to use a dark or light mode depending on your operating system preferences. Thanks to the magic of CSS media queries and CSS custom properties (otherwise known as CSS variables) this …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Dark mode is the hot schnitzel these days. Not wanting to be left behind, I recently updated my site to use a dark or light mode depending on your operating system preferences. Thanks to the magic of CSS media queries and CSS custom properties (otherwise known as CSS variables) this turned out to be rather easy.&lt;/p&gt;
&lt;h2&gt;CSS Media Queries&lt;/h2&gt;
&lt;p&gt;The Level 5 version of CSS media queries lets us specify styles based on the user's color scheme preferences. According to &lt;a href="https://caniuse.com/#feat=prefers-color-scheme" target="_blank"&gt;CanIUse.com&lt;/a&gt;, this query type is supported by all major browsers including Chrome, Firefox, Edge, and Safari. Internet Explorer and some Linux browsers do not support it.&lt;/p&gt;
&lt;p&gt;To use this media query, we'd add something like the following to our CSS file:&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="k"&gt;media&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;light&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="c"&gt;/* light mode styles here */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;media&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dark&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="c"&gt;/* dark mode styles here */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is great ... except I don't want to duplicate all my CSS rules to specify both light and dark mode colors. Thankfully, we can use CSS variables to limit duplication.&lt;/p&gt;
&lt;h2&gt;CSS variables&lt;/h2&gt;
&lt;p&gt;If you're not familiar with CSS variables, I highly recommend you check out some of the great articles at &lt;a href="https://css-tricks.com/?s=variables&amp;orderby=relevance&amp;post_type=post%2Cpage%2Cguide&amp;tag=css-variables" target="_blank"&gt;CSS Tricks&lt;/a&gt;. In a nutshell, we can define variable values, then use those variables in our rules. We need to specify a scope (where the variable is valid) and prefix the variable name with two dashes, 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="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;root&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="nv"&gt;--link-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;--some-other-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;white&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;In the code above, my variable names are &lt;code&gt;link-color&lt;/code&gt; and &lt;code&gt;some-other-color&lt;/code&gt; and the scope is &lt;code&gt;root&lt;/code&gt;, meaning the entire web page. While I'm showing colors here, you can specify any valid CSS values, for example, strings like 'center' for text alignment, numeric values, or special CSS keywords (like color names). Once I've defined my variables, I can use them in a CSS rule elsewhere in the file 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;a&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="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--link-color&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;The &lt;code&gt;var()&lt;/code&gt; syntax directs the CSS parser to use the variable's value &amp;mdash; in this case for the text color of a link. Check out the CSS Tricks site I linked to above. You can do math on variables, use them in animations, and a whole lot more. &lt;/p&gt;
&lt;h2&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;By using the media query, my site sets color variable values based on your preference for light or dark mode. My CSS rules use those variable values to set colors for the elements on the site. Here's a snippet of my site's main.css file:&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="k"&gt;media&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;light&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="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;root&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="nv"&gt;--sidebar-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;--sidebar-link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;191&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;--article-fg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;61&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;--article-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;228&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;228&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;228&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;--article-link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;media&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dark&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="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;root&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="nv"&gt;--sidebar-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;--sidebar-link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;191&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;--article-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;61&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;--article-fg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;191&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;--article-link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;191&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &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;And later in that CSS file, here are a couple of the rules I specify:&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;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;article&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="k"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--article-bg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--article-fg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;aside&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;leftbar&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;leftbar&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="k"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--sidebar-bg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;aside&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;leftbar&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="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--sidebar-link&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;a&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="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--article-link&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;By far, the hardest part was figuring out the various classes used by Pelican, the static site generator I use. Oh, and choosing colors.&lt;/p&gt;
&lt;h2&gt;It's Alive!&lt;/h2&gt;
&lt;p&gt;I think the coolest part of this technique is that it's applied automatically. If you change your OS preference, your browser will automatically update the page.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/darkmode.gif"&gt;&lt;/p&gt;
&lt;p&gt;There you have it, dark mode, CSS variables, and media queries. Powerful stuff!&lt;/p&gt;</content><category term="WebDev"></category><category term="html/css"></category><category term="webdev"></category></entry><entry><title>Controlling Christmas lights with MQTT</title><link href="https://www.timpoulsen.com/2020/controlling-christmas-lights-with-mqtt.html" rel="alternate"></link><published>2020-01-02T00:00:00-05:00</published><updated>2020-01-02T00:00:00-05:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2020-01-02:/2020/controlling-christmas-lights-with-mqtt.html</id><summary type="html">&lt;p&gt;I'm one of those Clark Griswold kind of guys that totally over-decorates his house. To make my lights more interesting, I've built my own light animation system which I call PiLit.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/christmas2019.gif" width="600" title="Christmas lights controlled by PiLit"/&gt;&lt;/p&gt;
&lt;p&gt;Of course, all my code is free and open source. Check it out at &lt;a href="https://github.com/skypanther/PiLit"&gt;https://github.com/skypanther/PiLit …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm one of those Clark Griswold kind of guys that totally over-decorates his house. To make my lights more interesting, I've built my own light animation system which I call PiLit.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/christmas2019.gif" width="600" title="Christmas lights controlled by PiLit"/&gt;&lt;/p&gt;
&lt;p&gt;Of course, all my code is free and open source. Check it out at &lt;a href="https://github.com/skypanther/PiLit"&gt;https://github.com/skypanther/PiLit&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;PiLit is made up of three components:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PiLit player&lt;/td&gt;
&lt;td&gt;The PiLit player is a python script that runs the show animation. It reads from a JSON "script" and sends commands on the network to control the lights.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nodes&lt;/td&gt;
&lt;td&gt;Nodes are the networked hardware components that control the lights.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PiLit GUI&lt;/td&gt;
&lt;td&gt;A show generation web app to make creating show sequences relatively easy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The player can run on any computer that supports Python and can connect to the same network as your nodes. I use a Raspberry Pi 3B+ for this. The player sends MQTT messages to the nodes with instructions of what light sequence to play. The nodes are smart devices in that they "know" how to perform various lighting functions. They listen for MQTT messages telling them what function to perform, but they do the actual controlling.&lt;/p&gt;
&lt;p&gt;There are a few benefits, and a few downsides to this architecture. On the plus side, much less data must be passed across the network and the nodes and player can use different architectures, programming languages, etc. In other words, the player can be a lot simpler but the nodes must be a bit more complex. Another downside is that to add new lighting functions, both the nodes and player need to be updated.&lt;/p&gt;
&lt;h2&gt;MQTT&lt;/h2&gt;
&lt;p&gt;I chose to use &lt;a href="https://www.baldengineer.com/mqtt-introduction.html"&gt;MQTT&lt;/a&gt; for message transport because there is very little overhead associated with the MQTT protocol. Messages are transmitted very quickly. Plus there are some great libraries for both Python and C/C++.&lt;/p&gt;
&lt;p&gt;In PiLit, messages are basically in the form &lt;code&gt;channel/payload&lt;/code&gt; where &lt;code&gt;channel&lt;/code&gt; is the name of the node that should act on the message and &lt;code&gt;payload&lt;/code&gt; is a string describing what animation to play. For example, a leaping arch node subscribed to an &lt;code&gt;arch1&lt;/code&gt; channel would listen for a message like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;arch1/red:bounce:5:50:true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The message is a colon-delimited string that tells the arch what color to show, what animation sequence to play, and additional timing and control values. See the repo for details on the actual message payloads.&lt;/p&gt;
&lt;p&gt;Nodes can subscribe to multiple channels, so a leaping arch might join both the &lt;code&gt;arch1&lt;/code&gt; channel and the &lt;code&gt;all_arches&lt;/code&gt; channel. If all my leaping arches have joined &lt;code&gt;all_arches&lt;/code&gt; then I can set them all to the same animation with a single MQTT message addressed to that channel.&lt;/p&gt;
&lt;h2&gt;Nodes&lt;/h2&gt;
&lt;p&gt;Nodes as I mentioned are smart devices. They are some type of microcontroller or single-board computer running code that I wrote. PiLit currently includes three types of nodes:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node type&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Lights that are controlled&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pixel_node&lt;/td&gt;
&lt;td&gt;Arduino/ESP8266&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;td&gt;RGB "neopixels"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;onoff_node&lt;/td&gt;
&lt;td&gt;Arduino/ESP8266&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;td&gt;Single relay (e.g. to turn on/off a spotlight)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;multi_relay&lt;/td&gt;
&lt;td&gt;Raspbery Pi&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Multi-channel relay (e.g. a Sainsmart 16-relay board)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For microcontrollers, you could use just about any Arduino-like board. The ESP8266s are great because they offer better specs than most Arduinos, at a lower price, and include WiFi support built in. The downside of most any of these microcontrollers is that they're 3.3V devices and the typical RGB LED is a 5V device. So, you'll need a level shifter to convert the 3.3 into 5V signals. You can generally buy 3.3V relays for on/off nodes, eliminating the need for a level shifter.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/pixel_node.jpg" width="600" title="A pixel_node in its box"/&gt;&lt;/p&gt;
&lt;p&gt;The first Christmas light device I ever created was my Raspberry Pi-based megatree controller. Using a cheap 16-channel relay board, I controlled outlets into which the strings of lights on my tree were plugged. A Python script running on the Pi turned the outlets on or off. &lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/multi_relay.jpg" width="600" title="My megatree multi-relay board"/&gt;&lt;/p&gt;
&lt;p&gt;I've rewritten and updated that original script to fit the PiLit architecture. But, since I had the hardware, I kept this node as a Python script. There's no reason an ESP8266 couldn't be used instead, though new node control software would be needed.&lt;/p&gt;
&lt;h2&gt;PiLit GUI&lt;/h2&gt;
&lt;p&gt;The show-generation app is for now called PiLit GUI. It's a React web app. Basically, I needed to learn React for work so this was a great "real world" app to learn on. (In other words, don't judge the code too harshly. I'm sure I have not written the best React app here.)&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2020/pilitgui.jpg" width="600" title="The PiLit GUI web app"/&gt;&lt;/p&gt;
&lt;p&gt;This app lets you define one or more channels, each of which corresponds to a node. Then, you define a series of animations for each channel. When done, you export your show to a JSON file. This file is the script you run with the PiLit Player.&lt;/p&gt;
&lt;h2&gt;Challenges&lt;/h2&gt;
&lt;p&gt;The system overall works well. The software portions of PiLit have worked out great. But I have had to deal with some physical / hardware challenges.&lt;/p&gt;
&lt;p&gt;Keeping water out of the nodes has been a big problem. I put each in some sort of plastic container (an old food container, craft box, etc.). But of course, wires must go in and out of the boxes. Despite my best attempts, water has gotten in causing shorts. One box filled with water, then froze. The ESP8266 in that node did not like being frozen in a block of ice -- it fried. I also discovered that exterior latex caulk dissolves when exposed to constant moisture. Next year I'll use bathtub style caulk instead.&lt;/p&gt;
&lt;p&gt;Another challenge has been solid wiring connections. The pixel strips are rather delicate. Wind and ice have broken my connections a few times. Apparently I'm not great at soldering since my connections at the microcontrollers have failed repeatedly too. More than once I've had to drag out my soldering tools and sit in the snow re-soldering connections.&lt;/p&gt;
&lt;h2&gt;Looking ahead&lt;/h2&gt;
&lt;p&gt;I designed and wrote PiLit, and built most of my nodes, in my spare time in September and November this year. Over the next year, I plan to refine my physical node builds to better protect from the elements. I also plan to enhance the software components. A few areas I may tackle include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improving timing &amp;mdash; currently it's impossible to synchronize multiple nodes exactly. So for example, my three leaping arches don't "bounce" in time with each other.&lt;/li&gt;
&lt;li&gt;Adding more lighting sequences to the nodes, as well as improving performance.&lt;/li&gt;
&lt;li&gt;Updating the GUI show-creation app in various ways, such as offering a show preview mode and refining its UI.&lt;/li&gt;
&lt;li&gt;Adding more instructions on building the nodes, as well as on setting up and using PiLit in general.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, I'd really like to add an LED matrix display (to show images, video clips, and text) to my decorations. I'd also like to add an FM transmitter and audio track to my show. I will update PiLit to support those features if I can add those elements.&lt;/p&gt;</content><category term="Making"></category><category term="python"></category><category term="arduino"></category><category term="xmas"></category></entry><entry><title>Multiprocessing in Python</title><link href="https://www.timpoulsen.com/2019/multiprocessing-in-python.html" rel="alternate"></link><published>2019-04-15T00:00:00-04:00</published><updated>2019-04-15T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2019-04-15:/2019/multiprocessing-in-python.html</id><summary type="html">&lt;p&gt;Modern CPUs typically feature multiple cores, which in some sense is like having multiple computers at your disposal. By default, your Python code will run on one core. But when performance is critical, you can use multiple cores to run operations simultaneously. In this article, I'll walk through how we …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Modern CPUs typically feature multiple cores, which in some sense is like having multiple computers at your disposal. By default, your Python code will run on one core. But when performance is critical, you can use multiple cores to run operations simultaneously. In this article, I'll walk through how we used multiprocessing in our FIRST Robotics code to run our scripts across all four cores of our Jetson TX2 board.&lt;/p&gt;
&lt;p&gt;Python supports both multithreading and multiprocessing. Judging by the number of StackOverflow posts on the topic, there's a ton of confusion surrounding these terms. There are many &lt;a href="https://realpython.com/python-concurrency/" target="_blank"&gt;articles&lt;/a&gt; explaining the differences and nuances of multithreading and multiprocessing. Here's my take:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multithreading &amp;mdash; run portions of your code &lt;strong&gt;asynchronously&lt;/strong&gt; so that your script doesn't have to wait for &lt;strong&gt;I/O bound operations&lt;/strong&gt;; but all your code runs on the same CPU core, and &lt;strong&gt;shares the same memory and variable space.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Multiprocessing &amp;mdash; run portions of your code &lt;strong&gt;simultaneously&lt;/strong&gt; across multiple cores so that you script doesn't have to wait for &lt;strong&gt;CPU bound operations&lt;/strong&gt;; because your code runs across multiple cores, each process has its own &lt;strong&gt;separate memory and variable space.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use multithreading if your script needs to access data across the network, read a lot of data from disk, or do other I/O bound operations. Multithreading isn't really running code simultaneously. It's just not stopping everything while waiting for the I/O operation to finish.&lt;/p&gt;
&lt;p&gt;Use multiprocessing if your script does a lot of computation, such as processing image frames from a webcam stream. You might employ multithreading to do the camera I/O. But multiprocessing would let you do the processing simultaneously with other code your script might perform. Sharing information between processes is more difficult since processes don't share memory or variables.&lt;/p&gt;
&lt;h2&gt;Multiprocess-bot&lt;/h2&gt;
&lt;p&gt;While we used both on our 2019 FRC bot, I'm going to focus on multiprocessing. Here are the tasks we programmed our Jetson to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One process captured frames from an IP camera, did target identification and field orientation calculations, then wrote the results to NetworkTables so that the data was available to the robot and driver computers.&lt;/li&gt;
&lt;li&gt;Another process captured frames from a pair of web cams, received data via a queue, and used that data to create custom overlays atop the video feeds. Then it streamed those processed feeds across the network to the driver computer.&lt;/li&gt;
&lt;li&gt;And we had a third process that instantiated and managed those processes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Starting multi-process scripts&lt;/h2&gt;
&lt;p&gt;Let's start by seeing how we can start our multiple processes. (You might not want to actually run code like this till I cover how to stop the processes a bit later in this article.) In &lt;a href="https://github.com/Raider-Robotics-Team-1518/Jetson/blob/master/parallelized/main.py" target="_blank"&gt;the main.py file&lt;/a&gt;, we import the two custom classes of our bot program, and then call their &lt;code&gt;start()&lt;/code&gt; methods:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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 our bot-specific classes&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;camstreamer&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Camstreamer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;targeting&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Targeting&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;camstreamer_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Camstreamer&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;span class="n"&gt;target_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Targeting&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;span class="n"&gt;camstreamer_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;target_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&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;That part looks pretty standard. The magic that enables multiprocessing is actually put in those custom classes. Let's take a look at the (simplified) code of the &lt;a href="https://github.com/Raider-Robotics-Team-1518/Jetson/blob/master/parallelized/targeting.py" target="_blank"&gt;targeting.py script&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;multiprocessing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Process&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Targeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&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="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Targeting&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="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# other initialization stuff here&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&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="c1"&gt;# do the work of your class in this method&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;Some key points here: we import Process and then inherit from it when defining our class. This gives our class the capabilities to run as a distinct process. Then, as the first statement in our class's init method, we call the superclass's init method. For probably no good reason, I'm using the older Python 2.x syntax in that line. With Python 3.x, you can simplify that to &lt;code&gt;super().__init__()&lt;/code&gt; (or use the older syntax). For more information on super(), check &lt;a href="https://www.pythonforbeginners.com/super/working-python-super-function" target="_blank"&gt;here&lt;/a&gt; and &lt;a href="https://stackoverflow.com/questions/576169/understanding-python-super-with-init-methods" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As you saw above, we'll call our class's &lt;code&gt;start()&lt;/code&gt; method to actually start the process. But you don't provide a start method in your subclass. Instead, you have to create a method named &lt;code&gt;run()&lt;/code&gt;. The inherited start() method will call your run() method. It seems a little weird, but that's how it works.&lt;/p&gt;
&lt;h2&gt;Pipes and Queues&lt;/h2&gt;
&lt;p&gt;As I mentioned above, process don't share memory or variables. To communicate between processes, we need to use &lt;a href="https://docs.python.org/3/library/multiprocessing.html#pipes-and-queues" target="_blank"&gt;pipes or queues&lt;/a&gt;. Pipes are fast, one-way communication channels between a sender/receiver pair &amp;mdash; put a message in at one end and it's immediately available at the other. Multiprocessing queues are two-way channels between multiple senders and receivers. They work like other FIFO queues you might be familiar with &amp;mdash; the sender places a message onto the queue, the receiver pulls off the first-available message on the queue.&lt;/p&gt;
&lt;p&gt;We used queues to share field / positional data between the processes. The targeting script would calculate its values and put them into the queue. The camera streamer class would pull values off the queue and use them in constructing the overlays. Let's see how that was accomplished.&lt;/p&gt;
&lt;p&gt;In &lt;a href="https://github.com/Raider-Robotics-Team-1518/Jetson/blob/master/parallelized/main.py" target="_blank"&gt;the main.py file&lt;/a&gt;, we create the queue instance and pass it to each of the processes that will use it. In this way, each process has a reference to the queue in its memory space. (Only thread/process-safe constructs, like Pipes and Queues can be shared between processes like this.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;multiprocessing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;targeting_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;camstreamer_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Camstreamer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targeting_queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;targeting_queue&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;target_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Targeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targeting_queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;targeting_queue&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To write data to the queue, the &lt;a href="https://github.com/Raider-Robotics-Team-1518/Jetson/blob/master/parallelized/targeting.py" target="_blank"&gt;targeting.py script&lt;/a&gt; just needed to call &lt;code&gt;put()&lt;/code&gt; on the queue reference passed to the class, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;targeting_queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_data&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;Our bot's &lt;a href="https://github.com/Raider-Robotics-Team-1518/Jetson/blob/master/parallelized/camstreamer.py" target="_blank"&gt;camera streamer class&lt;/a&gt; read data from the queue. In that script, we used the &lt;code&gt;get_nowait()&lt;/code&gt; method to read values from the queue. We could have used the &lt;code&gt;get()&lt;/code&gt; method. However that function is blocking, meaning your script would pause till data was available on the queue. The &lt;code&gt;get_nowait()&lt;/code&gt; method doesn't pause for data to be available, so it is faster. But it will throw an exception if there's nothing on the queue to read. To handle that, we just wrapped it in a try/except block, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;field_data&lt;/span&gt; &lt;span class="o"&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;targeting_queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_nowait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# use the field data to draw the overlays&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# exception thrown if there&amp;#39;s nothing in the queue to read&lt;/span&gt;
    &lt;span class="k"&gt;pass&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;We probably could have used a pipe for this communication, since it was a one-way stream. But, a queue has the advantage of holding multiple values. So, if the sender script gets ahead, messages will still be available to be processed when the reader is available. Pipes pass just a single value. If a new one is written before the previous value is read it will "push" the first value out of the pipe.&lt;/p&gt;
&lt;h3&gt;Stopping background processes&lt;/h3&gt;
&lt;p&gt;Once a script is launched as a separate process, it will run till stopped (or you kill it at the operating system level). During the competition, we were fine with the scripts running till we shut down our Jetson board. But, that's not typically how you'd manage your multiprocessing scripts.&lt;/p&gt;
&lt;p&gt;Classes that inherit from Process have a &lt;code&gt;terminate()&lt;/code&gt; method. When called, it will abruptly halt the process. If you need to close connections or otherwise gracefully stop your script, you'll want to signal it that it's about to be terminated. Once the process finishes its cleanup, then you call terminate().&lt;/p&gt;
&lt;p&gt;In our bot program, we implemented this signalling using Pipes. In main.py, we listened for the Escape or "q" key to be pressed. When that happened, we sent a message across each child process's pipe that a shutdown was imminent. The child scripts would then stop their actions and clean up. After a brief delay, main.py would terminate the processes.&lt;/p&gt;
&lt;p&gt;Looking again at main.py:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;multiprocessing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pipe&lt;/span&gt;

&lt;span class="n"&gt;stop_pipes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cs_reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cs_writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duplex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;trgt_reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trgt_writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duplex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stop_pipes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cs_writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stop_pipes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trgt_writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;targeting_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;camstreamer_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Camstreamer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targeting_queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;targeting_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stop_pipe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cs_reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;target_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Targeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targeting_queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;targeting_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stop_pipe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;trgt_reader&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;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# wait for Esc or q key and then exit&lt;/span&gt;
        &lt;span class="n"&gt;key&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;waitKey&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="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;q&amp;quot;&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;destroyAllWindows&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;pipe&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stop_pipes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;camstreamer_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;target_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;exit&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;When instantiating a pipe, you get back a tuple whose values represent the read and write ends of the pipe. As shown in the snippet above, we created those pipe instances and passed the read ends to our child processes. In main.py's while loop, we use the write ends of the pipes (which were stored in a list) to put the string "stop" onto the pipe when the user signaled to quit.&lt;/p&gt;
&lt;p&gt;In our child processes, all the computation was done inside an endless &lt;code&gt;while True:&lt;/code&gt; loop. At the start of each iteration, we'd poll to see if data was available on the pipe. If so, and if it were the string "stop" we'd break out of the loop.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&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="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&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;stop_pipe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="c1"&gt;# try reading from the stop pipe; if it&amp;#39;s not empty&lt;/span&gt;
            &lt;span class="c1"&gt;# this block will work, and we&amp;#39;ll exit the while&lt;/span&gt;
            &lt;span class="c1"&gt;# loop and terminate the script&lt;/span&gt;
            &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="o"&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;stop_pipe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&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;Pipes are great for this sort of one-way communication. The main.py control script sent out stop messages, and the child messages listened for those messages.&lt;/p&gt;</content><category term="Python"></category><category term="python"></category></entry><entry><title>Robovision on our 2019 FRC bot</title><link href="https://www.timpoulsen.com/2019/robovision-on-our-2019-frc-bot.html" rel="alternate"></link><published>2019-04-15T00:00:00-04:00</published><updated>2019-04-15T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2019-04-15:/2019/robovision-on-our-2019-frc-bot.html</id><summary type="html">&lt;p&gt;Now that our 2019 FRC season is over, I'd like to describe how Team 1518 implemented vision processing on our bot. We set some aggressive goals, which we aimed to achieve by using the the &lt;a href="https://www.timpoulsen.com/2019/introducing-robovision.html"&gt;robovision&lt;/a&gt; library, OpenCV, and Python on a Jetson TX2. The primary goals of this post …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Now that our 2019 FRC season is over, I'd like to describe how Team 1518 implemented vision processing on our bot. We set some aggressive goals, which we aimed to achieve by using the the &lt;a href="https://www.timpoulsen.com/2019/introducing-robovision.html"&gt;robovision&lt;/a&gt; library, OpenCV, and Python on a Jetson TX2. The primary goals of this post are to cover the high-level approaches we used and to share what did or didn't work. Of course, I'll link to the code we created.&lt;/p&gt;
&lt;p&gt;We set out the following goals for vision on our bot this year:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Identify and isolate the retro-reflective tape targets&lt;/li&gt;
&lt;li&gt;Determine our distance to those targets&lt;/li&gt;
&lt;li&gt;Determine our angle to the plane of the targets&lt;/li&gt;
&lt;li&gt;When aiming reasonably face-on, determine how far left or right of center we were situated relative to the targets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We had a few other needs, such as communicating with the RoboRIO and driver's station, and maintaining good performance while performing multiple tasks. We also decided to control a set of RGB "neopixel" LEDs to use as signals to the driver of the bot's state. And, we planned to use the Jetson to stream a pair of cameras back to the driver's station, with custom overlays.&lt;/p&gt;
&lt;p&gt;That's a significant pile-of-priorities and I'm very proud of our sole vision sub-team programmer Emma. Working together, she and I accomplished all but one of those goals on the test field. Unfortunately, we never got to put it into "production." At the Finger Lakes Regional (FLR), our bot was overweight and we had to leave off the Jetson. Between then and Buckeye, we shaved off enough weight to permit the Jetson. But by then, the drive team was comfortable driving the bot manually and didn't want to deal with the new controls. Just the same, our system did work and we learned a lot from implementing it.&lt;/p&gt;
&lt;div&gt;&lt;iframe width="640" height="360" src="https://www.youtube.com/embed/KfhcVvqHO0U" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;h2&gt;Targeting&lt;/h2&gt;
&lt;p&gt;The core function of our efforts were target identification and measurement. As shown below, a pair of retro-reflective vision tapes were mounted aside each hatch opening. The 2019 Game Manual listed various details of the tapes, for example that they were tipped at inward at 14.5&amp;deg; angles. Our goal was to isolate those tapes and make sure we were looking at a matched pair. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Integration diagram" src="../images/2019/vision_targets.jpg"&gt;&lt;/p&gt;
&lt;p&gt;At a high level, we:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Grabbed frames from the camera&lt;/li&gt;
&lt;li&gt;Used contour detection, looking for specific colors that represented the tape targets&lt;/li&gt;
&lt;li&gt;Used a series of filters to be sure we were looking at a matched pair of tape targets while discarding false matches (e.g. the right tape of one set and the left tape of another)&lt;/li&gt;
&lt;li&gt;Measured various characteristics of the targets when we found them&lt;/li&gt;
&lt;li&gt;Shared that info with the Rio via NetworkTables and with the Arduino controlling our LEDs via serial-over-USB.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That's all implemented in the &lt;a href="https://github.com/Raider-Robotics-Team-1518/Jetson/blob/master/parallelized/targeting.py" target="_blank"&gt;targeting.py&lt;/a&gt; file in the team's repo. There's a lot going on in this file. I'll go into some of the details below.&lt;/p&gt;
&lt;h3&gt;Finding and isolating the targets&lt;/h3&gt;
&lt;p&gt;The robovision library includes a pre-built contour detection method. We just needed to supply a range of colors to look for. We used the standard green LED ring light that comes in the FRC "kit of parts." We used an Axis IP camera with its exposure and brightness settings cranked about as low as they'd go. Through experimentation, we found that we could isolate the reflective tape by looking for colors between HSV (60, 100, 100) and HSV (100, 255, 255). &lt;/p&gt;
&lt;p&gt;Looking at the &lt;a href="https://github.com/Raider-Robotics-Team-1518/Jetson/blob/752d2038b11c970363e4b3f2451afebb596343b8/parallelized/targeting.py#L69" target="_blank"&gt;&lt;code&gt;run()&lt;/code&gt; function&lt;/a&gt; you can see we call robovision's &lt;code&gt;target.get_contours(frame)&lt;/code&gt; in each iteration of the loop. Then, we call our own &lt;code&gt;self.process_contours(contours)&lt;/code&gt; function. It gives us back info like whether a target is in view.&lt;/p&gt;
&lt;p&gt;In that function, we sort the contours left-to-right so that we can methodically step through them in a known order. Then, we pass them through a series of "filters" to eliminate false detections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Aspect ratio &amp;mdash; the tape targets were 2-inches by 5.5-inches, giving an aspect ratio of 0.36. Any contour with an aspect ratio significantly different than this was ignored. &lt;/li&gt;
&lt;li&gt;Solidity &amp;mdash; a contour that encloses a "non-solid" region (think of something shaped like a letter "C") wasn't likely to be one of the rectangular tape targets. So, we'd ignore it.&lt;/li&gt;
&lt;li&gt;Angle &amp;mdash; the tape strips were attached at 14.5&amp;deg; angles. Contours that were not oriented at angles close to that were ignored. Robovision provides a convenient &lt;code&gt;target.get_skew_angle(contour)&lt;/code&gt; function for finding the contour orientation angle.&lt;/li&gt;
&lt;li&gt;And to be a valid target, we need a pair of tapes, one tilted right and one tilted left.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Positional orienteering&lt;/h3&gt;
&lt;p&gt;Once we had weeded out any false detections, we moved on to measuring and calculating. We used the "triangle similarity" technique, described on the &lt;a href="https://www.pyimagesearch.com/2015/01/19/find-distance-camera-objectmarker-using-python-opencv/" target="_blank"&gt;PyImageSearch blog&lt;/a&gt;, to measure the bot's distance from the tape targets. &lt;/p&gt;
&lt;p&gt;In the "lab," we measured the perceived width in pixels of a 12" piece of reflective tape at various distances. This gave us a ratio of pixels seen in the image to actual distance. At game time, we used robovision's target.get_extreme_points(contour) function to find the x/y coordinates of the corners of the tape targets. Using those coordinates, we counted the pixels between the top-most and bottom-most points. We plugged that pixel count, plus our precalculated ratio, into the triangle formula to determine the bot-to-target distance. This technique was accurate to a fraction of an inch.&lt;/p&gt;
&lt;p&gt;We also used those corner coordinates to determine how far left or right the bot was in relation to the midpoint between the tape targets. Our original bot design included an articulated ball/hatch-cover manipulator. We planned to use this offset info to move that arm, alleviating the need for the bot to be centered on the hatch. We were able to calculate the offset within a fraction of an inch.&lt;/p&gt;
&lt;p&gt;We were not successful in calculating the bot's angle to the hatch (plane of the target tapes). We tried a few techniques, none of which worked. For example, basic trigonometry tells us that the arc-cosine of the distance between the tapes divided by the distance to the tape equals the approach angle. In practice, we never got a good result. We decided that there were just too few pixels to work with, leading to gross inaccuracies. This is definitely an area we'll explore more over the off-season.&lt;/p&gt;
&lt;h3&gt;Communicating with the RoboRIO&lt;/h3&gt;
&lt;p&gt;Per FRC rules, the Rio must control the bot's actions. The parameters calculated by our vision code could be inputs to the Rio's routines. But for that to work, we needed to pass the calculated values in realtime to the Rio.&lt;/p&gt;
&lt;p&gt;We considered a couple of options, including streaming data across a custom socket connection. In the end, we settled on a super-simple technique. The wonderful folks at the &lt;a href="https://robotpy.readthedocs.io/en/stable/" target="_blank"&gt;RobotPy&lt;/a&gt; project have created a NetworkTables implementation that worked flawlessly for us. We simply wrote our calculated values to a NetworkTable and the Rio read them from there. (Note: NetworkTables might be too slow for a high-speed, shooting game.)&lt;/p&gt;
&lt;h3&gt;Powering the Jetson&lt;/h3&gt;
&lt;p&gt;Our original plan was to power the Jetson from a cellphone powerpack. We found one on Amazon that output 12V and wasn't too heavy. This would have given us a stable power source that wouldn't abruptly shut off when the bot powered down. Unfortunately, the inspectors at Buckeye would not permit us to use that battery because they said it had too high an amperage rating. &lt;/p&gt;
&lt;p&gt;After some research, we found that the Jetson TX2 is set up to handle an "uncontrolled" power loss by going through a &lt;a href="https://devtalk.nvidia.com/default/topic/1030971/jetson-tx2/effects-when-the-vdd_in-cannot-keep-longer-than-20ms-in-an-uncontrolled-powerdown/" target="_blank"&gt;fast shutdown process.&lt;/a&gt; According to the support forums, a power loss shouldn't cause filesystem corruption issues. So, we wired the Jetson directly to the power distribution panel (PDP) and it seemed to work fine. Of course, we didn't use it on the field so your mileage may vary.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;We were intrigued by the Limelight system that some teams used this year. It promised to provide prebuilt routines to do many of the tasks we had to code ourselves this year. Nvidia just released the Jetson Nano, which is much smaller and lighter and runs off a 5V power source. Our TX2 may be retired in favor of one of these other systems. But the techniques we learned this year have given us a deeper understanding of vision processing. We'll put that knowledge to good use next year regardless of which system we use to implement our vision subsystem.&lt;/p&gt;
&lt;p&gt;Per FIRST's rules for future re-use, all our code is on our &lt;a href="https://github.com/Raider-Robotics-Team-1518/Jetson" target="_blank"&gt;GitHub&lt;/a&gt; repo. Feel free to explore it and use it in your projects; there's some cool stuff going on in there. In a future post, I'll go over the parallel processing code that we used to run multiple scripts across the cores of the Jetson TX2.&lt;/p&gt;</content><category term="OpenCV"></category><category term="robotics"></category><category term="opencv"></category><category term="python"></category></entry><entry><title>Introducing Robovision</title><link href="https://www.timpoulsen.com/2019/introducing-robovision.html" rel="alternate"></link><published>2019-03-10T00:00:00-05:00</published><updated>2019-03-10T00:00:00-05:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2019-03-10:/2019/introducing-robovision.html</id><summary type="html">&lt;p&gt;The FIRST robotics competition (FRC) challenges high school students to design and build a robot capable of performing multiple challenging tasks. These annual challenges typically involve computer vision components, such as identifying and using reflective markers to locate targets. High school computer science curriculum rarely covers software engineering topics, let …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The FIRST robotics competition (FRC) challenges high school students to design and build a robot capable of performing multiple challenging tasks. These annual challenges typically involve computer vision components, such as identifying and using reflective markers to locate targets. High school computer science curriculum rarely covers software engineering topics, let alone advanced topics like computer vision.&lt;/p&gt;
&lt;p&gt;To help FRC teams, I have written the robovision python library. This library includes functions useful for the types of vision tasks typically involved in an FRC competition. The goal of this library is to reduce and hide some of the complexity involved with target identification, measuring, field orienteering, etc.&lt;/p&gt;
&lt;p&gt;Robovision is open source, licenced under the permissive MIT license, and free for all FRC teams to use. You can install it from &lt;a href="https://pypi.org/project/robovision/" target="_blank"&gt;PyPI&lt;/a&gt; or directly from the &lt;a href="https://github.com/skypanther/robovision" target="_blank"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some of the functions included in robovision are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multi-threaded image acquisition from a web cam, IP cam (i.e. Axis cam), Raspberry Pi camera, or Jetson onboard gstreamer camera&lt;/li&gt;
&lt;li&gt;Lens distortion removal based on the camera calibrations created with the provided autocalibrate.py script&lt;/li&gt;
&lt;li&gt;Retroreflective target identification, contour finding, and geometry finding functions&lt;/li&gt;
&lt;li&gt;Image resizing, equalization, brightness and contrast adjustments, and more&lt;/li&gt;
&lt;li&gt;A preprocessor class, which enables you to set up a pipeline of functions that will be applied in series to an image.&lt;/li&gt;
&lt;li&gt;Overlay arrows, text, borders, or crosshairs on images&lt;/li&gt;
&lt;li&gt;Rolling (moving) average calculations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Robovision complements the &lt;a href="https://robotpy.readthedocs.io/en/stable/" target="_blank"&gt;RobotPy library&lt;/a&gt;. It does not duplicate functionality, nor is it meant to replace that excellent project. Team 1518 uses both robovision and the cscore and networktables components of RobotPy.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;Requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python 3.5+ (2.x is not supported)&lt;/li&gt;
&lt;li&gt;OpenCV 3.4+ (4.x will probably work, but is untested)&lt;/li&gt;
&lt;li&gt;Numpy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Robovision is meant to run on a coprocessor (e.g. a Jetson or Raspberry Pi) and hasn't been tested on the RoboRio itself. While it probably works on a Windows computer, it was developed and is tested only on Mac OS and Linux systems.&lt;/p&gt;
&lt;p&gt;Installation details are covered in the wiki's &lt;a href="https://github.com/skypanther/robovision/wiki/Installation-and-System-Setup" target="_blank"&gt;Installation and System Setup&lt;/a&gt; page.&lt;/p&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;p&gt;Here's a sample of how you might use robovision to calculate the distance in inches to a 12" piece of retro-reflective tape held horizontally and face-on to an IP camera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="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;robovision&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;rv&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://10.15.18.100/mjpg/video.mjpg&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;fl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;  &lt;span class="c1"&gt;# Calculated apparent focal length of your camera&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;namedWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CapturedImage&amp;#39;&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;WINDOW_NORMAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# connect to our streaming video source and start the capture thread&lt;/span&gt;
&lt;span class="n"&gt;vs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_video_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# instantiate a robovision Target object which does all the work of finding&lt;/span&gt;
&lt;span class="c1"&gt;# and isolating the retro-reflective tape&lt;/span&gt;
&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_color_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&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;255&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_frame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# get a list of contours around objects within the color range set&lt;/span&gt;
    &lt;span class="c1"&gt;# above; these will be the retro-reflective tape assuming you&amp;#39;re using&lt;/span&gt;
    &lt;span class="c1"&gt;# the standard AndyMark green LED light source&lt;/span&gt;
    &lt;span class="n"&gt;contours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_contours&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;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contours&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;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;# draw a red border around each of the contours that were found&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;drawContours&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;contours&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="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;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# assuming you&amp;#39;re holding the tape horizontally, you need only&lt;/span&gt;
        &lt;span class="c1"&gt;# the width (in pixels) of the detected object&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;for_contour&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;contours&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="c1"&gt;# Distance from formula: D’ = (W x F) / P&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;fl&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Distance to tape: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt; inches&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CapturedImage&amp;#39;&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="c1"&gt;# wait for Esc or q key and then exit&lt;/span&gt;
    &lt;span class="n"&gt;key&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;waitKey&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="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;q&amp;quot;&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;destroyAllWindows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;break&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;h3&gt;Examples and documentation&lt;/h3&gt;
&lt;p&gt;I include a selection of example scripts in the &lt;a href="https://github.com/skypanther/robovision/tree/master/extras" target="_blank"&gt;extras folder&lt;/a&gt; in the GitHub repo. While not really production ready, these show how to perform selected vision tasks that might be helpful for an FRC challenge. There's also a couple of scripts to help you do lens calibration (used to remove lens distortions, also called field flattening).&lt;/p&gt;
&lt;p&gt;Be sure to check out the &lt;a href="https://github.com/skypanther/robovision/wiki" target="_blank"&gt;project's wiki&lt;/a&gt; for documentation on the library's classes and functions. You'll need the numpy and OpenCV python packages to use robovision. Some of the example scripts use additional libraries. &lt;/p&gt;
&lt;p&gt;FRC Team 1518, Raider Robotics used robovision successfully in its 2019 Deep Space Challenge bot. So, another good source of examples (as well as some "incomplete thoughts") is the &lt;a href="https://github.com/Raider-Robotics-Team-1518/Jetson" target="_blank"&gt;team's GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Future&lt;/h3&gt;
&lt;p&gt;Looking ahead, the library needs even more simplification and abstraction to make it easy for FRC teams to use. Additional documentation and examples are also needed. I have not extensively tested or optimized the library for the Raspberry Pi (we're using a Jetson TX2, which has plenty of horsepower for our vision tasks). I very much welcome pull requests and contributions to the project.&lt;/p&gt;
&lt;p&gt;#omgrobots!&lt;/p&gt;</content><category term="OpenCV"></category><category term="python"></category><category term="opencv"></category><category term="robotics"></category></entry><entry><title>Using OpenCV in an iOS app</title><link href="https://www.timpoulsen.com/2019/using-opencv-in-an-ios-app.html" rel="alternate"></link><published>2019-02-01T00:00:00-05:00</published><updated>2019-02-01T00:00:00-05:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2019-02-01:/2019/using-opencv-in-an-ios-app.html</id><summary type="html">&lt;p&gt;Computer vision is cool tech ... computer vision in an iOS app is even better! In this post, I'll describe how you can integrate and use OpenCV in your Swift-based iOS app. Before we dig in to the process, let's take a look at how the finished integration will work.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Integration diagram" src="../images/2019/ios_opencv.png"&gt;&lt;/p&gt;
&lt;p&gt;As …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Computer vision is cool tech ... computer vision in an iOS app is even better! In this post, I'll describe how you can integrate and use OpenCV in your Swift-based iOS app. Before we dig in to the process, let's take a look at how the finished integration will work.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Integration diagram" src="../images/2019/ios_opencv.png"&gt;&lt;/p&gt;
&lt;p&gt;As shown in the diagram, you will continue to write most of your app in Swift as you normally would do. The OpenCV framework will be included in your app, inside a wrapper that you'll write. A bridging header will connect your Swift code to the Objective-C / C++ code of OpenCV. (You'll also add a prefix/precompile header to optimize build times.)&lt;/p&gt;
&lt;p&gt;Once you've set all that up, you'll be able to make use of OpenCV with Swift code like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;edged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;UIImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenCVWrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;detectEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inRGBImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;your_image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But we're jumping ahead. There are a few steps to go through to get this all setup. Let's get OpenCV integrated and then I'll go through some usage examples.&lt;/p&gt;
&lt;h2&gt;Integration overview&lt;/h2&gt;
&lt;p&gt;At a high-level, you'll need to perform the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add the OpenCV framework to your project&lt;/li&gt;
&lt;li&gt;Create the wrapper&lt;/li&gt;
&lt;li&gt;Create a bridging header&lt;/li&gt;
&lt;li&gt;Create a prefix header&lt;/li&gt;
&lt;li&gt;Implement the OpenCV functionality you need&lt;/li&gt;
&lt;li&gt;Use those methods from Swift&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's dive in.&lt;/p&gt;
&lt;h2&gt;Integration steps&lt;/h2&gt;
&lt;p&gt;First, download and unzip the OpenCV pack:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://opencv.org/releases.html"&gt;https://opencv.org/releases.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Find 3.4.4 (or other suitable version) and click the &lt;strong&gt;iOS pack&lt;/strong&gt; link&lt;/li&gt;
&lt;li&gt;Unzip the file to a convenient temporary location&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next, either create or open your Xcode project. You'll want to arrange Xcode and Finder side-by-side and then drag the &lt;strong&gt;opencv2.framework&lt;/strong&gt; bundle (special folder) into the Xcode project tree, at the top level, to add it to your project. Note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make sure &lt;strong&gt;Copy items if needed&lt;/strong&gt; is checked&lt;/li&gt;
&lt;li&gt;Make sure &lt;strong&gt;Create folder references&lt;/strong&gt; is checked&lt;/li&gt;
&lt;li&gt;Make sure &lt;strong&gt;Add to targets: &lt;em&gt;your_project&lt;/em&gt;&lt;/strong&gt; is checked&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="OpenCV framework file in the Xcode file tree" src="../images/2019/opencv_framework.png"&gt;&lt;/p&gt;
&lt;p&gt;You can close Finder at this point. We'll work in Xcode for the rest of this. Next, we need to create the wrapper around OpenCV. In doing so, we'll also create the bridging header.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Choose File &amp;gt; New &amp;gt; File&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;Cocoa Touch Class&lt;/strong&gt; file, naming it &lt;em&gt;OpenCVWrapper&lt;/em&gt; (or another name if you prefer)&lt;/li&gt;
&lt;li&gt;It should subclass &lt;strong&gt;NSOBject&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;It should be an &lt;strong&gt;Objective-C&lt;/strong&gt; file&lt;/li&gt;
&lt;li&gt;When prompted, click the &lt;strong&gt;Create Bridging Header&lt;/strong&gt; button&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Open the &lt;em&gt;YourApp&lt;/em&gt;-Bridging-Header.h file and add this line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;#import &amp;quot;OpenCVWrapper.h&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's all you'll need to do inside your bridging header. With this simple file in place, you've created a &lt;em&gt;bridge&lt;/em&gt; between Objective-C (where you'll write the actual OpenCV code) and Swift (where you'll use it).&lt;/p&gt;
&lt;p&gt;Next, we need to make a couple of changes to the OpenCVWrapper.m file. First, change the name of the OpenCVWrapper.m file to &lt;strong&gt;OpenCVWrapper.mm&lt;/strong&gt;. Doing so informs Xcode to treat it as a C++ rather than C file. Then, inside that file, make sure the imports at the top are in this order. If you don't, you will get weird compile errors later.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;#import &amp;lt;opencv2/opencv.hpp&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;#import &amp;quot;OpenCVWrapper.h&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, we need to set up the prefix header. Again, there's not much that you'll need to do with this file. It will simply tell Xcode to not recompile the already-compiled OpenCV framework to speed up your builds.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Choose File &amp;gt; New &amp;gt; File&lt;/li&gt;
&lt;li&gt;Scroll to near the bottom and choose &lt;strong&gt;PCH File&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Make sure your app is checked in the Targets list&lt;/li&gt;
&lt;li&gt;Click Create&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Inside the prefix header, before the closing &lt;code&gt;#endif&lt;/code&gt; statement, add:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#ifdef __cplusplus&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;opencv2/opencv.hpp&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That is, you're done! Well, you can build the app now and OpenCV is integrated. Of course you've done nothing to implement OpenCV's functions yet. (By the way, you'll get many OpenCV-related build warnings which you can safely ignore. They mostly relate to inline documentation comments being in the wrong format and won't affect your app's execution in any way.)&lt;/p&gt;
&lt;h2&gt;Implementing an OpenCV function&lt;/h2&gt;
&lt;p&gt;The basic steps you'll follow to implement an OpenCV function and use it in your app are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add the function signature to the OpenCVWrapper.h file&lt;/li&gt;
&lt;li&gt;Add the function's implementation code in the OpenCVWrapper.mm file&lt;/li&gt;
&lt;li&gt;Call that function in your Swift file&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, your .h header declares the function and its signature, which might 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="cp"&gt;#import &amp;lt;Foundation/Foundation.h&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;NS_ASSUME_NONNULL_BEGIN&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpenCVWrapper&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;NSObject&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="n"&gt;NSString&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;openCVVersionString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;NS_ASSUME_NONNULL_END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Your .mm file would have the corresponding function implementation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#import &amp;lt;opencv2/opencv.hpp&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#import &amp;quot;OpenCVWrapper.h&amp;quot;&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpenCVWrapper&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="n"&gt;NSString&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;openCVVersionString&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NSString&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stringWithFormat&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;OpenCV Version %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CV_VERSION&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Per Objective-C syntax, the &lt;code&gt;+&lt;/code&gt; indicates a class method; use &lt;code&gt;-&lt;/code&gt; to indicate an instance method. The difference is probably inconsequential since there will only ever be one instance of your OpenCVWrapper class. So, you can probably use &lt;code&gt;+&lt;/code&gt; to prefix all of your functions as shown above.&lt;/p&gt;
&lt;p&gt;OK, so let's use this powerful new OpenCV capability. In your main ViewController.swift file, update &lt;strong&gt;viewDidLoad&lt;/strong&gt; to call that new function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kc"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;openVersionString&lt;/span&gt;&lt;span class="si"&gt;())&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&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;Build to a simulator and watch the Xcode debug console and you should see &lt;code&gt;OpenCV Version 3.4.4&lt;/code&gt; there. Congratulations, you've integrated OpenCV into your iOS app.&lt;/p&gt;
&lt;h2&gt;A better example&lt;/h2&gt;
&lt;p&gt;I've created a simple project so that you can explore something a little more meaningful. This app lets you take a picture (so you'll need to run it on a device) and then apply some OpenCV manipulations.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="../images/2019/OpenCVTest.zip"&gt;Download the OpenCVTest Xcode project&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Update the project properties with your organization / app-signing profile.&lt;/li&gt;
&lt;li&gt;Download and drag the &lt;strong&gt;opencv2.framework&lt;/strong&gt; bundle into the Xcode project tree (it's too big to include in my zip file). I used OpenCV 3.4.4 and any of the 3.x version should work. But I haven't tested with 4.x, so YMMV.&lt;/li&gt;
&lt;li&gt;Build the app to a device. The app should be fairly self-explantory.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Make sure to explore the &lt;code&gt;.h&lt;/code&gt; and &lt;code&gt;.mm&lt;/code&gt; files, as well as the &lt;code&gt;ProcessImageController.swift&lt;/code&gt; file in this project. Also check out the UIImage extension. Together, these should give you a good start on writing your own OpenCV functionality.&lt;/p&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;You'll need to write your OpenCV code in Objective-C (that's what's in the &lt;code&gt;.mm&lt;/code&gt; file). But as best as I can tell, there is no specific OpenCV-for-iOS documentation. For the most part, any C++ OpenCV sample code you find will work without much modification. From there, I'm sorry to say you'll have to turn to StackOverflow and Google and hope.&lt;/p&gt;
&lt;p&gt;I've found OpenCV's functions to be typically much faster than the equivalent Swift implementations. For example, converting an image to grayscale is considerably faster with OpenCV. But test, because some OpenCV operations are slower, sometimes surprisingly so. Blurring an image, for example, is many times faster in Swift than in OpenCV.&lt;/p&gt;
&lt;p&gt;But, avoid passing images back-and-forth between Swift and OpenCV. You'll need to change data formats (e.g. from OpenCV Mat to UIImage or CIImage) which can be quite slow and you may encounter memory leaks. Instead, do all the processing you can in one layer or the other and pass only the final result between layers.&lt;/p&gt;
&lt;p&gt;Images you capture from an iPhone (and presumably iPad) camera can be appear to be turned upside-down by OpenCV. In fact, it's just a different way of handling the orientation information in the files. Check out the UIImage extension that I've included in the sample project above. As you'll see in that project, you can get a correctly oriented portrait image by simply accessing the &lt;em&gt;normalized&lt;/em&gt; property of the image, like this: &lt;code&gt;OpenCVWrapper.convert(toGrayscale: img.normalized!)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Good luck and have fun building great OpenCV vision-enabled iOS apps!&lt;/p&gt;</content><category term="OpenCV"></category><category term="OpenCV"></category><category term="iOS"></category><category term="swift"></category></entry><entry><title>Publishing to PyPI</title><link href="https://www.timpoulsen.com/2019/publishing-to-pypi.html" rel="alternate"></link><published>2019-01-25T00:00:00-05:00</published><updated>2019-01-25T00:00:00-05:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2019-01-25:/2019/publishing-to-pypi.html</id><summary type="html">&lt;p&gt;I recently published my first &lt;a href="https://pypi.org/project/robovision/"&gt;Python package&lt;/a&gt; to &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt;. The guides I found on how to do so were mostly out-of-date and confusing. Of course, PyPI is reportedly coming out with new updates soon and my instructions here will soon be outdated. In any case, here's my take on how …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently published my first &lt;a href="https://pypi.org/project/robovision/"&gt;Python package&lt;/a&gt; to &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt;. The guides I found on how to do so were mostly out-of-date and confusing. Of course, PyPI is reportedly coming out with new updates soon and my instructions here will soon be outdated. In any case, here's my take on how you can publish your own project.&lt;/p&gt;
&lt;p&gt;(&lt;em&gt;Note, the following guide will work for OS X and Linux. You'll need to make some adjustments for Windows, which I apologize but I'm unable to provide guidance for.&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;I recommend you start right and use &lt;a href="https://github.com/audreyr/cookiecutter"&gt;CookieCutter&lt;/a&gt; to create the shell of your project. There are a couple of Python &lt;a href="https://github.com/audreyr/cookiecutter#python"&gt;package templates&lt;/a&gt; that will give you the shell of what you need. (I used the cookiecutter-pypackage-minimal template, but the "ultimate" template might fit your needs better.)&lt;/p&gt;
&lt;p&gt;Many guides suggest you reserve your package's name on PyPI right away so that no one else "steals" it. However, PyPI no longer supports package name reservations. The best you can do is search ahead of time to be reasonably sure your chosen name is not already taken.&lt;/p&gt;
&lt;h2&gt;Local package installation&lt;/h2&gt;
&lt;p&gt;As you develop, you can test that your package is installable and working by installing it from your local folder. Use &lt;code&gt;pip&lt;/code&gt; to install from your development folder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cd path/to/your/package/folder
pip install -e .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;PyPI registration&lt;/h2&gt;
&lt;p&gt;Once your package is ready, you'll need to create accounts on both &lt;a href="https://pypi.org/account/register/"&gt;pypi.org&lt;/a&gt; and &lt;a href="https://test.pypi.org/account/register/"&gt;test.pypi.org&lt;/a&gt;. PyPI says those systems will be linked eventually. But for now, the test and production environments are completely separate. You can use the same information to register for both sites.&lt;/p&gt;
&lt;h2&gt;The &lt;code&gt;twine&lt;/code&gt; package uploader&lt;/h2&gt;
&lt;p&gt;You'll be using &lt;code&gt;twine&lt;/code&gt; to upload your package to PyPI. Let's get that set up. First, install it with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install twine
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You'll need to create a configuration file in your home directory. Using your favorite code editor, create a file named &lt;code&gt;.pypirc&lt;/code&gt; in your home directory with the following contents:&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;[distutils]&lt;/span&gt;
&lt;span class="na"&gt;index-servers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;testpypi&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;pypi&lt;/span&gt;

&lt;span class="k"&gt;[testpypi]&lt;/span&gt;
&lt;span class="na"&gt;repository&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="s"&gt;https://test.pypi.org/legacy/&lt;/span&gt;
&lt;span class="na"&gt;username&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="s"&gt;your_username&lt;/span&gt;
&lt;span class="na"&gt;password&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="s"&gt;your_password&lt;/span&gt;

&lt;span class="k"&gt;[pypi]&lt;/span&gt;
&lt;span class="na"&gt;username&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="s"&gt;your_username&lt;/span&gt;
&lt;span class="na"&gt;password&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="s"&gt;your_password&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Take note that there's no &lt;code&gt;repository&lt;/code&gt; line for the pypi (production) entry. Previous versions of twine required that line but it will cause an error with current versions.&lt;/p&gt;
&lt;h2&gt;Configure distribution options&lt;/h2&gt;
&lt;p&gt;You'll need to choose between the &lt;a href="https://packaging.python.org/discussions/wheel-vs-egg/"&gt;wheel and egg&lt;/a&gt; packaging formats. In most cases, you'll want to choose wheel. See the linked guide if you think you might need to use the older egg format.&lt;/p&gt;
&lt;p&gt;Next, you're going to need to make sure you have a few files in your package directory. If you used one of the CookieCutter templates I linked to above, you'll have these. But, you'll need to make sure they're updated with correct information.&lt;/p&gt;
&lt;p&gt;Use the ReStructuredText format for your readme or the info on the PyPI site will be all borked up. That means you'll need a &lt;code&gt;README.rst&lt;/code&gt; file in the root directory of your project. Supposedly PyPI supports Markdown but don't believe them. Most code repositories (GitHub, etc.) support ReStructuredText readmes, so you don't need to create multiple files.&lt;/p&gt;
&lt;p&gt;Make sure to include a LICENSE and/or LICENSE.md file to announce your package's license.&lt;/p&gt;
&lt;p&gt;You'll need to update the setup.cfg file to reflect your package publishing options. First, assuming you're using the wheel format, you'll need to set &lt;code&gt;universal=1&lt;/code&gt; for a package that targets &lt;em&gt;both&lt;/em&gt; Python 2.x and Python 3.x or &lt;code&gt;universal=0&lt;/code&gt; if targeting just Python 3.x. Second, make sure the &lt;code&gt;description-file&lt;/code&gt; references your readme file's name. Here's my setup.cfg:&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;[bdist_wheel]&lt;/span&gt;
&lt;span class="na"&gt;universal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;[metadata]&lt;/span&gt;
&lt;span class="na"&gt;description-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;README.rst&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Assuming you used CookieCutter, you should have already configured the basics of the setup.py file. It notes various settings and is used by &lt;code&gt;setuptools&lt;/code&gt; to create your distribution file. Make sure that the version number, readme file name, license, and other options are correct for your package.&lt;/p&gt;
&lt;p&gt;Note that if you have Python files in subdirectories off your main package folder, you'll need to list them in the &lt;code&gt;packages=[]&lt;/code&gt; list. See &lt;a href="https://github.com/skypanther/robovision/blob/master/setup.py"&gt;my setup.py file&lt;/a&gt; and my source code organization if this doesn't make sense to you.&lt;/p&gt;
&lt;h2&gt;Create your distribution file&lt;/h2&gt;
&lt;p&gt;With all that configuration out of the way, you're ready to create your distribution wheel file. Use this command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python setup.py bdist_wheel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will package up your file and create a wheel file in the dist directory in your project. &lt;/p&gt;
&lt;h2&gt;Uploading&lt;/h2&gt;
&lt;p&gt;You should upload to the test environment first, and install your package from the test environment. That way, you're assured everything is working as expected before you announce your package to the world. Use this command:&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;twine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;YOUR_PACKAGE_NAME&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;testpypi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Use a command like this to install from the test environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install -i https://test.pypi.org/simple YOUR_PACKAGE_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once you're sure everything is in order, upload to the production PyPI server:&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;twine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;YOUR_PACKAGE_NAME&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Congratulations! You've published a Python package to PyPI.&lt;/p&gt;
&lt;h2&gt;Updating your package&lt;/h2&gt;
&lt;p&gt;Going forward, as you make improvements and additions to your library, you'll need to do just a couple of things to publish an update.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, update setup.py with the new version number&lt;/li&gt;
&lt;li&gt;Update your readme to note any new features, and probably new version number&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;python setup.py bdist_wheel&lt;/code&gt; to create a new distribution file&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;twine upload dist/YOUR_PACKAGE_NAME.whl&lt;/code&gt; to upload it&lt;/li&gt;
&lt;/ul&gt;</content><category term="Python"></category><category term="python"></category></entry><entry><title>Raspberry Pi for IoT</title><link href="https://www.timpoulsen.com/2018/raspberry-pi-for-iot.html" rel="alternate"></link><published>2018-12-14T00:00:00-05:00</published><updated>2018-12-14T00:00:00-05:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-12-14:/2018/raspberry-pi-for-iot.html</id><summary type="html">&lt;p&gt;I gave a presentation to the Buffalo Python meetup group in December. My talk centered around using the Raspberry Pi as a platform for IoT and embedded development. I want to share my presentation and the resources I mentioned here on my blog.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; &lt;em&gt;I presented an expanded version of …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I gave a presentation to the Buffalo Python meetup group in December. My talk centered around using the Raspberry Pi as a platform for IoT and embedded development. I want to share my presentation and the resources I mentioned here on my blog.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; &lt;em&gt;I presented an expanded version of this talk to the RocDev group in January. My updated slides are here as a &lt;a href="../images/2019/2019RocDev_Pi_IoT.pdf"&gt;PDF&lt;/a&gt; file. The links below still apply.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Slide preview" src="../images/2018/2018DecBufPy.jpg"&gt;&lt;/p&gt;
&lt;p&gt;View/download as a &lt;a href="../images/2018/2018DecBufPy.pdf"&gt;PDF&lt;/a&gt;, &lt;a href="../images/2018/2018DecBufPy.pptx"&gt;PowerPoint&lt;/a&gt;, or &lt;a href="../images/2018/2018DecBufPy.key"&gt;Keynote&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;During my talk, I referred to a bunch of sites and resources. These included:&lt;/p&gt;
&lt;h2&gt;Getting started with the Pi&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://www.raspberrypi.org/"&gt;Raspberry Pi home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.raspberrypi.org/downloads/"&gt;OS downloads&lt;/a&gt;, including Raspbian and more&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.balena.io/etcher/"&gt;Etcher&lt;/a&gt;, for copying OS images to an SD card&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://store.rpipress.cc/products/the-official-raspberry-pi-beginner-s-guide"&gt;Official Raspberry Pi Beginner's Guide&lt;/a&gt; looks to be a good starter book for younger RPi tinkerers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Development tools and docs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.headmelted.com/"&gt;Visual Studio Code&lt;/a&gt; for the Raspberry Pi&lt;/li&gt;
&lt;li&gt;&lt;a href="www.piwheels.org"&gt;Pi Wheels&lt;/a&gt; precompiled python libraries&lt;/li&gt;
&lt;li&gt;&lt;a href="https://picamera.readthedocs.io"&gt;PiCamera&lt;/a&gt; docs&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Blogs and tutorial sites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.adafruit.com/"&gt;Adafruit&lt;/a&gt; offers many great tutorials on the Raspberry Pi, Arduino, and other electronics topics. Their &lt;a href="https://github.com/adafruit"&gt;GitHub account&lt;/a&gt; has tons of free code, too.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.sparkfun.com/"&gt;SparkFun&lt;/a&gt; has great electronics tutorials, more towards the microcontroller (Arduino) type level than the Pi.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pololu.com/blog"&gt;Pololu&lt;/a&gt;'s blog has some info, but unlike some other vendors, their individual product listings often come with sample code and how-to information.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://www.pyimagesearch.com/"&gt;PyImageSearch blog&lt;/a&gt; is a great resource for OpenCV and computer vision. Adrian, the author, often includes Raspberry Pi specifics for his posts, though most can also be implemented on other platforms. I specifically mentioned his &lt;a href="www.pyimagesearch.com/2015/05/25/basic-motion-detection-and-tracking-with-python-and-opencv/"&gt;motion detection / tracking post&lt;/a&gt; (and its &lt;a href="https://www.pyimagesearch.com/2015/06/01/home-surveillance-and-motion-detection-with-the-raspberry-pi-python-and-opencv/"&gt;second part&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;And while we're on the topic of OpenCV, Satya Mallick's &lt;a href="https://www.learnopencv.com/"&gt;LearnOpenCV.com&lt;/a&gt; blog has tons of great computer vision information and he generally posts both Python and C++ code for every example.&lt;/li&gt;
&lt;li&gt;And humbly, my blog right here&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Stores / resellers&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.adafruit.com/"&gt;Adafruit&lt;/a&gt; - Pi, Arduino, and other electronic components&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sparkfun.com/"&gt;SparkFun&lt;/a&gt; - Generally a bit more Arduino/microcontroller oriented components&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pololu.com/"&gt;Pololu&lt;/a&gt; - Robotics, electronics, and other components.&lt;/li&gt;
&lt;li&gt;I usually purchase Raspberry Pis from Amazon. Just be sure you're getting the model you want (the 3B+ is the current model) since many vendors are still selling the older boards.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Unlike earlier models, the 3B+ has somewhat stringent power demands. You'll need a 5v power brick that has a 2.4 amps or greater output rating. That old cell phone charger you've got probably won't cut it.&lt;/p&gt;
&lt;h2&gt;My projects&lt;/h2&gt;
&lt;p&gt;Disclosure, the code here sucks and is nothing I'd show as part of a job interview. But, it gets the job done.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My &lt;a href="https://github.com/skypanther/PiLit"&gt;PiLit Christmas light controller&lt;/a&gt; project&lt;/li&gt;
&lt;li&gt;And my someday-I'll-actually-finish-it &lt;a href="https://github.com/skypanther/catfeeder"&gt;automated cat feeder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Making, Python"></category><category term="raspberry pi"></category><category term="making"></category><category term="python"></category></entry><entry><title>Getting user input with OpenCV trackbars</title><link href="https://www.timpoulsen.com/2018/getting-user-input-with-opencv-trackbars.html" rel="alternate"></link><published>2018-10-16T00:00:00-04:00</published><updated>2018-10-16T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-10-16:/2018/getting-user-input-with-opencv-trackbars.html</id><summary type="html">&lt;p&gt;Trackbars, or sliders, are a simple way to get user input when working with OpenCV. In this post, I’ll show you how to add trackbars to an OpenCV window, read their values, and use those values in your Python scripts.&lt;/p&gt;
&lt;img alt="Trackbars (sliders) on an OpenCV window" src="../images/2018/trackbars.png" style="width: 320px;" /&gt;
&lt;p&gt;To demonstrate a use for trackbars, we’ll read …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Trackbars, or sliders, are a simple way to get user input when working with OpenCV. In this post, I’ll show you how to add trackbars to an OpenCV window, read their values, and use those values in your Python scripts.&lt;/p&gt;
&lt;img alt="Trackbars (sliders) on an OpenCV window" src="../images/2018/trackbars.png" style="width: 320px;" /&gt;
&lt;p&gt;To demonstrate a use for trackbars, we’ll read user input from a pair of trackbars and use that input to tune edge detection parameters in real time. I’ll show you both the common way of reading trackbar values, and a more performant way to structure your code.&lt;/p&gt;
&lt;div class="section" id="adding-trackbars"&gt;
&lt;h2&gt;Adding trackbars&lt;/h2&gt;
&lt;p&gt;In order to use trackbars, you have to use a named OpenCV window. We use that name to specify which window to attach the trackbar to.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# first, create your named window&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;namedWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Window_Name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# next, create the trackbar&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;createTrackbar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Trackbar_label&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Window_Name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# finally, show something in the window, the trackbar will be attached&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Window_Name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s take a quick look at the parameters we pass to &lt;tt class="docutils literal"&gt;createTrackbar&lt;/tt&gt;. The first param is a label that will be shown next to the trackbar. Next is the window name as discussed above. The minimum trackbar value is always 0, thus the third param is setting the starting value. Next comes the max value to which the trackbar can be set. Finally, we pass a reference to a function that will be called each time the trackbar’s value changes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="edge-detection"&gt;
&lt;h2&gt;Edge detection&lt;/h2&gt;
&lt;p&gt;Edge detection is the process of identifying edges in an image. Edge detection is useful for feature detection and feature extraction, as well as other applications. OpenCV supports a few edge detection algorithms; we’ll be using the &lt;a href="https://en.wikipedia.org/wiki/Canny_edge_detector" target="_blank"&gt;Canny edge detection method&lt;/a&gt;. In essence, this algorithm looks for a quick change from light to dark pixels (or vice versa) extending across some minimum area in an image to be considered an edge.&lt;/p&gt;
&lt;p&gt;A picture is worth a thousand words...&lt;/p&gt;
&lt;img alt="Edges detected with Canny" src="../images/2018/edges.png" style="width: 320px;" /&gt;
&lt;p&gt;Compare this image to the one at the top of the post. You can see that the edges (white lines) have been drawn in the areas where the itensity changed from light to dark, or dark to light, mostly marking the edges of the lamp.&lt;/p&gt;
&lt;p&gt;Canny edge detection is a multi-step process. The image is blurred slightly, a &lt;a href="https://en.wikipedia.org/wiki/Sobel_operator" target="_blank"&gt;Sobel filter&lt;/a&gt; is applied to find and emphasize strong gradients in the image. Then finally, a process of finding edges and filtering out false edges is performed. The Canny algorithm accepts three parameters to tune its operations. These are a minimum and maximum threshold and the aperture size.&lt;/p&gt;
&lt;p&gt;The aperture size is used in the Sobel filter, and in my experience seems to have the least effect on Canny results. The min and max threshold values, on the other hand, strongly affect the outcome. Tuning them just right will result in edges being detected; getting them wrong will result in unimportant gradient areas being identified as “edges.”&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="tuning-with-trackbars"&gt;
&lt;h2&gt;Tuning with trackbars&lt;/h2&gt;
&lt;p&gt;With that explanation out of the way, let's get down to code. We’ll use a pair of trackbars to let the user set the values of the min and max thresholds in the Canny edge detection algorithm. You can grab the image I used from &lt;a href="https://unsplash.com/photos/wsvCC6UyKjs" target="_blank"&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's start with the shell of our script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&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;imutils&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
   &lt;span class="n"&gt;min_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
   &lt;span class="n"&gt;max_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
   &lt;span class="n"&gt;aperture_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

   &lt;span class="n"&gt;ap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-i&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--image&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Image to use for edge detection&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
   &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;image&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="n"&gt;image&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="n"&gt;file_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imutils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;)&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;image&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="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;In the script above, we import our required libraries and begin to build out our &lt;cite&gt;main()&lt;/cite&gt; function. We'll be accepting the name of the image as a command line argument. So, we instantiate the &lt;cite&gt;ArgumentParser&lt;/cite&gt; and specify our one argument.&lt;/p&gt;
&lt;p&gt;I'm resizing the image in my demo here, but keep in mind that doing so will reduce the effectiveness of edge detection. In a production app, you will probably want to work with full-size images. Finally, edge detection works best on a grayscale image, so I convert it with &lt;tt class="docutils literal"&gt;cvtColor&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Next, let's add the code to create our named window and trackbars. We'll attach these to the window showing the original version of the image.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&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;/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;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;namedWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Original&amp;#39;&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;createTrackbar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Min&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Original&amp;#39;&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;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;no_op&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;createTrackbar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Max&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Original&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;no_op&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Original&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;We're not going to use the callback function, which is called when the user moves the trackbar. For that parameter, we'll pass a &lt;tt class="docutils literal"&gt;no_op&lt;/tt&gt; function, which we'll add in a minute. First, let's add a loop where we will check for user input.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&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;/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;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="n"&gt;min_val&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;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getTrackbarPos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Min&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Original&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       &lt;span class="n"&gt;max_val&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;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getTrackbarPos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Max&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Original&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Min: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_val&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Max: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_val&lt;/span&gt;&lt;span class="p"&gt;))&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;min_val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aperture_size&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Edges&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imutils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resize&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="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       &lt;span class="k"&gt;if&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;waitKey&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="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;q&amp;quot;&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;destroyAllWindows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
           &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;There are a few things going on in that loop! First, we use &lt;tt class="docutils literal"&gt;getTrackbarPos&lt;/tt&gt; to get the trackbar position. This function takes two parameters: the label of the trackbar we're reading from and the name of the window it's attached to. For our example script, we need the value as an integer, so we convert it with &lt;tt class="docutils literal"&gt;int()&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Here's also where we perform the edge detection. We call the &lt;tt class="docutils literal"&gt;cv2.Canny&lt;/tt&gt; method passing in the image, min and max values, and the aperture size. This returns the edge-detected version which we show in a new cv2 window.&lt;/p&gt;
&lt;p&gt;Let's not forget our &lt;tt class="docutils literal"&gt;no_op&lt;/tt&gt; function to finish this off:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&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;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;no_op&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_val&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;You can grab the entire script from this &lt;a href="https://gist.github.com/skypanther/f4d4e7c4407fd9188c47b13f805a2380" target="_blank"&gt;Gist&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-better-trackbar"&gt;
&lt;h2&gt;A better trackbar&lt;/h2&gt;
&lt;p&gt;So, that script works. But, when you run it, you'll notice a couple of problems. The print statements spam your console. That loop is running continuously so that it can read user input. Even with that, the trackbars are somewhat unresponsive. You can't slide them too quickly and the script is slow to reflect your changes.&lt;/p&gt;
&lt;p&gt;We can fix those problems by using the callback functions we ignored earlier. The downside to this approach is that you can't control what arguments are passed to the callback; just the new trackbar value is passed. So, we'll need to be a little creative. Here's the full script with explanation following it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&lt;/span&gt;
&lt;span class="normal"&gt;52&lt;/span&gt;
&lt;span class="normal"&gt;53&lt;/span&gt;
&lt;span class="normal"&gt;54&lt;/span&gt;
&lt;span class="normal"&gt;55&lt;/span&gt;
&lt;span class="normal"&gt;56&lt;/span&gt;
&lt;span class="normal"&gt;57&lt;/span&gt;
&lt;span class="normal"&gt;58&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;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&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;imutils&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;edge_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;min_val&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;max_val&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;aperture_size&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;edge_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gray&lt;/span&gt;
    &lt;span class="n"&gt;ap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-i&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--image&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Image to use for edge detection&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;image&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Cannot open image, quitting...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;image&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="n"&gt;file_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imutils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;)&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;image&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="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;namedWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Original&amp;#39;&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;createTrackbar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Min&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Original&amp;#39;&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;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_change&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;createTrackbar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Max&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Original&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_change&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Original&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redraw_edges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&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;waitKey&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="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;q&amp;quot;&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;destroyAllWindows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;min_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_val&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;change_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;min_val&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;max_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_val&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;change_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;max_val&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;edge_params&lt;/span&gt;
    &lt;span class="n"&gt;edge_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&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;value&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redraw_edges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;redraw_edges&lt;/span&gt;&lt;span class="p"&gt;():&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;edge_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;min_val&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                      &lt;span class="n"&gt;edge_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;max_val&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                      &lt;span class="n"&gt;edge_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;aperture_size&amp;#39;&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Edges&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imutils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resize&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="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;I've added a couple of global values to store references to our grayscale image and our edge detection parameters. The &lt;tt class="docutils literal"&gt;createTrackbar&lt;/tt&gt; calls now point to meaningful callback functions. But, since both are doing about the same thing, just on different data, each of those functions simply call the &lt;tt class="docutils literal"&gt;change_params&lt;/tt&gt; function with an identifier of the data to change. I've moved the edge detection code to its own function too.&lt;/p&gt;
&lt;p&gt;This script is also available at the same &lt;a href="https://gist.github.com/skypanther/f4d4e7c4407fd9188c47b13f805a2380" target="_blank"&gt;Gist&lt;/a&gt; linked to above. When you run it, you'll notice there's no console output until you move one of the trackbars. The trackbars should be a bit more responsive too. Performance will never be great. You're not writing a native compiled GUI app. But, it's certainly usable to get quick user input.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;So, there you have it, trackbars (sliders) on an OpenCV window. Depending on your operating system, they'll be attached to either the top or bottom of the window (if there's a way to control that, I haven't found it). I don't think there's a limit to how many you can attach, other than the size of your screen. I find them very useful for operations like the edge detection I demoed with here; operations where I need to tune some parameters and don't want to keep updating code and re-running a script.&lt;/p&gt;
&lt;/div&gt;
</content><category term="OpenCV"></category><category term="opencv"></category><category term="python"></category></entry><entry><title>Multiple cameras with a single Raspberry Pi</title><link href="https://www.timpoulsen.com/2018/multiple-cameras-with-a-single-raspberry-pi.html" rel="alternate"></link><published>2018-09-02T00:00:00-04:00</published><updated>2018-09-02T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-09-02:/2018/multiple-cameras-with-a-single-raspberry-pi.html</id><summary type="html">&lt;p&gt;The Arducam Multi Camera Adapter board is a neat accessory for a Raspberry Pi. With it, you can connect multiple cameras to a single Pi. A single board supports up to four cameras. According to Arducam, you can stack up to four boards for a total of 16 cameras on …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The Arducam Multi Camera Adapter board is a neat accessory for a Raspberry Pi. With it, you can connect multiple cameras to a single Pi. A single board supports up to four cameras. According to Arducam, you can stack up to four boards for a total of 16 cameras on a single Pi.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Arducam Multicamera Adapter" src="../images/2018/arducam.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I had a chance to use a couple of these boards in a recent project. Our goal was to capture multiple images to form an image mosaic. My setup included a Raspberry Pi 3B+, two of the Arducam multicamera boards, and six cameras. (Only the 5MP v1 Pi cameras, or Arducam's 5MP camera are supported, not the 8MP v2 camera.)&lt;/p&gt;
&lt;p&gt;Since Arducam's documentation is sparse, I thought I'd share my experiences with this adapter as well as a Python library I wrote that simplifies accessing each of the cameras.&lt;/p&gt;
&lt;p&gt;First, you should know that you cannot capture the images simultaneously. The adapter works by enabling one camera at a time. So, you can take photos one after the other, but there will be a small delay between each. Arducam does not specify how long it takes to switch between cameras. Our code pauses for 0.1 second between photos though we could probably wait less time than that.&lt;/p&gt;
&lt;h2&gt;Hardware setup&lt;/h2&gt;
&lt;p&gt;Let's start with the hardware side. The adapter board will use 26 of the GPIO pins of the Pi, leaving you the last 14 pins free for other purposes. You will connect the Pi's camera port (called the CSI port) to the adapter board with a ribbon cable. Then, you'll connect the CSI cable from each of your cameras to the ports on the adapter. &lt;/p&gt;
&lt;p&gt;Key hardware setup points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connect the Arducam board starting with GPIO pin 1&lt;/li&gt;
&lt;li&gt;Connect the Pi's CSI port to the Arducam board, pin side of the ribbon towards the board (blue/tape side up)&lt;/li&gt;
&lt;li&gt;Connect camera CSI cables to the Arducam board, pin side of the ribbon towards the board (blue/tape side up)&lt;/li&gt;
&lt;li&gt;It does not matter whether you connect the cameras in order (to port A, then B, etc.) though it will matter for your code&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Stacking multiple boards&lt;/h3&gt;
&lt;p&gt;How about stacking multiple boards? You'll need to solder on the 10-pin connector that Arducam supplies to each of the adapters. These are the high-speed MSI bus over which the camera data is transferred between boards. Because of this connector, the bottom adapter didn't sit straight on my Pi3B+ since the connector hits the HDMI connector. &lt;/p&gt;
&lt;p&gt;You'll need to set DIP switches on the boards to identify which of the OE (output enable) pins that board will use. For board 1, switches 1 and 5 must be on and the rest off. For board 2, switches 2 and 6 are on. See the &lt;a href="http://www.arducam.com/multi-camera-adapter-module-raspberry-pi/" target="_blank"&gt;Arducam site&lt;/a&gt; for the full list. &lt;/p&gt;
&lt;p&gt;&lt;img alt="DIP switches" src="../images/2018/arducam_dip_switches.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Pictures on the Amazon listing for the adapter shows the CSI cable going between the bottom board to the Pi. In their emails to me, Arducam suggested connecting the ribbon cable to the top-most board. (It made no difference for me where it was connected.)&lt;/p&gt;
&lt;h2&gt;Software&lt;/h2&gt;
&lt;p&gt;With that hardware setup out of the way, let's move onto the software side of things. &lt;/p&gt;
&lt;h3&gt;Channels and ports&lt;/h3&gt;
&lt;p&gt;The adapter enables multiple camera support by providing two channels, each with two camera ports. You select which camera to use by enabling one of the channels and one of the camera ports. You do so by setting certain GPIO pins high or low.&lt;/p&gt;
&lt;p&gt;Arducam calls these the channel select (CS) and output enable (OE) pins. All of the stacked boards share the same CS pin but have their own OE pins (hence the DIP switches you must set). There's a listing of which pins you must set on the &lt;a href="http://www.arducam.com/multi-camera-adapter-module-raspberry-pi/" target="_blank"&gt;Arducam site&lt;/a&gt;, though my library hides that complexity.&lt;/p&gt;
&lt;h3&gt;Caveat&lt;/h3&gt;
&lt;p&gt;I must point out that I couldn't get stacked adapters to work. Even after multiple emails back and forth with Arducam, connecting a second adapter would cause my Pi to crash as soon as I tried accessing any camera. So, while my library supports up to 16 cameras, I can only be sure it works with four.&lt;/p&gt;
&lt;h3&gt;The multicam library&lt;/h3&gt;
&lt;p&gt;I have not published the library to PyPI (and probably won't). Instead, I've put it on &lt;a href="https://gist.github.com/skypanther/04b44eaa455f358eb70e59336c068312"&gt;a GitHub Gist&lt;/a&gt;. Using it is pretty simple, though it has a couple of dependencies.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pythonhosted.org/RPIO/"&gt;RPi.GPIO&lt;/a&gt; - installed by default in Raspbian Stretch (full version)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://picamera.readthedocs.io/en/release-1.13/index.html"&gt;PiCamera&lt;/a&gt; - install with &lt;code&gt;pip install picamera&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because my project used OpenCV, by default, my library returns captured photos as an OpenCV "BGR" formatted blob. You can pass the &lt;code&gt;image_format&lt;/code&gt; param to the capture method if you want to use another format. Any format &lt;a href="https://picamera.readthedocs.io/en/release-1.13/api_camera.html#picamera.PiCamera.capture"&gt;supported by PiCamera's &lt;code&gt;capture&lt;/code&gt; method&lt;/a&gt; will work.&lt;/p&gt;
&lt;p&gt;OpenCV is not installed by default, check out &lt;a href="https://www.pyimagesearch.com/2017/09/04/raspbian-stretch-install-opencv-3-python-on-your-raspberry-pi/"&gt;PyImageSearch's tutorial&lt;/a&gt; for install instructions if you want to use OpenCV on your Pi.&lt;/p&gt;
&lt;h3&gt;Examples&lt;/h3&gt;
&lt;p&gt;Using OpenCV, a minimal example follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cv2&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;multicam&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Multicam&lt;/span&gt;

&lt;span class="n"&gt;mcam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Multicam&lt;/span&gt;&lt;span class="p"&gt;()&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;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cam&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Cam A&amp;#39;&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;waitKey&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;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleanup&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;Or, with Matplotlib, you just need to specify the &lt;code&gt;rgb&lt;/code&gt; image format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;matplotlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;multicam&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Multicam&lt;/span&gt;

&lt;span class="n"&gt;mcam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Multicam&lt;/span&gt;&lt;span class="p"&gt;()&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;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cam&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rgb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_window_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Cam A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imshow&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;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleanup&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;Of course, you could do the above with a single camera. Where this library shines is of course accessing the other cameras:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cv2&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;multicam&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Multicam&lt;/span&gt;

&lt;span class="n"&gt;mcam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Multicam&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Cam A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cam&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Cam B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cam&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;b&amp;#39;&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Cam C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cam&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;c&amp;#39;&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Cam D&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cam&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;d&amp;#39;&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;waitKey&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;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleanup&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;There's no need to worry about setting GPIO pins, selecting channels, etc. The library does all that for you. If you have multiple adapters stacked, you can keep going (up to camera 16, or "p")&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cv2&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;multicam&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Multicam&lt;/span&gt;

&lt;span class="n"&gt;mcam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Multicam&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Camera 5  &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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Cam E&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cam&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;e&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# Camera 16&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Cam P&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cam&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;p&amp;#39;&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;waitKey&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;mcam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleanup&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;h3&gt;Notes and miscellaneous&lt;/h3&gt;
&lt;p&gt;Each of the above examples ends with a call to &lt;code&gt;cleanup&lt;/code&gt;. This releases the GPIO ports. You should always call this function at the end of your script to avoid errors if you re-run the script (without restarting the Pi).&lt;/p&gt;
&lt;p&gt;By default, my library uses "board" port assignments. This means GPIO ports are accessed according to their physical pin number. This use matches the examples on the Arducam site.&lt;/p&gt;
&lt;p&gt;However, some libraries, like &lt;a href="https://gpiozero.readthedocs.io/en/stable/index.html"&gt;gpiozero&lt;/a&gt;, are limited to using the Broadcom numbering scheme. If you're using such a library, you can tell the multicam library to use "bcm" numbering during initialization:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;multicam&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Multicam&lt;/span&gt;

&lt;span class="n"&gt;mcam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Multicam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gpio_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bcm&amp;#39;&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;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If you buy one of these boards, grab a copy of &lt;a href="https://gist.github.com/skypanther/04b44eaa455f358eb70e59336c068312"&gt;my multicam library&lt;/a&gt; to ease working with the cameras. Please comment on that Gist if you find issues. It's MIT licensed so free to use in any of your projects.&lt;/p&gt;</content><category term="Python"></category><category term="python"></category><category term="raspberry pi"></category><category term="making"></category></entry><entry><title>Finding the dominant colors of an image</title><link href="https://www.timpoulsen.com/2018/finding-the-dominant-colors-of-an-image.html" rel="alternate"></link><published>2018-07-24T00:00:00-04:00</published><updated>2018-07-24T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-07-24:/2018/finding-the-dominant-colors-of-an-image.html</id><summary type="html">&lt;p&gt;The typical way to isolate or find an object in an image is to look for its color. You specify a range of colors, then use OpenCV to identify regions in an image that contain colors within that range. But, even if you know the exact color of your target …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The typical way to isolate or find an object in an image is to look for its color. You specify a range of colors, then use OpenCV to identify regions in an image that contain colors within that range. But, even if you know the exact color of your target, lighting, shadows, and your camera's sensor will alter the detected color. So, how do you best determine the color range to use?&lt;/p&gt;
&lt;p&gt;In this article, I'll explore some different methods to work with colors. In a future article, we'll use this knowledge, plus &lt;a href="https://www.timpoulsen.com/2018/handling-mouse-events-in-opencv.html"&gt;selecting a region of interest&lt;/a&gt; within an image/video to determine the exact range of colors needed to isolate an object.&lt;/p&gt;
&lt;p&gt;Let's say your goal is to isolate the puppy in this image. You'll notice that his fur color varies between an off-white to a golden tan. The foreground is brighter, making his hindquarters darker and less richly colored. The toy he has is only a bit darker red-brown.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2018/puppy.jpg" width="480" title="Puppy"/&gt;&lt;/p&gt;
&lt;h2&gt;An average solution&lt;/h2&gt;
&lt;p&gt;Your first attempt might be to take an average of the colors to find the midpoint of his range of colors. Then, to make the range you might choose colors a bit darker and lighter than that average. Of course, you wouldn't want to include the grass in the average. Assuming a cropped version of just the puppy, you could use this script to calculate the average color:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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;#&lt;/span&gt;
&lt;span class="c1"&gt;#  Find the average color in an image&lt;/span&gt;
&lt;span class="c1"&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="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="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;&amp;#39;puppy_cropped.jpg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&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;shape&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="c1"&gt;# calculate the average color of each row of our image&lt;/span&gt;
&lt;span class="n"&gt;avg_color_per_row&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;average&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;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# calculate the averages of our rows&lt;/span&gt;
&lt;span class="n"&gt;avg_colors&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;average&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg_color_per_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# avg_color is a tuple in BGR order of the average colors&lt;/span&gt;
&lt;span class="c1"&gt;# but as float values&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;avg_colors: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;avg_colors&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# so, convert that array to integers&lt;/span&gt;
&lt;span class="n"&gt;int_averages&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;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg_colors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&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;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;int_averages: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;int_averages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# create a new image of the same height/width as the original&lt;/span&gt;
&lt;span class="n"&gt;average_image&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;zeros&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&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;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# and fill its pixels with our average color&lt;/span&gt;
&lt;span class="n"&gt;average_image&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;int_averages&lt;/span&gt;

&lt;span class="c1"&gt;# finally, show it side-by-side with the original&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Avg Color&amp;quot;&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;hstack&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;average_image&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;waitKey&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Which would give you this:&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2018/average_color.png" width="480" title="Average color of the puppy"/&gt;&lt;/p&gt;
&lt;p&gt;Visually, that resulting average tan looks like it would represent the puppy's colors. In practice though, you probably won't find any pixels in the puppy that match that exact shade. Even if you looked at a range of shades lighter to darker than that average, only a few of his pixels would be included. It's even likely you'd get a few pixels of his toy. As bad as the average works with the puppy, the average would totally fail on a multi-colored object, for example the FIRST&lt;sup&gt;&amp;reg;&lt;/sup&gt; logo.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2018/first_logo_average.png" width="480" title="Average color of the NASA log"/&gt;&lt;/p&gt;
&lt;p class="imgcaption"&gt;FIRST&amp;reg;, the FIRST&amp;reg; logo, FIRST&amp;reg; Robotics Competition (formerly also known as FRC&amp;reg;), FIRST&amp;reg; Tech Challenge (formerly also known as FTC&amp;reg;) are trademarks of For Inspiration and Recognition of Science and Technology (FIRST&amp;reg;).&lt;/p&gt;

&lt;h2&gt;K-means clustering&lt;/h2&gt;
&lt;p&gt;Let's try a more powerful method. We'll find the most common colors in our image using &lt;em&gt;k-means clustering&lt;/em&gt;. A &lt;a href="https://en.wikipedia.org/wiki/K-means_clustering" target="_blank"&gt;formal definition&lt;/a&gt; would go something like "&lt;em&gt;k-means clustering partitions n observations into k clusters&lt;/em&gt;". In our case, we have a bunch of pixels (our &lt;code&gt;n&lt;/code&gt;) and we want to pull out some number (the &lt;code&gt;k&lt;/code&gt;) of colors.&lt;/p&gt;
&lt;p&gt;With k-means, you have to specify the number of clusters up-front. There are other techniques that don't have this requirement. In any case, it's not a significant limitation for our needs here.&lt;/p&gt;
&lt;p&gt;We'll use the scikit-learn library's &lt;a href="http://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html" target="_blank"&gt;&lt;code&gt;sklearn.cluster.kmeans&lt;/code&gt;&lt;/a&gt; function to do the heavy lifting. (OpenCV itself offers a &lt;code&gt;kmeans()&lt;/code&gt; function, but scikit's is a bit more flexible for our needs). The following script is based on a post on &lt;a href="https://www.pyimagesearch.com/2014/05/26/opencv-python-k-means-color-clustering/" target="_blank"&gt;the PyImageSearch blog&lt;/a&gt; (a great resource!)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&lt;/span&gt;
&lt;span class="normal"&gt;52&lt;/span&gt;
&lt;span class="normal"&gt;53&lt;/span&gt;
&lt;span class="normal"&gt;54&lt;/span&gt;
&lt;span class="normal"&gt;55&lt;/span&gt;
&lt;span class="normal"&gt;56&lt;/span&gt;
&lt;span class="normal"&gt;57&lt;/span&gt;
&lt;span class="normal"&gt;58&lt;/span&gt;
&lt;span class="normal"&gt;59&lt;/span&gt;
&lt;span class="normal"&gt;60&lt;/span&gt;
&lt;span class="normal"&gt;61&lt;/span&gt;
&lt;span class="normal"&gt;62&lt;/span&gt;
&lt;span class="normal"&gt;63&lt;/span&gt;
&lt;span class="normal"&gt;64&lt;/span&gt;
&lt;span class="normal"&gt;65&lt;/span&gt;
&lt;span class="normal"&gt;66&lt;/span&gt;
&lt;span class="normal"&gt;67&lt;/span&gt;
&lt;span class="normal"&gt;68&lt;/span&gt;
&lt;span class="normal"&gt;69&lt;/span&gt;
&lt;span class="normal"&gt;70&lt;/span&gt;
&lt;span class="normal"&gt;71&lt;/span&gt;
&lt;span class="normal"&gt;72&lt;/span&gt;
&lt;span class="normal"&gt;73&lt;/span&gt;
&lt;span class="normal"&gt;74&lt;/span&gt;
&lt;span class="normal"&gt;75&lt;/span&gt;
&lt;span class="normal"&gt;76&lt;/span&gt;
&lt;span class="normal"&gt;77&lt;/span&gt;
&lt;span class="normal"&gt;78&lt;/span&gt;
&lt;span class="normal"&gt;79&lt;/span&gt;
&lt;span class="normal"&gt;80&lt;/span&gt;
&lt;span class="normal"&gt;81&lt;/span&gt;
&lt;span class="normal"&gt;82&lt;/span&gt;
&lt;span class="normal"&gt;83&lt;/span&gt;
&lt;span class="normal"&gt;84&lt;/span&gt;
&lt;span class="normal"&gt;85&lt;/span&gt;
&lt;span class="normal"&gt;86&lt;/span&gt;
&lt;span class="normal"&gt;87&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;#&lt;/span&gt;
&lt;span class="c1"&gt;#  Use k-means clustering to find the most-common colors in an image&lt;/span&gt;
&lt;span class="c1"&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="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="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.cluster&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KMeans&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_histogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Count the number of pixels in each cluster&lt;/span&gt;
&lt;span class="sd"&gt;    :param: KMeans cluster&lt;/span&gt;
&lt;span class="sd"&gt;    :return: numpy histogram&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;numLabels&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;arange&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="nb"&gt;len&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;unique&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labels_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&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;hist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&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;histogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labels_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;numLabels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;float32&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hist&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hist&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Create an image of a given color&lt;/span&gt;
&lt;span class="sd"&gt;    :param: height of the image&lt;/span&gt;
&lt;span class="sd"&gt;    :param: width of the image&lt;/span&gt;
&lt;span class="sd"&gt;    :param: BGR pixel values of the color&lt;/span&gt;
&lt;span class="sd"&gt;    :return: tuple of bar, rgb values, and hsv values&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;bar&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;zeros&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&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;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;
    &lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blue&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;color&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&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;color&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;color&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;hsv_bar&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;bar&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_BGR2HSV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hsv_bar&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sort_hsvs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hsv_list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Sort the list of HSV values&lt;/span&gt;
&lt;span class="sd"&gt;    :param hsv_list: List of HSV tuples&lt;/span&gt;
&lt;span class="sd"&gt;    :return: List of indexes, sorted by hue, then saturation, then value&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;bars_with_indexes&lt;/span&gt; &lt;span class="o"&gt;=&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;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hsv_val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hsv_list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;bars_with_indexes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hsv_val&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;hsv_val&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;hsv_val&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="n"&gt;bars_with_indexes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elem&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;elem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&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="n"&gt;item&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bars_with_indexes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="c1"&gt;# START HERE&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;&amp;#39;puppy_cropped.jpg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&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;shape&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="c1"&gt;# reshape the image to be a simple list of RGB pixels&lt;/span&gt;
&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&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;reshape&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# we&amp;#39;ll pick the 5 most common colors&lt;/span&gt;
&lt;span class="n"&gt;num_clusters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="n"&gt;clusters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KMeans&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_clusters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;num_clusters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# count the dominant colors and put them in &amp;quot;buckets&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;histogram&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_histogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# then sort them, most-common first&lt;/span&gt;
&lt;span class="n"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;histogram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cluster_centers_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&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;x&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;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# finally, we&amp;#39;ll output a graphic showing the colors in order&lt;/span&gt;
&lt;span class="n"&gt;bars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;hsv_values&lt;/span&gt; &lt;span class="o"&gt;=&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;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hsv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Bar &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;index&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;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;  RGB values: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rgb&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;  HSV values: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hsv&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hsv_values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hsv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# sort the bars[] list so that we can show the colored boxes sorted&lt;/span&gt;
&lt;span class="c1"&gt;# by their HSV values -- sort by hue, then saturation&lt;/span&gt;
&lt;span class="n"&gt;sorted_bar_indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sort_hsvs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hsv_values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sorted_bars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&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;idx&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sorted_bar_indexes&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Sorted by HSV values&amp;#39;&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;hstack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sorted_bars&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;num_clusters&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; Most Common Colors&amp;#39;&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;hstack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bars&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;waitKey&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's a bunch of code I realize. This script does a few things; let's go through it. Skip the functions for now and jump to the "Start Here" part. The script reads in our image and determines its height and width. Then we convert it from a height by width matrix into a list of RBG values for easier processing. &lt;/p&gt;
&lt;p&gt;Next, the script examines the image (our cropped puppy) and uses &lt;code&gt;KMeans()&lt;/code&gt; to find the five most common colors (lines 59 - 61). The &lt;code&gt;make_histogram()&lt;/code&gt; function essentially counts the pixels in each of those "buckets" returning those counts as a value between 0 and 1. We sort those "buckets" into a descending order of most to least common. Finally, starting on line 70, the code outputs some useful info about those dominant colors. We loop through our "buckets" to create images (aka "bars") from those colors and show them in OpenCV windows.&lt;/p&gt;
&lt;h2&gt;Output&lt;/h2&gt;
&lt;p&gt;Check out your console: the script outputs the RGB and HSV color values for each of the colored boxes. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;dominant_color.py&lt;span class="w"&gt; &lt;/span&gt;
Bar&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;RGB&lt;span class="w"&gt; &lt;/span&gt;values:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;131&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;114&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;HSV&lt;span class="w"&gt; &lt;/span&gt;values:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;131&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Bar&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;RGB&lt;span class="w"&gt; &lt;/span&gt;values:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;153&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;133&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;114&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;HSV&lt;span class="w"&gt; &lt;/span&gt;values:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;153&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Bar&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;RGB&lt;span class="w"&gt; &lt;/span&gt;values:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;141&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;138&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;136&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;HSV&lt;span class="w"&gt; &lt;/span&gt;values:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;141&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Bar&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;RGB&lt;span class="w"&gt; &lt;/span&gt;values:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;165&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;156&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;148&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;HSV&lt;span class="w"&gt; &lt;/span&gt;values:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;165&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Bar&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;RGB&lt;span class="w"&gt; &lt;/span&gt;values:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;87&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;75&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;HSV&lt;span class="w"&gt; &lt;/span&gt;values:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Given our cropped puppy image, the script outputs two CV2 windows. The first shows colored boxes for the top 5 dominant colors, listed most to least common left to right. If you move that window out of the way, you'll see a similar one showing those same colors arranged in HSV order (sorted lowest to highest by hue, saturation, then value).&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2018/puppy_5_colors_with_hsv.png" width="480" title="Top 5 colors of the puppy"/&gt;&lt;/p&gt;
&lt;p&gt;It might be surprising when seen as RGB boxes on your screen, but the dominant colors are fairly close in hue, even in saturation. In fact, hue values range between 12 and 15. (Sidenote: OpenCV represents hue ranges from 0-180, not 0-360 as graphics apps typically do.)&lt;/p&gt;
&lt;p&gt;This gives us just what we need to isolate objects in an image or video stream. In a future article, I'll show how to isolate objects based on their color. My plan is to then go on to show how to discern info about the object, such as its actual size, position in the real world, and so forth. But, let's not get ahead of ourselves!&lt;/p&gt;
&lt;h2&gt;In summary&lt;/h2&gt;
&lt;p&gt;In this article, I showed how to find an average color of an image. While I didn't go on to prove it with an object isolation script, average colors are typically the best way to represent a target object. Instead, I showed a more powerful technique, the k-means cluster. I showed how to determine the five most common colors. The script I provided output convenient RGB and HSV values that you can use for object isolation.&lt;/p&gt;
&lt;p&gt;Source materials and further explorations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.timpoulsen.com/2018/handling-mouse-events-in-opencv.html"&gt;Selecting a region of interest&lt;/a&gt; within an image/video (from this site)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pyimagesearch.com/2014/05/26/opencv-python-k-means-color-clustering/" target="_blank"&gt;PyImageSearch: OpenCV k-means color clustering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/43111029/how-to-find-the-average-colour-of-an-image-in-python-with-opencv" target="_blank"&gt;StackOverflow: How to find the average colour of an image in python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://adamspannbauer.github.io/2018/03/02/app-icon-dominant-colors/" target="_blank"&gt;Adam Spannbauer: Dominant colors in app icons&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="OpenCV"></category><category term="opencv"></category><category term="python"></category></entry><entry><title>Ad-hoc objects in Python</title><link href="https://www.timpoulsen.com/2018/ad-hoc-objects-in-python.html" rel="alternate"></link><published>2018-07-06T00:00:00-04:00</published><updated>2018-07-06T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-07-06:/2018/ad-hoc-objects-in-python.html</id><summary type="html">&lt;p&gt;If you know one programming language, it's natural to look for parallels as you learn a new language. In my case, I've coded in JavaScript for many years while Python is much more recent for me. I regularly find myself thinking "in JavaScript, I'd do ..."&lt;/p&gt;
&lt;p&gt;Take object handling. I really …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you know one programming language, it's natural to look for parallels as you learn a new language. In my case, I've coded in JavaScript for many years while Python is much more recent for me. I regularly find myself thinking "in JavaScript, I'd do ..."&lt;/p&gt;
&lt;p&gt;Take object handling. I really like JavaScript's dynamic handling of objects. With JS, there's no need to create a class template (though you can). Just create an ad-hoc object and start assigning it properties and methods. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// JavaScript example&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;js_obj&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="c1"&gt;// dynamically add a property&lt;/span&gt;
&lt;span class="nx"&gt;js_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foo&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;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// and use it&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;js_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// bar&lt;/span&gt;

&lt;span class="c1"&gt;// you can even create methods&lt;/span&gt;
&lt;span class="nx"&gt;js_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&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="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&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="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// and use them&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;js_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What wasn't obvious to me at first was that I could do similar things in Python. I saw the &lt;code&gt;class ClassName()&lt;/code&gt; syntax and was fooled into thinking that I had to predefine my class with all its attributes and methods before I could instantiate objects from it. Not perfectly parallel, but in Python, we can do this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python example&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="n"&gt;py_obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;py_obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;baz&amp;#39;&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;py_obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# baz&lt;/span&gt;

&lt;span class="c1"&gt;# we can dynamically assign methods with lambdas&lt;/span&gt;
&lt;span class="n"&gt;py_obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&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;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;py_obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 7&lt;/span&gt;


&lt;span class="c1"&gt;# or even full functions&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;undefined&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;step_down&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="n"&gt;start&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="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step_down&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;step_down&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;


&lt;span class="n"&gt;py_obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;py_obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 120&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's great. But a leg-up that JS has is that objects are "associative arrays" meaning that in addition to the dot-notation, we can use array notation (e.g. Python's dict notation) to access properties of JS objects:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// this works in JS:&lt;/span&gt;
&lt;span class="n"&gt;js_obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wouldn&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;work&lt;/span&gt;
&lt;span class="n"&gt;py_obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Why would that matter you ask? If you need to dynamically access properties of an object, say by looping through a list of keys returned by an API, the associative array notation comes in very handy. &lt;/p&gt;
&lt;p&gt;Here I present a simple class that gives similar functionality in Python. The Object class will create an object that you can access with either dot or dict notation. It offers support for iteration via &lt;code&gt;items()&lt;/code&gt; and could be used by methods expecting a &lt;code&gt;__getitem__&lt;/code&gt; accessor. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;    Creates an object for simple key/value storage; enables access via&lt;/span&gt;
&lt;span class="sd"&gt;    object or dictionary syntax (i.e. obj.foo or obj[&amp;#39;foo&amp;#39;]).&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;arg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;setattr&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="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__getitem__&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="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;        Enables dict-like access, ie. foo[&amp;#39;bar&amp;#39;]&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__getattribute__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__str__&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="sd"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;        String-representation as newline-separated string useful in print()&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&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;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;items&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="sd"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;        Enables enumeration via foo.items()&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&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;To use it:&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;generic_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generic_object&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# bar&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generic_object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# bar&lt;/span&gt;

&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="n"&gt;dynamically&lt;/span&gt;
&lt;span class="n"&gt;generic_object&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generic_object&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 123&lt;/span&gt;

&lt;span class="c1"&gt;# iterate over its attributes&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;generic_object&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# even use its convenience string representation&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generic_object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# prints multi-line string of key/values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This simple generic object class uses a couple of the &lt;a href="https://docs.python.org/3/reference/datamodel.html#special-method-names" target="_blank"&gt;special attribute names&lt;/a&gt; common to all Python objects. By overriding &lt;code&gt;__getitem__&lt;/code&gt;, &lt;code&gt;__str__&lt;/code&gt;, and &lt;code&gt;items&lt;/code&gt; we give custom functionality to objects created from this class.&lt;/p&gt;
&lt;p&gt;Just because you can, doesn't mean you should. Many would argue that ad-hoc objects like this are not "Pythonic." There are other data structures in Python that can serve a similar purpose as generic containers of data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There's the Dict (dictionary) and for pre-Python 3.7, OrderedDict. The disadvantage to dicts is the way you access their attributes &amp;mdash; &lt;code&gt;my_dict['some_attribute']&lt;/code&gt;. It's wordy, and to me inelegant compared to the &lt;code&gt;my_object.some_attribute&lt;/code&gt; syntax of objects.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/collections.html#collections.namedtuple" target="_blank"&gt;Named tuples&lt;/a&gt; offer similar functionality, though like "normal" tuples, they are immutable.&lt;/li&gt;
&lt;li&gt;And starting with Python 3.7, you have &lt;a href="https://docs.python.org/3/library/dataclasses.html" target="_blank"&gt;data classes&lt;/a&gt; (also see &lt;a href="https://hackernoon.com/a-brief-tour-of-python-3-7-data-classes-22ee5e046517" target="_blank"&gt;Hackernoon's overview article&lt;/a&gt;). If you can use Python 3.7, data classes are superior to my simple class above (such as enforcing type hinting) so use them instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As always, use the data structure that best suits your needs and will most clearly communicate to future readers of your code its purpose and requirements. But don't be afraid to explore the ins and outs of the language too.&lt;/p&gt;</content><category term="Python"></category><category term="python"></category></entry><entry><title>Path prediction with OpenCV</title><link href="https://www.timpoulsen.com/2018/path-prediction-with-opencv.html" rel="alternate"></link><published>2018-06-03T00:00:00-04:00</published><updated>2018-06-03T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-06-03:/2018/path-prediction-with-opencv.html</id><summary type="html">&lt;p&gt;Recently, I spotted an article on HackerNews, &lt;a href="https://news.ycombinator.com/item?id=17142815"&gt;Self-driving RC car that uses AI to predict turning angles&lt;/a&gt;. The project looked interesting. They applied AI to predict the path a radio-controlled car should take to stay centered over a tape track on the floor. Using a Raspberry Pi and PiCam mounted …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently, I spotted an article on HackerNews, &lt;a href="https://news.ycombinator.com/item?id=17142815"&gt;Self-driving RC car that uses AI to predict turning angles&lt;/a&gt;. The project looked interesting. They applied AI to predict the path a radio-controlled car should take to stay centered over a tape track on the floor. Using a Raspberry Pi and PiCam mounted to the car, their algorithm performed path prediction and controlled the car's servos via the Pi's GPIO pins.&lt;/p&gt;
&lt;p&gt;As I read the article and looked through their GitHub project, I realized that much of what they were doing with AI could probably be done nearly as effectively with OpenCV. My solution below won't cover all the functionality of the original project. But, at a basic level, here's what we'll do instead:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Acquire an image and pre-process it.&lt;/li&gt;
&lt;li&gt;Isolate the tape from the background by strongly thresholding the image.&lt;/li&gt;
&lt;li&gt;Find the largest contour that surrounds the tape track.&lt;/li&gt;
&lt;li&gt;Find the minimum size bounding box around that contour.&lt;/li&gt;
&lt;li&gt;Finally, calculate the angle of that bounding box to the Y axis&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That angle is the needed turning angle to keep the car on track. From there, given the car's speed, you would want to apply some smoothing (turn somewhat less than the angle) to avoid jerky, sudden turns. (I'll note some other limitations below.)&lt;/p&gt;
&lt;p&gt;The script is short enough that I'll just include the whole thing here. Comments inline explain the code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&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="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;imutils&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;glob&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&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;# for demo purposes, we&amp;#39;ll use some static JPGs&lt;/span&gt;
&lt;span class="c1"&gt;# of a green tape line&lt;/span&gt;
&lt;span class="n"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;full_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&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;cwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;images&amp;#39;&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;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&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;full_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;*.jpg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;image&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="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# https://github.com/jrosebr1/imutils has a handy resize func&lt;/span&gt;
    &lt;span class="n"&gt;resized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imutils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;480&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;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;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resized&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;# threshold fairly agressively assuming a bold tape&lt;/span&gt;
    &lt;span class="c1"&gt;# color on a white background&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thresh&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;threshold&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="mi"&gt;240&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="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;THRESH_BINARY_INV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# get the countours&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cnts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&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;findContours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thresh&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;RETR_LIST&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;CHAIN_APPROX_SIMPLE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# sort the contours&lt;/span&gt;
    &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&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;contourArea&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# get the min rotated bounding rect of the largest one&lt;/span&gt;
    &lt;span class="n"&gt;rect&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;minAreaRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&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;# grab the height, width, and angle of that rect&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rect&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;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# The value of OpenCV&amp;#39;s angle depends on the rect&amp;#39;s&lt;/span&gt;
    &lt;span class="c1"&gt;# height/width ratio. https://stackoverflow.com/a/24085639/292947&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;
    &lt;span class="c1"&gt;# do something here with the angle to steer the car&lt;/span&gt;
    &lt;span class="c1"&gt;# we&amp;#39;ll just print it&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# and then, for visual purposes of this article, draw&lt;/span&gt;
    &lt;span class="c1"&gt;# the bounding box on and show the resized image&lt;/span&gt;
    &lt;span class="n"&gt;box&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;boxPoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;box&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;int0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;box&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;drawContours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;box&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="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;2&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Image&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resized&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;waitKey&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;/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 a sample of what is output:&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2018/angler1.png" width="640" title="Bounding box and angle output"/&gt;&lt;/p&gt;
&lt;p&gt;You can see the angle output to the console (34.04&amp;deg;) as well as the bounding box around the contour (which outlines the green tape). If you want to try yourself with the images I used, I've put them here: &lt;a href="../images/2018/angler_images.zip"&gt;angler_images.zip&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Limitations&lt;/h2&gt;
&lt;p&gt;As may be obvious, there are a few limitations to this approach. First, as shown above, the algorithm wouldn't permit the car to closely follow the line. If the car turned the 34-degree angle calculated above, it would cut the corner rather than following the bend in the tape.&lt;/p&gt;
&lt;p&gt;Perhaps less obvious is that the car could end up driving off center from the tape. Consider the following diagram:&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2018/angler2.png" width="640" title="Algorithm doesn't account for the offset"/&gt;&lt;/p&gt;
&lt;p&gt;When the car captured this image, it was offset from the tape. The vertical dashed line approximates the center of the image. The solid black line approximates the center of the green tape track, which is ideally where we want the car to drive. Instead, if the car simply turned the roughly 10 degrees calculated by our simple algorithm, it would drive next to the tape track following the solid green line.&lt;/p&gt;
&lt;p&gt;Ideally, we'd want to calculate the offset of the tape and adjust our angle. In the simple case shown in the above diagram, simply driving straight forward for a moment or two would resolve the offset. But, if we were well off the tape track, we might even need to turn left instead of the calculated right.&lt;/p&gt;
&lt;p&gt;More formally, we would want to adjust our calculated bounding box angle by approximately the angle formed by the tape offset and the height of the image. We'd find the tape offset (the base of our triangle) by finding the center point of the bottom of the bounding box and subtracting that from the center of the image. Then, the adjustment angle would be the &lt;code&gt;arctan(tape_offset &amp;divide; image_height)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The final limitation to consider, and something faced by the original project, is the processing speed of the Raspberry Pi. The faster you drive the car, the faster the images would need to be processed. At some point, the car will drive faster than the Pi can calculate. Your car would drive enough between frames that the tape would no longer be in the captured image.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Obviously, I haven't put this algorithm to the real test. Maybe someday I'll dig my &lt;a href="https://www.teamassociated.com/cars_and_trucks/RC10_Classic/RC10_Classic_Kit/" target="_blank"&gt;RC-10&lt;/a&gt; out of the garage and try it for real. Just the same, I still believe that OpenCV and a little trigonometry would be enough to let a toy car automatically follow a tape track, no artificial intelligence needed.&lt;/p&gt;</content><category term="OpenCV"></category><category term="opencv"></category><category term="python"></category></entry><entry><title>Handling mouse events in OpenCV</title><link href="https://www.timpoulsen.com/2018/handling-mouse-events-in-opencv.html" rel="alternate"></link><published>2018-05-21T00:00:00-04:00</published><updated>2018-05-21T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-05-21:/2018/handling-mouse-events-in-opencv.html</id><summary type="html">&lt;p&gt;For a project I'm working on, I need to select a region of interest in a video frame and extract info about that region. Fortunately, that's a pretty simple task to do in OpenCV. In this article, I'll show you how to detect and react to mouse actions, such as …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For a project I'm working on, I need to select a region of interest in a video frame and extract info about that region. Fortunately, that's a pretty simple task to do in OpenCV. In this article, I'll show you how to detect and react to mouse actions, such as clicking and moving. Then, as an example of what to do with that sort of functionality, we'll see how to crop an image to the dimensions selected.&lt;/p&gt;
&lt;p&gt;You'll need Python and OpenCV installed. I'm using Python 3.6 (Anaconda) and OpenCV 3.4 myself. Open your favorite text editor and create a file named &lt;strong&gt;crop_image.py&lt;/strong&gt; and enter the following code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cv2&lt;/span&gt;

&lt;span class="n"&gt;coords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;drawing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;click_and_crop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;That gives us the shell of the file we'll fill in throughout the rest of this article. We'll need the argparse and cv2 (OpenCV) libraries, as well as a couple of global variables. We'll have a couple of functions plus &lt;tt class="docutils literal"&gt;main()&lt;/tt&gt; and the code to call &lt;tt class="docutils literal"&gt;main()&lt;/tt&gt; when we run the script.&lt;/p&gt;
&lt;p&gt;Let's fill in the rest of &lt;tt class="docutils literal"&gt;main()&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&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;/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;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# construct the argument parser and parse the arguments&lt;/span&gt;
    &lt;span class="n"&gt;ap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-i&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;--image&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Path to the image&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;image_source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;image&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;image&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="c1"&gt;# now get our image from either the file or built-in webcam&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# show the captured image in a window&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;namedWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CapturedImage&amp;#39;&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;WINDOW_NORMAL&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CapturedImage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# specify the callback function to be called when the user&lt;/span&gt;
        &lt;span class="c1"&gt;# clicks/drags in the &amp;#39;CapturedImage&amp;#39; window&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;setMouseCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CapturedImage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;click_and_crop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# wait for Esc or q key and then exit&lt;/span&gt;
            &lt;span class="n"&gt;key&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;waitKey&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="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;q&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Image cropped at coordinates: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&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;destroyAllWindows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;That's a good chunk of code. Let's walk through what it's doing. Using argparse, the script will look for and parse command-line arguments. This script will accept a single argument (either &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-i&lt;/span&gt;&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--image&lt;/span&gt;&lt;/tt&gt;) to specify an image file to load. We'll store the value in an &lt;tt class="docutils literal"&gt;image_source&lt;/tt&gt; variable on line 12. If no image is specified, we'll store &lt;tt class="docutils literal"&gt;0&lt;/tt&gt; in the variable -- that will tell the script to use the default webcam on the system as the image source.&lt;/p&gt;
&lt;p&gt;One line 14, we call our &lt;tt class="docutils literal"&gt;get_image()&lt;/tt&gt; function (which we've yet to write) to load our image. If we successfully load the image, the statements in the &lt;tt class="docutils literal"&gt;if&lt;/tt&gt; block will run. line 17 createa a named &lt;tt class="docutils literal"&gt;cv2.imshow&lt;/tt&gt; window and the next line shows the image in that window.&lt;/p&gt;
&lt;p&gt;Line 21 is perhaps the key line here. With &lt;tt class="docutils literal"&gt;setMouseCallback()&lt;/tt&gt; we specify which window to listen for mouse events on ('CapturedImage'), what function to call each time there's a mouse event (a click, drag, move, etc.), as well as an optional third parameter. The &lt;tt class="docutils literal"&gt;cv2.setMouseCallback()&lt;/tt&gt; function lets us pass whatever value we want in that third parameter. In the docs and other examples, you'll typically see it named generically as &lt;tt class="docutils literal"&gt;param&lt;/tt&gt; for this reason. We'll use it to pass the image to that function.&lt;/p&gt;
&lt;p&gt;Finally, we want the image window to remain open while the rest of the script operates. So, we use a while loop. In each iteration of the loop, we use the &lt;tt class="docutils literal"&gt;cv.waitKey()&lt;/tt&gt; function to listen for a key press. If the key pressed is the escape key (value 27) or the letter q (the script would actually receive the ordinal value of the character 'q'), then we destroy all the cv2 windows and break out of the loop, ending the script.&lt;/p&gt;
&lt;p&gt;There's the main logic of our script -- get an image, show it, then watch for mouse events and call a function when they happen. We've got a couple of other functions to write. Let's tackle &lt;tt class="docutils literal"&gt;get_image()&lt;/tt&gt; first.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&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;/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;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# open the camera, grab a frame, and release the camera&lt;/span&gt;
    &lt;span class="n"&gt;cam&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;VideoCapture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image_captured&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cam&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="n"&gt;cam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_captured&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This function is pretty straightforward. We'll use &lt;tt class="docutils literal"&gt;cv2.VideoCapture()&lt;/tt&gt; to open whatever source it's passed (which will be either a filename or &lt;tt class="docutils literal"&gt;0&lt;/tt&gt; for the webcam). It will read a single frame, then return the captured image.&lt;/p&gt;
&lt;p&gt;The bigger function is &lt;tt class="docutils literal"&gt;click_and_crop()&lt;/tt&gt; which is our event handler. Let's see this one in stages.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&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;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;click_and_crop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Callback function, called by OpenCV when the user interacts&lt;/span&gt;
&lt;span class="sd"&gt;    with the window using the mouse. This function will be called&lt;/span&gt;
&lt;span class="sd"&gt;    repeatedly as the user interacts.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# get access to the two global variables we&amp;#39;ll need&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drawing&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Here we see the function signature. When called, the function will be passed five arguments. The first is the event, such as the left button being pressed down (&lt;tt class="docutils literal"&gt;cv2.EVENT_LBUTTONDOWN&lt;/tt&gt;). Next comes the x/y coordinates of the event. I could not find documentation on the &lt;tt class="docutils literal"&gt;flag&lt;/tt&gt; parameter, other than the docs saying it was an integer. In testing, it appears to identify the event type and modifier keys. For example on my Mac, while moving the mouse, &lt;tt class="docutils literal"&gt;flag&lt;/tt&gt; is 0. Holding down the Control key while moving the mouse, it was 8. Holding down Shift and moving was 16. Since this may vary by platform, test before using this parameter.&lt;/p&gt;
&lt;p&gt;The last param, &lt;tt class="docutils literal"&gt;image&lt;/tt&gt;, corresponds to whatever third parameter we passed in the &lt;tt class="docutils literal"&gt;cv2.setMouseCallback()&lt;/tt&gt; call. In our case, our call looks like &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;cv2.setMouseCallback('CapturedImage',&lt;/span&gt; click_and_crop, image)&lt;/tt&gt; where we're passing the captured image in that parameter.&lt;/p&gt;
&lt;p&gt;Next, we're going to handle three different mouse events associated with cropping an image. First, the user will click the mouse down at the top-left corner. Then, they'll drag down to the bottom right. And third, they'll release the mouse button. I'll cover these three separately. As you examine this code, remember, this function will be called repeatedly, every time there's a mouse event (such as moving the mouse over the image).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&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;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&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;EVENT_LBUTTONDOWN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# user has clicked the mouse&amp;#39;s left button&lt;/span&gt;
        &lt;span class="n"&gt;drawing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="c1"&gt;# save those starting coordinates&lt;/span&gt;
        &lt;span class="n"&gt;coords&lt;/span&gt; &lt;span class="o"&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;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;When the user clicks the button down, we'll set our global &lt;tt class="docutils literal"&gt;drawing&lt;/tt&gt; variable to True. We'll use this for tracking state in the next sections. Then, we store the x/y coordinates of the mouse down event in our global &lt;tt class="docutils literal"&gt;coords&lt;/tt&gt; list.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;52&lt;/span&gt;
&lt;span class="normal"&gt;53&lt;/span&gt;
&lt;span class="normal"&gt;54&lt;/span&gt;
&lt;span class="normal"&gt;55&lt;/span&gt;
&lt;span class="normal"&gt;56&lt;/span&gt;
&lt;span class="normal"&gt;57&lt;/span&gt;
&lt;span class="normal"&gt;58&lt;/span&gt;
&lt;span class="normal"&gt;59&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;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&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;EVENT_MOUSEMOVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# user is moving the mouse within the window&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;drawing&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# if we&amp;#39;re in drawing mode, we&amp;#39;ll draw a green rectangle&lt;/span&gt;
            &lt;span class="c1"&gt;# from the starting x,y coords to our current coords&lt;/span&gt;
            &lt;span class="n"&gt;clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&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;rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coords&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="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="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="mi"&gt;2&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CapturedImage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The user will move their mouse all over the image, even when they're not trying to crop it. We want this section of the code to run only if they've clicked down the mouse button. For that reason, we check the &lt;tt class="docutils literal"&gt;drawing&lt;/tt&gt; variable set in the mouse down portion of the function. When that's true, we'll create a clone of our original image. Next, we'll draw a rectangle on the clone with the top-left corner at &lt;tt class="docutils literal"&gt;coords[0]&lt;/tt&gt; (where the user clicked the mouse button down) and the current x/y coordinates. We'll draw a green outline (remember, OpenCV specifies the colors in blue-green-red order) with a width of 2. Finally, we show that image in our CapturedImage window.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;60&lt;/span&gt;
&lt;span class="normal"&gt;61&lt;/span&gt;
&lt;span class="normal"&gt;62&lt;/span&gt;
&lt;span class="normal"&gt;63&lt;/span&gt;
&lt;span class="normal"&gt;64&lt;/span&gt;
&lt;span class="normal"&gt;65&lt;/span&gt;
&lt;span class="normal"&gt;66&lt;/span&gt;
&lt;span class="normal"&gt;67&lt;/span&gt;
&lt;span class="normal"&gt;68&lt;/span&gt;
&lt;span class="normal"&gt;69&lt;/span&gt;
&lt;span class="normal"&gt;70&lt;/span&gt;
&lt;span class="normal"&gt;71&lt;/span&gt;
&lt;span class="normal"&gt;72&lt;/span&gt;
&lt;span class="normal"&gt;73&lt;/span&gt;
&lt;span class="normal"&gt;74&lt;/span&gt;
&lt;span class="normal"&gt;75&lt;/span&gt;
&lt;span class="normal"&gt;76&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;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&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;EVENT_LBUTTONUP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# user has released the mouse button, leave drawing mode&lt;/span&gt;
        &lt;span class="c1"&gt;# and crop the photo&lt;/span&gt;
        &lt;span class="n"&gt;drawing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
        &lt;span class="c1"&gt;# save our ending coordinates&lt;/span&gt;
        &lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&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="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# calculate the four corners of our region of interest&lt;/span&gt;
            &lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coords&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;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;coords&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;coords&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="n"&gt;coords&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="c1"&gt;# crop the image using array slicing&lt;/span&gt;
            &lt;span class="n"&gt;roi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;bx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;roi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# make sure roi has height/width to prevent imshow error&lt;/span&gt;
                &lt;span class="c1"&gt;# and show the cropped image in a new window&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;namedWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ROI&amp;quot;&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;WINDOW_NORMAL&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ROI&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Finally, we handle the case when the user releases the mouse button. This is the bottom-right corner of the cropping rectangle. We set &lt;tt class="docutils literal"&gt;drawing&lt;/tt&gt; back to False so we stop processing mouse-move events. We append the new x/y coordinates to our global list. Assuming something didn't go wrong, that list will have two members.&lt;/p&gt;
&lt;p&gt;On line 70, we crop the image. OpenCV images are represented as Numpy arrays of pixel values. We're using array slicing, in perhaps a non-inuitive way. Let's step back to line 68, where we grab the y values from the top-left and bottom-right corners (so &lt;tt class="docutils literal"&gt;ty&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;by&lt;/tt&gt;), and then the x values from the two corners (&lt;tt class="docutils literal"&gt;tx&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;bx&lt;/tt&gt;) out of our global &lt;tt class="docutils literal"&gt;coords&lt;/tt&gt; list.&lt;/p&gt;
&lt;p&gt;We're ready to crop the image. OpenCV images are represented as Numpy arrays of pixel values. Since the image is just an array, we can use array slicing to select a portion of it -- in other words, to crop it. On line 68, we grab the y values from the top-left and bottom-right corners (so &lt;tt class="docutils literal"&gt;ty&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;by&lt;/tt&gt;), and then the x values from the two corners (&lt;tt class="docutils literal"&gt;tx&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;bx&lt;/tt&gt;) out of our global &lt;tt class="docutils literal"&gt;coords&lt;/tt&gt; list. Then on line 70, we crop by slicing the &lt;tt class="docutils literal"&gt;image&lt;/tt&gt; array to those ty/by, tx/bx values and storing the &amp;quot;region of interest&amp;quot; in the &lt;tt class="docutils literal"&gt;roi&lt;/tt&gt; variable.&lt;/p&gt;
&lt;p&gt;If the user were to select a very small slice, such that either the width or height were treated as zero, the &lt;tt class="docutils literal"&gt;imshow()&lt;/tt&gt; function would throw an error. To avoid that, we'll calcualte the height and width of the cropped image by grabbing the first two members of the roi's shape. We do our zero-test and then show the &lt;tt class="docutils literal"&gt;roi&lt;/tt&gt; image in a new OpenCV window.&lt;/p&gt;
&lt;p&gt;And that's it! If you haven't so far, try it out.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;crop_image.py
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Click, drag, and release on the &amp;quot;CapturedImage&amp;quot; window and you'll get a new window with the cropped portion of your original. It may show up behind other windows you have open. (That's an annoying OpenCV bug on some platforms.)&lt;/p&gt;
&lt;div class="section" id="the-whole-shebang"&gt;
&lt;h2&gt;The whole shebang&lt;/h2&gt;
&lt;p&gt;Finally, let's close by seeing the entire script. Hopefully you've typed in the code as we went and didn't just copy &amp;amp; paste it. But this here will let you check your work against my original.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&lt;/span&gt;
&lt;span class="normal"&gt;52&lt;/span&gt;
&lt;span class="normal"&gt;53&lt;/span&gt;
&lt;span class="normal"&gt;54&lt;/span&gt;
&lt;span class="normal"&gt;55&lt;/span&gt;
&lt;span class="normal"&gt;56&lt;/span&gt;
&lt;span class="normal"&gt;57&lt;/span&gt;
&lt;span class="normal"&gt;58&lt;/span&gt;
&lt;span class="normal"&gt;59&lt;/span&gt;
&lt;span class="normal"&gt;60&lt;/span&gt;
&lt;span class="normal"&gt;61&lt;/span&gt;
&lt;span class="normal"&gt;62&lt;/span&gt;
&lt;span class="normal"&gt;63&lt;/span&gt;
&lt;span class="normal"&gt;64&lt;/span&gt;
&lt;span class="normal"&gt;65&lt;/span&gt;
&lt;span class="normal"&gt;66&lt;/span&gt;
&lt;span class="normal"&gt;67&lt;/span&gt;
&lt;span class="normal"&gt;68&lt;/span&gt;
&lt;span class="normal"&gt;69&lt;/span&gt;
&lt;span class="normal"&gt;70&lt;/span&gt;
&lt;span class="normal"&gt;71&lt;/span&gt;
&lt;span class="normal"&gt;72&lt;/span&gt;
&lt;span class="normal"&gt;73&lt;/span&gt;
&lt;span class="normal"&gt;74&lt;/span&gt;
&lt;span class="normal"&gt;75&lt;/span&gt;
&lt;span class="normal"&gt;76&lt;/span&gt;
&lt;span class="normal"&gt;77&lt;/span&gt;
&lt;span class="normal"&gt;78&lt;/span&gt;
&lt;span class="normal"&gt;79&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;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cv2&lt;/span&gt;

&lt;span class="n"&gt;coords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;drawing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# construct the argument parser and parse the arguments&lt;/span&gt;
    &lt;span class="n"&gt;ap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-i&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;--image&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Path to the image&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;image_source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;image&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;image&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="c1"&gt;# now get our image from either the file or built-in webcam&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# show the captured image in a window&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;namedWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CapturedImage&amp;#39;&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;WINDOW_NORMAL&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CapturedImage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# specify the callback function to be called when the user&lt;/span&gt;
        &lt;span class="c1"&gt;# clicks/drags in the &amp;#39;CapturedImage&amp;#39; window&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;setMouseCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CapturedImage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;click_and_crop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# wait for Esc or q key and then exit&lt;/span&gt;
            &lt;span class="n"&gt;key&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;waitKey&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="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;q&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Image cropped at coordinates: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&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;destroyAllWindows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# open the camera, grab a frame, and release the camera&lt;/span&gt;
    &lt;span class="n"&gt;cam&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;VideoCapture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image_captured&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cam&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="n"&gt;cam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_captured&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;click_and_crop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Callback function, called by OpenCV when the user interacts&lt;/span&gt;
&lt;span class="sd"&gt;    with the window using the mouse. This function will be called&lt;/span&gt;
&lt;span class="sd"&gt;    repeatedly as the user interacts.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# get access to a couple of global variables we&amp;#39;ll need&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drawing&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&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;EVENT_LBUTTONDOWN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# user has clicked the mouse&amp;#39;s left button&lt;/span&gt;
        &lt;span class="n"&gt;drawing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="c1"&gt;# save those starting coordinates&lt;/span&gt;
        &lt;span class="n"&gt;coords&lt;/span&gt; &lt;span class="o"&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="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&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;EVENT_MOUSEMOVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# user is moving the mouse within the window&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;drawing&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# if we&amp;#39;re in drawing mode, we&amp;#39;ll draw a green rectangle&lt;/span&gt;
            &lt;span class="c1"&gt;# from the starting x,y coords to our current coords&lt;/span&gt;
            &lt;span class="n"&gt;clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&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;rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coords&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="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="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="mi"&gt;2&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CapturedImage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&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;EVENT_LBUTTONUP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# user has released the mouse button, leave drawing mode&lt;/span&gt;
        &lt;span class="c1"&gt;# and crop the photo&lt;/span&gt;
        &lt;span class="n"&gt;drawing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
        &lt;span class="c1"&gt;# save our ending coordinates&lt;/span&gt;
        &lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&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="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# calculate the four corners of our region of interest&lt;/span&gt;
            &lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coords&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;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;coords&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;coords&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="n"&gt;coords&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="c1"&gt;# crop the image using array slicing&lt;/span&gt;
            &lt;span class="n"&gt;roi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;bx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;roi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# make sure roi has height/width to prevent imshow error&lt;/span&gt;
                &lt;span class="c1"&gt;# and show the cropped image in a new window&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;namedWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ROI&amp;quot;&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;WINDOW_NORMAL&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ROI&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;In this article, you saw how to detect and react to mouse actions, such as clicking and moving. Then, as an example of what to do with that sort of functionality, I showed you how to crop an image to the dimensions selected. In a future article, I will show you how to extract information from that region of interest, such as the predominant color.&lt;/p&gt;
&lt;/div&gt;
</content><category term="OpenCV"></category><category term="opencv"></category></entry><entry><title>Reading email with Python</title><link href="https://www.timpoulsen.com/2018/reading-email-with-python.html" rel="alternate"></link><published>2018-05-10T00:00:00-04:00</published><updated>2018-05-10T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-05-10:/2018/reading-email-with-python.html</id><summary type="html">&lt;p&gt;For a recent project at work, I needed to read and parse email messages with a python script. I found the documentation confusing, and most of the samples on various blogs and StackOverflow posts to be old and not fully compatible with python 3.x. So, here is an adaptation …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For a recent project at work, I needed to read and parse email messages with a python script. I found the documentation confusing, and most of the samples on various blogs and StackOverflow posts to be old and not fully compatible with python 3.x. So, here is an adaptation of my solution.&lt;/p&gt;
&lt;p&gt;Further below in this post is my actual class for interacting with an IMAP email account, logging in and out, accessing the messages in a folder, and even deleting messages. But let's start with a simple script that uses the class.&lt;/p&gt;
&lt;h2&gt;Using the class&lt;/h2&gt;
&lt;p&gt;As I'm sure you know, it's a bad idea to code passwords into a file. Instead, you should load them at runtime, either from a command line argument (e.g. with the &lt;code&gt;argparse&lt;/code&gt; library) or from the system environment. The script I wrote for work was going to be part of a Django project deployed in a Docker container, so for my needs an environment variable was the best choice. &lt;/p&gt;
&lt;p&gt;Before you can use the script below, you'll have to add a &lt;code&gt;mailpwd&lt;/code&gt; environment variable (or modify the code to use argparse). On Linux/OS X, use &lt;code&gt;export mailpwd=password&lt;/code&gt; and on Windows, er, sorry, I don't know.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.ImapClient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ImapClient&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    You will need to store your email password in your environment, e.g.&lt;/span&gt;
&lt;span class="sd"&gt;    export mailpwd=password&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;imap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImapClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;you@gmail.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# retrieve messages from a given sender&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;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a_friend@another_isp.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Do something with the messages&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Messages in my inbox:&amp;quot;&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;msg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# msg is a dict of {&amp;#39;num&amp;#39;: num, &amp;#39;body&amp;#39;: body}&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;body&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="c1"&gt;# you could delete them after viewing&lt;/span&gt;
        &lt;span class="c1"&gt;# imap.delete_message(msg[&amp;#39;num&amp;#39;])&lt;/span&gt;
    &lt;span class="c1"&gt;# when done, you should log out&lt;/span&gt;
    &lt;span class="n"&gt;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&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;h2&gt;Some peculiarities of the class&lt;/h2&gt;
&lt;p&gt;For my work needs, I needed to access a GSuite (Gmail for business) email account. So, the class is a bit specific to Gmail. While it will work with other providers, you may need to tweak the &lt;code&gt;delete_message&lt;/code&gt; method since it is specific to Gmail's trash-vs-deleted folder locations.&lt;/p&gt;
&lt;p&gt;Another specific need I had was to monitor emails from a specific sender. Thus, as shown on line 12 above, you must pass in a sender's email address when calling &lt;code&gt;get_message()&lt;/code&gt;. The script would need some rewriting to accommodate other needs. But hopefully what's below will get you started.&lt;/p&gt;
&lt;h2&gt;The class&lt;/h2&gt;
&lt;p&gt;The class itself is considerably longer and more involved than the script that uses it. The following should be in a file named ImapClient.py in the same directory as the script that uses it. Look the code over. I'll explain some its points after the code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;span class="normal"&gt; 39&lt;/span&gt;
&lt;span class="normal"&gt; 40&lt;/span&gt;
&lt;span class="normal"&gt; 41&lt;/span&gt;
&lt;span class="normal"&gt; 42&lt;/span&gt;
&lt;span class="normal"&gt; 43&lt;/span&gt;
&lt;span class="normal"&gt; 44&lt;/span&gt;
&lt;span class="normal"&gt; 45&lt;/span&gt;
&lt;span class="normal"&gt; 46&lt;/span&gt;
&lt;span class="normal"&gt; 47&lt;/span&gt;
&lt;span class="normal"&gt; 48&lt;/span&gt;
&lt;span class="normal"&gt; 49&lt;/span&gt;
&lt;span class="normal"&gt; 50&lt;/span&gt;
&lt;span class="normal"&gt; 51&lt;/span&gt;
&lt;span class="normal"&gt; 52&lt;/span&gt;
&lt;span class="normal"&gt; 53&lt;/span&gt;
&lt;span class="normal"&gt; 54&lt;/span&gt;
&lt;span class="normal"&gt; 55&lt;/span&gt;
&lt;span class="normal"&gt; 56&lt;/span&gt;
&lt;span class="normal"&gt; 57&lt;/span&gt;
&lt;span class="normal"&gt; 58&lt;/span&gt;
&lt;span class="normal"&gt; 59&lt;/span&gt;
&lt;span class="normal"&gt; 60&lt;/span&gt;
&lt;span class="normal"&gt; 61&lt;/span&gt;
&lt;span class="normal"&gt; 62&lt;/span&gt;
&lt;span class="normal"&gt; 63&lt;/span&gt;
&lt;span class="normal"&gt; 64&lt;/span&gt;
&lt;span class="normal"&gt; 65&lt;/span&gt;
&lt;span class="normal"&gt; 66&lt;/span&gt;
&lt;span class="normal"&gt; 67&lt;/span&gt;
&lt;span class="normal"&gt; 68&lt;/span&gt;
&lt;span class="normal"&gt; 69&lt;/span&gt;
&lt;span class="normal"&gt; 70&lt;/span&gt;
&lt;span class="normal"&gt; 71&lt;/span&gt;
&lt;span class="normal"&gt; 72&lt;/span&gt;
&lt;span class="normal"&gt; 73&lt;/span&gt;
&lt;span class="normal"&gt; 74&lt;/span&gt;
&lt;span class="normal"&gt; 75&lt;/span&gt;
&lt;span class="normal"&gt; 76&lt;/span&gt;
&lt;span class="normal"&gt; 77&lt;/span&gt;
&lt;span class="normal"&gt; 78&lt;/span&gt;
&lt;span class="normal"&gt; 79&lt;/span&gt;
&lt;span class="normal"&gt; 80&lt;/span&gt;
&lt;span class="normal"&gt; 81&lt;/span&gt;
&lt;span class="normal"&gt; 82&lt;/span&gt;
&lt;span class="normal"&gt; 83&lt;/span&gt;
&lt;span class="normal"&gt; 84&lt;/span&gt;
&lt;span class="normal"&gt; 85&lt;/span&gt;
&lt;span class="normal"&gt; 86&lt;/span&gt;
&lt;span class="normal"&gt; 87&lt;/span&gt;
&lt;span class="normal"&gt; 88&lt;/span&gt;
&lt;span class="normal"&gt; 89&lt;/span&gt;
&lt;span class="normal"&gt; 90&lt;/span&gt;
&lt;span class="normal"&gt; 91&lt;/span&gt;
&lt;span class="normal"&gt; 92&lt;/span&gt;
&lt;span class="normal"&gt; 93&lt;/span&gt;
&lt;span class="normal"&gt; 94&lt;/span&gt;
&lt;span class="normal"&gt; 95&lt;/span&gt;
&lt;span class="normal"&gt; 96&lt;/span&gt;
&lt;span class="normal"&gt; 97&lt;/span&gt;
&lt;span class="normal"&gt; 98&lt;/span&gt;
&lt;span class="normal"&gt; 99&lt;/span&gt;
&lt;span class="normal"&gt;100&lt;/span&gt;
&lt;span class="normal"&gt;101&lt;/span&gt;
&lt;span class="normal"&gt;102&lt;/span&gt;
&lt;span class="normal"&gt;103&lt;/span&gt;
&lt;span class="normal"&gt;104&lt;/span&gt;
&lt;span class="normal"&gt;105&lt;/span&gt;
&lt;span class="normal"&gt;106&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;email&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;email.header&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;imaplib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImapClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;imap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&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="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;imap.gmail.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;use_ssl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;move_to_trash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# check for required param&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;You must provide a recipient email address&amp;#39;&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;recipient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;recipient&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;use_ssl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;use_ssl&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;move_to_trash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;move_to_trash&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;recipient_folder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;INBOX&amp;#39;&lt;/span&gt;
        &lt;span class="c1"&gt;# instantiate our IMAP client object&lt;/span&gt;
        &lt;span class="k"&gt;if&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;use_ssl&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;imap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imaplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IMAP4_SSL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&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;imap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imaplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IMAP4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&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;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;login&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;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mailpwd&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imaplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IMAP4_SSL&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="n"&gt;imaplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IMAP4&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;LOGIN FAILED!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;logout&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&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;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;select_folder&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="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Select the IMAP folder to read messages from. By default&lt;/span&gt;
&lt;span class="sd"&gt;        the class will read from the INBOX folder&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&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;recipient_folder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_messages&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="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Scans for email messages from the given sender and optionally&lt;/span&gt;
&lt;span class="sd"&gt;        with the given subject&lt;/span&gt;

&lt;span class="sd"&gt;        :param sender Email address of sender of messages you&amp;#39;re searching for&lt;/span&gt;
&lt;span class="sd"&gt;        :param subject (Partial) subject line to scan for&lt;/span&gt;
&lt;span class="sd"&gt;        :return List of dicts of {&amp;#39;num&amp;#39;: num, &amp;#39;body&amp;#39;: body}&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;You must provide a sender email address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# select the folder, by default INBOX&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&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;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&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;recipient_folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;OK&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ERROR: Unable to open the &lt;/span&gt;&lt;span class="si"&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;recipient_folder&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; folder&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&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;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

        &lt;span class="n"&gt;mbox_response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msgnums&lt;/span&gt; &lt;span class="o"&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;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;FROM&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mbox_response&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;OK&amp;#39;&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;num&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msgnums&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rawmsg&lt;/span&gt; &lt;span class="o"&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;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;(RFC822)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;OK&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERROR getting message&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message_from_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawmsg&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;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                &lt;span class="n"&gt;msg_subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Subject&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg_subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_multipart&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;part&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                            &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_content_type&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                            &lt;span class="n"&gt;disp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part&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;&amp;#39;Content-Disposition&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                            &lt;span class="c1"&gt;# look for plain text parts, but skip attachments&lt;/span&gt;
                            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;text/plain&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;attachment&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;disp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                                &lt;span class="n"&gt;charset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_content_charset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                                &lt;span class="c1"&gt;# decode the base64 unicode bytestring into plain text&lt;/span&gt;
                                &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_payload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&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="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ignore&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                &lt;span class="c1"&gt;# if we&amp;#39;ve found the plain/text part, stop looping thru the parts&lt;/span&gt;
                                &lt;span class="k"&gt;break&lt;/span&gt;
                    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c1"&gt;# not multipart - i.e. plain text, no attachments&lt;/span&gt;
                        &lt;span class="n"&gt;charset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_content_charset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_payload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&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="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ignore&amp;quot;&lt;/span&gt;&lt;span class="p"&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;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;num&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;body&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete_message&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="n"&gt;msg_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;if&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;move_to_trash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# move to Trash folder&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;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;+X-GM-LABELS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Trash&amp;#39;&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;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expunge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;else&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;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;+FLAGS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Deleted&amp;#39;&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;imap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expunge&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;h2&gt;Class details&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;login()&lt;/code&gt; and &lt;code&gt;logout()&lt;/code&gt; methods should be fairly self-explanatory. The &lt;code&gt;select_folder()&lt;/code&gt; method is optional. By default, the class will read from the INBOX folder but you could change that with this method.&lt;/p&gt;
&lt;p&gt;As you can see in the &lt;code&gt;__init__&lt;/code&gt; method, you must specify a recipient address. This is the email account you're logging into. You can override other defaults, such as the server to log into, whether to connect over SSL, and so forth.&lt;/p&gt;
&lt;p&gt;Let's dig into the &lt;code&gt;get_messages()&lt;/code&gt; method since it's the meat of what the class is offering. After checking params, it attempts to select the folder to search. Of the two values returned, we care only about the first one &lt;code&gt;resp&lt;/code&gt; which will be the string 'OK' if we succeed. Next we search for messages from the sender (line 68). Again, two values are returned, but this time we want to use both.&lt;/p&gt;
&lt;p&gt;As before, the first value, &lt;code&gt;mbox_response&lt;/code&gt; is an OK/not okay string. The second value is a list, the first member of which is the list of message numbers that match our search criteria. Every IMAP message in the folder is identified by an integer ID. To fetch the actual message, we'll use the &lt;code&gt;imap.fetch()&lt;/code&gt; method, passing to it the message ID number, and the portion of the content we want to retrieve. If you've looked at other IMAP examples, you'll see folks using many other content selectors. Here, we're using &lt;code&gt;'(RFC822)'&lt;/code&gt; to basically grab the entire message.&lt;/p&gt;
&lt;p&gt;Assuming that was retrieved from the server successfully, on line 75 we convert the raw representation of the message into a mail object we can further inspect. Then, we grab the subject on line 76. My original needs called for looking for messages with a given subject. It's optional; if you don't pass in a subject param to the function you'll retrieve all the messages from the sender.&lt;/p&gt;
&lt;p&gt;Starting on line 78, we extract the text from the message. The full IMAP specification (RFC 822) is quite complex. But for our needs now, we can ignore many of those details. Messages can be single or multipart. A single part message would be a very simple plain text email message, the type generated by email clients of the days before pretty formatted emails. (Such messages are still sent, though more typically these days by scripts/generators rather than by users with some mail app.) Multipart messages represent their contents in multiple formats, such as plain text and HTML formatted. The typical Gmail, Outlook, webmail generated email message would be a multipart message containing probably both a plain text and HTML formatted part.&lt;/p&gt;
&lt;p&gt;On line 79, we determine the message type and on line 80, we begin walking the parts looking for the plain text part. We do so in two ways. First, with &lt;code&gt;part.get_content_type()&lt;/code&gt; we determine whether it is plain/text or not. But, a text attachment to a message would have that content type too. So, on line 84 we check for that by looking at the content disposition, which will include the string &lt;code&gt;'attachment'&lt;/code&gt; if this part is an attachment. &lt;/p&gt;
&lt;p&gt;Finally (and we do this for both the multipart and single part sections) we need to convert the raw byte representation of the part into plain text. We determine its original character set (e.g. ISO-8859-1, UTF-8, etc.) using &lt;code&gt;part.get_content_charset()&lt;/code&gt; and then convert to that representation on line 87 (or 92). &lt;/p&gt;
&lt;p&gt;Each message that makes it through this filtering and converting is added as a dict to the &lt;code&gt;messages&lt;/code&gt; list. We return both the message ID and its text. And then finally we return that list to the caller.&lt;/p&gt;
&lt;h2&gt;Deleting messages&lt;/h2&gt;
&lt;p&gt;Deleting a message from an IMAP server involves two steps: copying it to the trash folder and calling &lt;code&gt;expunge()&lt;/code&gt; to remove it from its original folder. With Gmail, there are two "deleted" folders: Trash and Deleted. The first of these is like the Recycle Bin / Trash folder on your computer. Messages in Trash are easily recoverable. Depending on your mail provider, messages moved to Deleted could be fully removed. Google never deletes anything, even when you empty the trash. You can always do a search to find messages in the Deleted folder (but you'll need to know a subject or sender or some other criterion to find it).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;delete_message()&lt;/code&gt; method requires a message ID and will then move that message to the Trash (default behavior) or delete the message based on the value of &lt;code&gt;move_to_trash&lt;/code&gt; parameter you supplied when instantiating the ImapClient object.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;While the class is a bit involved, it really only scratches the surface of IMAP message complexity. Still, this class makes for a fairly simple means to grab messages from an email inbox and read their plaintext representations. I hope it helps clarify how IMAP works and gives you a good starting point for your email needs.&lt;/p&gt;</content><category term="Python"></category><category term="python"></category></entry><entry><title>Streaming birdcam with a Raspberry Pi and picamera</title><link href="https://www.timpoulsen.com/2018/pi-birdcam.html" rel="alternate"></link><published>2018-05-04T00:00:00-04:00</published><updated>2018-05-04T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-05-04:/2018/pi-birdcam.html</id><summary type="html">&lt;p&gt;My darling wife got me a Raspberry Pi 3B+ and a NOIR Pi camera for my birthday so that I could set up a streaming camera to monitor our nesting bluebirds. Here's how I did it.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2018/what_the_camera_sees.png" width="320" align="right" title="What the camera sees"&gt;My first attempt was to use the Motion program as &lt;a href="https://hackernoon.com/spy-your-pet-with-a-raspberry-pi-camera-server-e71bb74f79ea" target="_blank"&gt;described by Hackernoon&lt;/a&gt;. It …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My darling wife got me a Raspberry Pi 3B+ and a NOIR Pi camera for my birthday so that I could set up a streaming camera to monitor our nesting bluebirds. Here's how I did it.&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2018/what_the_camera_sees.png" width="320" align="right" title="What the camera sees"&gt;My first attempt was to use the Motion program as &lt;a href="https://hackernoon.com/spy-your-pet-with-a-raspberry-pi-camera-server-e71bb74f79ea" target="_blank"&gt;described by Hackernoon&lt;/a&gt;. It worked, but the resulting video stream was very slow and jerky. I'd say I was getting only a few frames a second at best. I knew I could do better.&lt;/p&gt;
&lt;h2&gt;Picamera&lt;/h2&gt;
&lt;p&gt;The better solution turned out to be &lt;a href="https://picamera.readthedocs.io" target="_blank"&gt;picamera&lt;/a&gt;. This is a Python module for using the Raspberry Pi camera. The author has not only written a fantastic library, but he's also provided lots of great recipes (sample scripts) showing how to use it.&lt;/p&gt;
&lt;p&gt;His &lt;a href="https://github.com/waveform80/pistreaming/" target="_blank"&gt;pistreaming&lt;/a&gt; recipe looked to be perfect for my needs. Raspbian Stretch comes with Python installed. So I jumped right into the pistreamer install steps described on the &lt;a href="https://github.com/waveform80/pistreaming/" target="_blank"&gt;pistreaming GitHub site&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The setup was quite simple and within just a few minutes I had the script running. It streamed much faster than Motion. I just needed to get this streaming across the web and I would be good to go.&lt;/p&gt;
&lt;h2&gt;Houston, we have a problem&lt;/h2&gt;
&lt;p&gt;I hit a little snag at this point. While I could access the stream just fine from the Pi itself, I could not do so from another computer on my local network. I found that I could access the pistreaming page if I connected the Pi to my network over Ethernet. It had to be something with my wifi connection.&lt;/p&gt;
&lt;p&gt;Eventually I discovered that my Asus router or the Tomato firmware I run on it was blocking Wireless Multicast Routing, meaning that it was not allowing RTP/RTSP streaming between wifi nodes. Once I enabled that option, I could access the stream from my laptop.&lt;/p&gt;
&lt;h2&gt;Streaming to the internet&lt;/h2&gt;
&lt;p&gt;Streaming to the internet involved a couple of changes. First, I had to enable Port Forwarding in my router's admin tool:&lt;/p&gt;
&lt;p&gt;&lt;img alt="port forwarding in Tomato" src="../images/2018/portforwarding.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;strong&gt;Note:&lt;/strong&gt; Make sure you use the IP addresses of your Pi, not those in my screenshot. You'll also notice that I set a custom port for the stream (8888 rather than the default 8082). Make sure you use the port your copy of pistreaming is set up to use.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;According to a closed issue on the pistreaming repo, I needed to modify the script's index.html file. That file includes a hard-coded LAN address for the stream's address. It needs to be modified to use your public IP, which is the one your ISP gives you. You can do a Google search for "what's my IP" to find that address.&lt;/p&gt;
&lt;p&gt;Then, update index.html, changing the &lt;code&gt;var client&lt;/code&gt; line near the bottom like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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;// change the var client line (at the bottom) to be:&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&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="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ws://your_external_ip_address:${WS_PORT}/&amp;#39;&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;Make sure you have the quotes right on that. There are few quotes in there to start and you end up with two single quotes around everything between the parentheses.&lt;/p&gt;
&lt;h2&gt;Streaming on startup&lt;/h2&gt;
&lt;p&gt;I wanted the streaming server to start up automatically when the Pi booted up. To do this, I modified /etc/rc.local to run the server.py script. Keep in mind that scripts run by system processes like this don't have access to the same environment as you would have at a terminal prompt. When auto-running scripts like this, you have to use full (absolute) paths to python and your script.&lt;/p&gt;
&lt;p&gt;Here are my steps for this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, I found the path to python with &lt;code&gt;which python3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Then, I opened the file in nano (&lt;code&gt;sudo nano /etc/rc.local&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;At the end of the file, right before the &lt;code&gt;exit 0&lt;/code&gt; line, I added:&lt;br/&gt;&lt;code&gt;/path/to/python3 /home/pi/pistreaming/server.py &amp;amp;&lt;/code&gt;&lt;br/&gt; Note that the &lt;code&gt;&amp;amp;&lt;/code&gt; at the end runs the script in the background so that it will keep running once rc.local exits.&lt;/li&gt;
&lt;li&gt;Then, &lt;code&gt;Ctrl + o&lt;/code&gt; to save and &lt;code&gt;Ctrl + x&lt;/code&gt; to exit nano.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I had one more change to make, again related to absolute paths. I had to modify server.py so it could find the index.html and jsmpg.js files when run by rc.local. The relative paths it used didn't work until I updated the &lt;code&gt;class StreamingHttpServer&lt;/code&gt; class definition follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StreamingHttpServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init&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="c1"&gt;# I added this line&lt;/span&gt;
        &lt;span class="n"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StreamingHttpServer&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="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTP_PORT&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;StreamingHttpHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# then modified this next line&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&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;cwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;index.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&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;index_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&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="c1"&gt;# this one too&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&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;cwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jsmpg.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&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;jsmpg_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And with that, I was done. A quick reboot of the Pi and a check and sure enough it was streaming away.&lt;/p&gt;
&lt;h2&gt;Lighting the way&lt;/h2&gt;
&lt;p&gt;The inside of a birdbox is pretty dark, even during the day. From what I could find in the literature, birds see wavelengths between roughly 300 to 700 nm. That would mean a 940 nm IR LED would be outside their vision range but it would be visible to my NOIR (no infrared filter) Pi camera.&lt;/p&gt;
&lt;p&gt;I used a 100 ohm resister and wired up the LED to &lt;a href="https://pinout.xyz/" target="_blank"&gt;pins 6 (ground) and 12 (Broadcom pin 18)&lt;/a&gt;. I chose this pin because it's the PWM pin, which means if the LED is too bright, I can dim it by implementing pulse width modulation in a script rather than having to solder in a bigger resister.&lt;/p&gt;
&lt;p&gt;Python on a Raspberry Pi comes with the &lt;code&gt;RPi.GPIO&lt;/code&gt; library for accessing the pins. I happen to be using Berryconda (the Anaconda distribution for the Pi) which doesn't come with the library. But, it was a simple install with &lt;code&gt;pip install RPi.GPIO&lt;/code&gt;. Here's my dead-simple script for turning on the LED.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;RPi.GPIO&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;GPIO&lt;/span&gt;

&lt;span class="n"&gt;ledPin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;  &lt;span class="c1"&gt;# Pi pin 12 is Broadcom pin 18&lt;/span&gt;

&lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setmode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BCM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Use Broadcom pin-numbering mode&lt;/span&gt;
&lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ledPin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# LED pin set as output&lt;/span&gt;
&lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ledPin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HIGH&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;I saved that as led.py in my home directory. I updated rc.local to also run this script after starting the streaming server.&lt;/p&gt;
&lt;h2&gt;Focusing the camera&lt;/h2&gt;
&lt;p&gt;Finally, I had to adjust the focus of the camera. A bluebird box is roughly 9 inches tall and the nests are usually a few inches of packed grass and sticks. By default, the Pi camera is not in focus that close. But, you can change that.&lt;/p&gt;
&lt;p&gt;To avoid breaking the camera lens assembly right off the board, you'll want to hold the square frame of the camera lens with one set of pliers. Then, use a pair of needle nose pliers to turn the inner lens to focus. I focused while watching the live stream until it was in focus at roughly 6 or 7 inches away. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Focusing the Pi cam" src="../images/2018/focusing_pi_camera.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Update 1&lt;/h2&gt;
&lt;p&gt;Sadly, the nest box that had four bluebird eggs is now empty. Something (a snake, raccoon, house sparrow, grackle, etc.) raided the box and took the eggs. I found a partial shell about 30 feet from the box, so I suspect another bird. As sad as it is, nest raiding is unfortunately common. &lt;/p&gt;
&lt;p&gt;As luck would have it, we have a nest with eggs in the next box over. We think it is a Carolina Wren nest based on the shape, building materials, and egg coloration. Unfortunately, Carolina Wrens make deep nests such that there's no way to mount the camera inside the box like I'd do with bluebirds. &lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2018/camera_housing.jpg" width="320" title="My high tech housing"&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;I went with a rather high-tech housing for the Pi, camera, and power supply. Everything fit inside an empty plastic nut container. I Gorilla-taped it to a metal garden stake. Ugly, but it works.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;
&lt;img src="../images/2018/camera_aiming.jpg" width="320" title="Aiming at the house"&gt;&lt;/p&gt;
&lt;p&gt;And aimed it at the house. We can watch the wrens come and go, which is definitely not as cool as seeing inside the box.&lt;/p&gt;
&lt;h2&gt;Update 2&lt;/h2&gt;
&lt;p&gt;The damn sparrows got the wrens too. Fortunately, a bluebird pair nested in another nearby box on our property. So far, they're safe and happily watching their eggs. Here's a brief look inside (recorded, not live).&lt;/p&gt;
&lt;p&gt;&lt;img src="../images/2018/bluebirdcam.gif" width="320" title="Animated gif of mama bluebird tending her eggs"&gt;&lt;/p&gt;
&lt;p&gt;Love them &lt;i class="fa fa-twitter blue"&gt;&lt;/i&gt;!&lt;/p&gt;</content><category term="Making"></category><category term="raspberry pi"></category><category term="making"></category><category term="python"></category></entry><entry><title>Acquiring images with OpenCV</title><link href="https://www.timpoulsen.com/2018/acquiring-images.html" rel="alternate"></link><published>2018-04-16T00:00:00-04:00</published><updated>2018-04-16T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-04-16:/2018/acquiring-images.html</id><summary type="html">&lt;p&gt;To manipulate an image, the first thing you must do is open it. OpenCV lets you open images and videos from files and cameras, both locally attached and on the network. Let's check it out.&lt;/p&gt;
&lt;h2&gt;Still images&lt;/h2&gt;
&lt;p&gt;We'll start with the simple case of still images. This is probably best …&lt;/p&gt;</summary><content type="html">&lt;p&gt;To manipulate an image, the first thing you must do is open it. OpenCV lets you open images and videos from files and cameras, both locally attached and on the network. Let's check it out.&lt;/p&gt;
&lt;h2&gt;Still images&lt;/h2&gt;
&lt;p&gt;We'll start with the simple case of still images. This is probably best illustrated with some sample code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cv2&lt;/span&gt;

&lt;span class="c1"&gt;# read an image from the current folder&lt;/span&gt;
&lt;span class="n"&gt;image&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;&amp;#39;cat.jpg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# or from another folder&lt;/span&gt;
&lt;span class="n"&gt;image&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;&amp;#39;images/cat.jpg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# then we&amp;#39;ll just show it in an OpenCV window&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Photo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&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;In the above example, the &lt;code&gt;image&lt;/code&gt; variable is a binary object representing the RGB data of your file. Keep in mind that internally, OpenCV represents these "backwards" as BGR images. That will matter if you use a different library, such as matplotlib, in your script which expects RGB image data.&lt;/p&gt;
&lt;p&gt;OpenCV supports the most common image file formats, including JPG, PNG, WebP, TIF, and more.&lt;/p&gt;
&lt;h3&gt;Errors and exception handling&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;imread&lt;/code&gt; method does not raise a proper exception in the case the file doesn't exist. So, you can't wrap your code in a try/except block to handle errors. Instead, you'll need to check before calling &lt;code&gt;imread&lt;/code&gt; with Python's usual file-handling functions, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cat.jpg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;image&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;&amp;#39;cat.jpg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&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;However, just because a file exists and can be opened by &lt;code&gt;imread&lt;/code&gt; does not mean that you can successfully read image data from it. In such cases, the function will return &lt;code&gt;None&lt;/code&gt; which you should test for:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="n"&gt;image&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;&amp;#39;not_an_image.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Could not read image data&amp;#39;&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;Note: OpenCV determines the file type by its contents, not its extension. So, if you renamed cat.jpg to cat.txt, the &lt;code&gt;imread()&lt;/code&gt; function would still be able to read it as a JPG file.&lt;/p&gt;
&lt;h2&gt;Video files and cameras&lt;/h2&gt;
&lt;p&gt;OpenCV lets you read video data from files and cameras. What's better, you use the same technique no matter the video source. First, you get a reference, or handle to the video source. Then, you read from that source frame by frame.&lt;/p&gt;
&lt;p&gt;Strictly speaking, you use the &lt;code&gt;cv2.VideoCapture.open()&lt;/code&gt; method to get a reference to your video source. However, OpenCV offers a shorter equivalent in the &lt;code&gt;cv2.VideoCapture()&lt;/code&gt; method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;VideoCapture(source[, options])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Where &lt;em&gt;source&lt;/em&gt; is one of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an integer, representing which locally attached webcam to read from. A value of &lt;code&gt;0&lt;/code&gt; represents your built-in webcam if available.&lt;/li&gt;
&lt;li&gt;a string, representing a path to a local video file&lt;/li&gt;
&lt;li&gt;a string, representing the URL to a streaming network camera, such as an IP camera&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Typically, you don't need to provide any options for the method. These flags would be used to specify the format of the data stream and other specifics. As with still images, OpenCV can typically figure out the video parameters automatically by examining the input data.&lt;/p&gt;
&lt;p&gt;Here is a very typical loop used to read frames from a video source. In this simple example, we just show each frame in an OpenCV window. Within each loop, the script checks for a key-press and if the letter "q" was typed, we break out of the loop, which in this example would release the video source and end the program:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="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;/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="n"&gt;camera&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;VideoCapture&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="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# loop continuously reading frame-by-frame&lt;/span&gt;
    &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;camera&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# a frame was successfully read&lt;/span&gt;
        &lt;span class="c1"&gt;# we&amp;#39;ll just show it in a window&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Live Video&amp;quot;&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="c1"&gt;# if the &amp;#39;q&amp;#39; key is pressed, stop the loop&lt;/span&gt;
        &lt;span class="k"&gt;if&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;waitKey&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="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;q&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;camera&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# frame wasn&amp;#39;t read, handle that problem, for example&lt;/span&gt;
        &lt;span class="n"&gt;camera&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;break&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;h3&gt;Errors and exceptions&lt;/h3&gt;
&lt;p&gt;As with reading still images, OpenCV does not offer proper exception handling when reading from a video source. If you specify a source that doesn't exist, you'll get an error like 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="n"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bound&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&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;opencv&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;camera&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;properly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initialize&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(To create the preceding error, I used &lt;code&gt;camera = cv2.VideoCapture(1)&lt;/code&gt; and since my laptop has just one camera, camera &lt;code&gt;1&lt;/code&gt; doesn't exist.)&lt;/p&gt;
&lt;p&gt;It's important to release the video source properly when you're done by calling the &lt;code&gt;release()&lt;/code&gt; method. Failing to do so can leave your camera unusable. Below is an example of the error you get attempting to use the built-in camera on a Mac that was not released properly before.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nl"&gt;OpenCV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;AVCaptureDeviceInput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initWithDevice&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nl"&gt;OpenCV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cannot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FaceTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Camera&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Built&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nl"&gt;OpenCV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;camera&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;properly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you end up in this state, you may have to restart your computer to recover. On the Mac, you can use the following command rather than restarting:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo killall VDCAssistant
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;There you have it, the ins-and-outs of aquiring image data to use with OpenCV.&lt;/p&gt;</content><category term="OpenCV"></category><category term="opencv"></category><category term="python"></category></entry><entry><title>OpenCV Basics</title><link href="https://www.timpoulsen.com/2018/opencv-basics.html" rel="alternate"></link><published>2018-04-15T00:00:00-04:00</published><updated>2018-04-15T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-04-15:/2018/opencv-basics.html</id><summary type="html">&lt;p&gt;OpenCV is a library of computer vision functions available for C++, Java, Python, and more on Windows, Linux, OS X, Android, and iOS. The OpenCV project began way back in 1999 and has been continually expanded and improved since then.&lt;/p&gt;
&lt;p&gt;This article and the others on this site focus on …&lt;/p&gt;</summary><content type="html">&lt;p&gt;OpenCV is a library of computer vision functions available for C++, Java, Python, and more on Windows, Linux, OS X, Android, and iOS. The OpenCV project began way back in 1999 and has been continually expanded and improved since then.&lt;/p&gt;
&lt;p&gt;This article and the others on this site focus on OpenCV-Python. As the OpenCV site puts it, "&lt;em&gt;OpenCV-Python is a library of Python bindings&lt;/em&gt;" for OpenCV. What they mean by that is the core functionality of OpenCV is performed by compiled C++ code (so its fast) yet is programmed from Python (which means it's easier to use).&lt;/p&gt;
&lt;h2&gt;What can you do with OpenCV?&lt;/h2&gt;
&lt;p&gt;Here's a short list of what's possible with OpenCV:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read an image from a file, a USB web cam, an IP cam, and more&lt;/li&gt;
&lt;li&gt;Convert an image to another color space: RGB, HSV, LAB, YCrCb, grayscale, etc.&lt;/li&gt;
&lt;li&gt;Blur or sharpen an image&lt;/li&gt;
&lt;li&gt;Add noise to or remove noise from (denoise) an image&lt;/li&gt;
&lt;li&gt;Resize, rotate, warp, and adjust perspective of an image&lt;/li&gt;
&lt;li&gt;Extract portions of an image&lt;/li&gt;
&lt;li&gt;Find the edges of objects in the image&lt;/li&gt;
&lt;li&gt;Get the outlines (contours) of objects in the image and find the dimensions, area, and more about those contours&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, you can do all the above operations on a video source too, with both files and video streams.&lt;/p&gt;
&lt;h2&gt;Installing OpenCV&lt;/h2&gt;
&lt;p&gt;For Python environments, installing OpenCV is pretty simple. You'll need to choose between installing just OpenCV or the contributed modules as well. The "contrib" modules are extra functions provided by the community. Popular and well-written contrib modules sometimes get pulled into the core OpenCV distribution. However, not all of them are error free or fully optimized. &lt;/p&gt;
&lt;p&gt;To install the core OpenCV Python version:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install opencv-python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To install the version containing the contrib modules:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install opencv-contrib-python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(The above commands will work just fine if you're using the Anaconda Python distribution, which I recommend you use.)&lt;/p&gt;
&lt;p&gt;Mac users can also install OpenCV using &lt;a href="http://brew.sh/" target="_blank"&gt;Homebrew&lt;/a&gt; and Linux users can sometimes use their package manager (e.g. apt-get). You can even &lt;a href="/compiling-opencv.html" target="_blank"&gt;compile OpenCV from its source code version&lt;/a&gt;. You would do this to get the newest version or to get a version tailored to your specific computer.&lt;/p&gt;
&lt;h2&gt;Learning to use OpenCV&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://docs.opencv.org/" target="_blank"&gt;OpenCV docs&lt;/a&gt; are not particularly beginner-friendly. Fortunately, there are many great resources for learning OpenCV. A few sites I recommend you check out are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The official &lt;a href="https://docs.opencv.org/3.4.1/d6/d00/tutorial_py_root.html" target="_blank"&gt;OpenCV Python tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pyimagesearch.com" target="_blank"&gt;www.pyimagesearch.com &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.learnopencv.com" target="_blank"&gt;www.learnopencv.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And of course, check back here too. In future articles on this site, I will cover how you can use OpenCV to perform some interesting and fun operations on images and video.&lt;/p&gt;</content><category term="OpenCV"></category><category term="opencv"></category><category term="python"></category></entry><entry><title>Build OpenCV and install from source</title><link href="https://www.timpoulsen.com/2018/build-opencv-and-install-from-source.html" rel="alternate"></link><published>2018-03-27T00:00:00-04:00</published><updated>2018-03-27T00:00:00-04:00</updated><author><name>Tim Poulsen</name></author><id>tag:www.timpoulsen.com,2018-03-27:/2018/build-opencv-and-install-from-source.html</id><summary type="html">&lt;p&gt;(Originally posted at skypanther.com)&lt;/p&gt;
&lt;p&gt;I wanted to set up the latest OpenCV version on my Mac. I found various instructions, but few that applied specifically to my setup — Mac OS X High Sierra running Anaconda. What follows are my steps to compile and install the latest OpenCV version onto …&lt;/p&gt;</summary><content type="html">&lt;p&gt;(Originally posted at skypanther.com)&lt;/p&gt;
&lt;p&gt;I wanted to set up the latest OpenCV version on my Mac. I found various instructions, but few that applied specifically to my setup — Mac OS X High Sierra running Anaconda. What follows are my steps to compile and install the latest OpenCV version onto my Mac.&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;You’ll need a few things first…if you don’t have these, set them up now.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Xcode and its command-line tools&lt;/li&gt;
&lt;li&gt;A working Python 3.x environment (hint: use Anaconda)&lt;/li&gt;
&lt;li&gt;Homebrew&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you have those installed, you’re ready to start. You’ll need to install some pre-requisite packages. Each of the following is a separate command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew install cmake pkg-config
brew install jpeg libpng libtiff openexr
brew install eigen tbb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, you’ll need the OpenCV source code. Clone OpenCV and OpenCV_contrib:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cd ~
git clone https://github.com/opencv/opencv
git clone https://github.com/opencv/opencv_contrib
cd ~/opencv
mkdir build
cd build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next you’ll configure &lt;code&gt;make&lt;/code&gt; with the following command. All of this is one big command, split over multiple lines, and yes it does end with two periods. You can just copy it all and paste in your terminal.&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;cmake&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;CMAKE_BUILD_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RELEASE&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;CMAKE_INSTALL_PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;import sys; print(sys.prefix)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;OPENCV_EXTRA_MODULES_PATH&lt;/span&gt;&lt;span class="o"&gt;=~/&lt;/span&gt;&lt;span class="n"&gt;opencv_contrib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;PYTHON3_INCLUDE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;from distutils.sysconfig import get_python_inc; print(get_python_inc())&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;PYTHON_PACKAGES_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;from distutils.sysconfig import get_python_lib; print(get_python_lib())&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;PYTHON3_EXECUTABLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;python3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;BUILD_opencv_python2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OFF&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;BUILD_opencv_python3&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ON&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;INSTALL_PYTHON_EXAMPLES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OFF&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;INSTALL_C_EXAMPLES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OFF&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;BUILD_TESTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OFF&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;BUILD_PERF_TESTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OFF&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;WITH_CUDA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OFF&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;WITH_FFMPEG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ON&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;ENABLE_PRECOMPILED_HEADERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OFF&lt;/span&gt; \
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;BUILD_EXAMPLES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OFF&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That command will take a while...after which you can do the actual build:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;make -j4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That will take even longer; depending on the speed of your system it could take an hour or longer. Once it’s all done, you’re ready to actually install the compiled project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo make install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That will copy all the files to the right spot on your system. At this point, you’re pretty much done. I found a few guides that suggested that you need to rename (or copy or link) one of the .so files that was copied to your python lib folder.&lt;/p&gt;
&lt;p&gt;Rather than digging for the location of that folder, what the first command below does is ask python to find that folder for you and pass it to the &lt;code&gt;cd&lt;/code&gt; command:&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;cd&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;from distutils.sysconfig import get_python_lib; print(get_python_lib())&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cp&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;cpython&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;darwin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;so&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;so&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, you’re done and ready test your work:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="n"&gt;Python&lt;/span&gt; &lt;span class="mf"&gt;3.6.3&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;Anaconda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="o"&gt;.|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Oct&lt;/span&gt;  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="mi"&gt;2017&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GCC&lt;/span&gt; &lt;span class="mf"&gt;4.2.1&lt;/span&gt; &lt;span class="n"&gt;Compatible&lt;/span&gt; &lt;span class="n"&gt;Clang&lt;/span&gt; &lt;span class="mf"&gt;4.0.1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;RELEASE_401&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;darwin&lt;/span&gt;
&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;help&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;copyright&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;credits&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;license&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt; &lt;span class="n"&gt;information&lt;/span&gt;&lt;span class="o"&gt;.&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="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__version__&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;3.4.1-dev&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That’s it, OpenCV is installed!&lt;/p&gt;</content><category term="OpenCV"></category><category term="python"></category><category term="opencv"></category><category term="anaconda"></category></entry></feed>