Merlin Bird ID

Jan 23, 2021

One of the tools I've been using in my backyard wildlife identification efforts is the Merlin Bird ID app (I'm using the Android version). This app is aptly named, because the ID capabilities are like some sort of dark magic!

There are a fair number of useful features in this app, none more so than the photograph identification tool. You simply upload a photo of the bird you want to identify, select the date and location where you spotted the bird, and the app gives you a list of possible birds. Each entry has information on the bird itself, and a number of excellent photos to compare your candidate against. In most of the trial cases I've given the app, only two or three candidates are usually returned, a testament to just how smart the "brains" of this thing are (all of its recommendations for my photos have been spot on so far).

Another feature that I haven't used much yet is the bird-call feature. While examining details on a bird, you can listen to a number of its calls, which makes identification by ear a lot easier. I cannot recommend this app highly enough!

Backyard Wildlife

Jan 17, 2021

We've had a bird feeder in our backyard for a few years now, but I've only ever half-heartedly watched the birds that come to it. This year for Christmas, my wife requested (and received) the addition of a suet feeder. Between the cold of winter, the never ending lock-down of this god forsaken pandemic, and my general boredom, I've been spending a lot more time actually watching the birds that visit our backyard. More importantly, I've also been photographing these birds, as well as other wildlife we see around our house. This Backyard Wildlife album is the result. As an aside, this is the first public photo album I've published since 2019!

I can emphatically say that I now enjoy watching and identifying the birds that visit. Prior to this endeavor, if you had asked me how many different types of birds come to our feeder, I would have guessed 10, maybe 12. As of this writing, I have photographed 26 different species of birds in and around our house. I've seen a few more which have yet to be photographed (some birds, it turns out, are fairly difficult to shoot). As a result, this album will be a living album; I plan on adding to it as I shoot new pictures.

My self-imposed criteria for this photo album is that all photos must be taken from the area immediately surrounding my house. There will be no duplicate species photos (with the exception of variants by sex), and I will replace photos over time with improved versions as I am able (a few photos are fairly rough, due to the birds' ephemeral visits). If you have a bird feeder in your yard, take the time to watch the birds that come. You'll be surprised at what's right in your backyard! If you don't have a feeder, be sure to get one; it's great, cheap fun.

Here are a couple of teaser photos from the album:

I've been doing web development in some form or fashion since 1999 (as an aside, the Wayback Machine even has a snapshot of one of my old websites; what a world)! I probably started picking up JavaScript way back in the early 2000s, as my web developing knowledge improved. Since web browsers are generally really good at supporting the old way of doing things, my knowledge of JavaScript has been pretty stagnant for a long time.

Not too long ago, I stumbled upon The Modern JavaScript Tutorial, a terrific resource for learning how to do things the modern way. I'm working my way through reading it, even taking the time to go back over the basics. I've already learned a lot; some of what I've been doing has apparently been deprecated for a while now, which was interesting to learn.

I've also learned about features I hadn't seen before (the nullish coalescing operator being one of those). I recommend it if, like me, you're still living in the dark ages.

The Carnyx

Jan 5, 2021

I stumbled upon the following museum lecture from 2015 on YouTube, highlighting the carnyx, a Celtic war horn that was used thousands of used ago (and only recently reconstructed!). If you have 45 minutes and an interest in either history or music, I highly recommend watching.

One of my personal goals for 2021 is to increase my writing frequency here at this website. A little over 14 years ago (!) I did some analysis on my posting habits, and I thought I'd do something similar to see how my posting has changed over time. Using a quick Python script and matplotlib, the latter of which I don't use frequently enough to ever remember how to use, I came up with the following two diagrams:

Both graphs show how, in recent years, my posting frequency has really dropped off a cliff (having a kid will do that to you, I guess). Even prior to kids, however, I was posting much less frequently than I did in the beginning.

Now that I have a new publishing platform, posting has gotten easier for me. My personal goal is to post at least three times a month, which should put me on par with 2011, which is the last time I averaged that total.

Python showPath Script

Dec 26, 2020

I occasionally have a need to either view the PATH environment variable from the command line, or search the PATH for something. I wrote a small Python script to make this easy to do, adapting it from an old Perl script I wrote years ago. The script, in its entirety, is shown below (you can also download it here; just save it as a Python script). Note that this script currently has a Windows focus, but could easily be adjusted to work in Linux too.

When used by itself, the script will simply pretty-print all of the paths currently in your environment's PATH. You can pass a --sort option to sort the output, or you can supply a needle to search for. Hopefully someone else will find this as useful as I do.

import argparse
import os

parser = argparse.ArgumentParser()
parser.add_argument('searchterm', default='', nargs='?')
parser.add_argument('--sort', action='store_true', default=False,
                    help='Print PATH in a sorted form')

args = parser.parse_args()

path = os.getenv('PATH').split(';')

if args.sort:
    path = sorted(path)

if args.searchterm:
    needle = args.searchterm.lower()
    print(f"\nSearching PATH for {needle}")

    matches = []
    for p in path:
        if needle in p.lower():

    if matches:
        print(f"Found {len(matches)} result{'' if len(matches) == 1 else 's'}")
        for m in matches:
        print(f"Unable to find {needle} in PATH")
    print("\nShowing PATH:\n")
    for p in path:

Django 3.1 now uses the Python pathlib module for its internal paths. This change caught me off guard when I started developing with it, as I was used to the old os.path way of doing things. Here's a look at the old way and its newer counterpart:

# Old way
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# New way
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent

Joining paths together uses a very different mechanism as well:

# Old way
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

# New way

# Alternate new way (which I prefer)
STATIC_ROOT = BASE_DIR.joinpath('static')

These new mechanisms feel so different since they treat paths as objects, not strings. This works well, however, since paths aren't really strings in the first place. Transitioning to this new way of thinking is taking me some time, since prior to this module, every path in Python was treated as a string. However, I can already see the utility of this module, especially when it comes to resolving relative paths.


Dec 21, 2020

We got rid of our Better Homes and Gardens cookbook, so I'm transcribing one of its recipes here so we can remember it. This makes pretty good hummus, great with pita bread or pita chips.


  • 1 15-ounce can garbanzo beans (chickpeas), rinsed and drained
  • 1 clove garlic, minced
  • 1/4 cup tahini
  • 1/4 cup lemon juice
  • 1/4 cup olive oil
  • 1/2 tsp salt
  • 1/4 tsp paprika


  1. In a blender or food processor, combine garbanzo beans, garlic, tahini, lemon juice, olive oil, salt, and paprika. Cover and blend until smooth, scraping the sides as necessary.
  2. If desired, add one or more stir-ins of your choice (e.g. sliced green onions, roasted red peppers, etc.).

Makes about 1-3/4 cups.

The dependency resolver in Python's pip command was recently updated in version 20.3. This fundamental change has a number of improvements, but I discovered today a serious drawback of this new machinery. Using the previous resolver, pip allowed you do the following to discover what versions of a package were available:

pip install markdown==

This command provided output like the following:

ERROR: Could not find a version that satisfies the requirement markdown==
(from versions: 1.7, 2.0, 2.0.1, 2.0.2, 2.0.3, 2.1.0, 2.1.1, 2.2.0, 2.2.1,
2.3, 2.3.1, 2.4, 2.4.1, 2.5, 2.5.1, 2.5.2, 2.6, 2.6.1, 2.6.2, 2.6.3, 2.6.4,
2.6.5, 2.6.6, 2.6.7, 2.6.8, 2.6.9, 2.6.10, 2.6.11, 3.0, 3.0.1, 3.1, 3.1.1,
3.2, 3.2.1, 3.2.2, 3.3, 3.3.1, 3.3.2, 3.3.3)
ERROR: No matching distribution found for markdown==

This trick was often useful to discover what new versions (if any) of required packages are available. Sadly, the new machinery no longer produces output like the above. Instead, all you get is this entirely unhelpful message:

ERROR: Could not find a version that satisfies the requirement markdown==
ERROR: No matching distribution found for markdown==

An open bug in the pip project is tracking this issue, but most of the developer responses so far have been of the "we don't have the funding to fix this" variety. There are a number of recommended solutions in the ticket, none of which seem as simple as the previous trick. Hopefully this is something that can be prioritized and fixed soon.

During the deploy process of my new web platform, I ran into a really puzzling situation. I had my application deployed per DreamHost's documentation, but I kept getting the standard domain landing page (" is almost here!"). This deploy was set up exactly like several other sites I had working successfully, but this one just wasn't working.

I ended up having to reach out to DreamHost support (which is fantastic, by the way), and one of their technicians helped me figure out the issue. Prior to deploying this new Django-powered platform, I was running with WordPress. While running in that environment, I had enabled HTTPS support through the Let's Encrypt service, something DreamHost offers for free. The HTTPS service was configured with a path that no longer applied, seeing as my new folder structure is different from the old WordPress setup.

There are two ways the pathing issue can be fixed at DreamHost:

  1. You can reach out to the support team to have them ensure that the HTTPS service is pointing at the right location.
  2. You can remove the HTTPS certificate from the domain, and request a new one. The path will be updated as a byproduct of this change.

Hopefully this tip will help someone else out there, as I spent several frustrating hours debugging this issue.

Mark Shields

Dec 18, 2020

I don't watch the news, and I generally despise politics. However, I routinely make time each week to watch Shields and Brooks on the PBS NewsHour. Mark Shields and David Brooks both seem like people I would enjoy knowing in real life. They are intelligent, thoughtful, and they clearly respect each other. It is so refreshing to hear two people disagree about things in a calm, respective manner.

It's sad news, then, that Mark is stepping down after 33 years. I'll really miss his insights and his funny one-liners. Here's a short video that the NewsHour put together to honor his work over the years.

Spam Mitigation

Dec 18, 2020

One of my primary concerns in writing my own web publishing platform was how to manage spam. In the WordPress world I used both Akismet, as well as another third-party plugin, to keep things under control. In looking around at various options, I stumbled upon a terrific article at Ned Batchelder's blog on how he manages spam.

His technique can prevent both playback bots, as well as form-filling bots, from submitting garbage data. The process is fairly basic:

  1. A timestamp field is inserted as a part of the commenting form.
  2. A spinner field is included, its value being a hash of four key data elements:
    • The timestamp
    • The client's IP address
    • The entry ID of the post being commented on
    • A secret
  3. Field names on the form are all randomized, with the exception of the spinner. The randomization process uses the spinner value, the real field name, and a secret.
  4. Honeypots are scattered throughout the form, and made invisible to humans through CSS.

Once the form data is submitted, valdation occurs to detect whether a bot was present:

  1. The spinner gets read to figure out which form fields match which data.
  2. The timestamp is checked and rejected if it's in too far into the past, in the future, or not present.
  3. The spinner value is checked to ensure it hasn't been tampered with.
  4. Honeypots are checked to see if data is provided in any of them.
  5. The rest of the data is validated as usual.

Ned's article goes further into the details than I have above, so I highly recommend reading it if you're interested in this kind of thing. Time will tell as to whether this technique will be successful at keeping bad comments out, but I'm optimistic that it will.

Dumping WordPress

Dec 17, 2020

Today is the day I dump WordPress for a custom-built, Django-powered platform. The site look and feel has changed, so click through from your RSS reader (if you're reading from one) to see the new look. In addition, comments are now enabled on all posts. I'm using an interesting spam mitigation technique that will hopefully keep things clean. Time will tell how well it works.

I made a few design decisions with this redesign and with the platform itself:

  1. Site search is now simply powered by Google. They do search better than most anyone around, so why reinvent the wheel?
  2. Posts and pages are Markdown-aware by default. Comments accept Markdown too!
  3. I'm using off-the-shelf components from Bootstrap, as well as icons from FontAwesome.
  4. Things should, generally, be responsive and pleasing on mobile.

Working with Django is, generally, a real pleasure. I was able to stand up this platform in just under three weeks time:

Commit graph for this site's platform

If you spot any issues, please let me know; bug reports are a big help. I'm sure there are broken images here and there, especially on some of my older posts. Additional details on this new platform are coming soon, including a pointer to a great article on spam mitigation. I'm hopeful that I will get back into a more frequent writing cadence now that I have a better platform to use.

I Hate WordPress

Dec 11, 2020

The longer I spend with WordPress, the more I loathe it. This site has been steadily chugging along since 2004 (16 years as of this writing!) with the Powered by WordPress brand stamped on each page. In that amount of time, WordPress has become an unwieldy behemoth of a platform. It's honestly feeling more and more like Microsoft FrontPage to me (remember FrontPage?). Everyone should apparently design websites in the WYSIWYG editing style. As hopefully everyone knows, however, that never seems to end well.

Apart from having to update the core software every few weeks to plug the latest security holes, I find the plugin support to be hit or miss. All the higher quality plugins tend to be paid pieces of software, some even requiring monthly subscription fees. Free plugins generally fall into two camps:

  1. They're outdated and unsupported
  2. They're poorly designed/documented

Lots of the functionality I'm interested in — image galleries, Markdown support, spam mitigation, bulk post management, the list goes on — isn't available natively. If it is, the implementation is generally half-baked. Keeping this site running has become a chore as WordPress has gotten larger.

Thankfully, this is where being a professional web developer pays off.

I'm in the process of designing a brand new, custom platform to power this site. It will be powered by Django, and will have every feature I care about baked in. I've been hacking on it solidly for the past two weeks, and I estimate another week or so before it's able to launch. Along with it will come a small visual refresh. I'm excited about these changes, and I look forward to sharing them soon.

Another great philosophical video from Essential Craftsman. If you know someone of an impressionable age, be sure to share it with them.

Railway Maintenance

Sep 21, 2020

I love trains, and I love mechanical automation. This really great video is an intersection between the two, and explains the process used to replace rail on an active railroad. Fascinating stuff!

During these COVID-19 lockdown times of ours, I've been thinking about the interesting research opportunities this strange occurrence must be affording scientists around the globe. For example, in the days following the 9/11 terrorist attacks on New York, weather scientists had a unique chance to study the skies without the influence of aircraft contrails. Certainly those kinds of studies can be done now, since air travel has been greatly reduced. I wonder what other branches of science this shutdown is helping. Are air-pollution studies easier to conduct? Can certain types of infrastructure examinations be completed more easily, without the burden of traffic and congestion? How is electricity use being affected? It's an interesting line of thought to ponder.

This line of thinking, however, goes in a darker direction as well. How many more cases of depression will result from these upcoming months of isolation? Will suicides increase? What about divorce rates? How will children in school be impacted in terms of what they learn? What about their social development?

My daughter, who is younger than 2 years of age, is reaching a critical point in her development as a child. Her isolation from both family members and other children will certainly have a negative impact, at least in the short term. What kind of long term effects will it have? I guess only time will tell.

Marbula One

Mar 1, 2020

I was recently (and randomly) recommended the following video on YouTube, in which marbles race in a Formula One format on surprisingly well constructed tracks. This is the first "race" of the 2020 "season," complete with podiums at the end of each race, "yellow-marble" track cautions (at least one occurs in race 2), and a season-long point system for each team.

I cannot stress how ridiculous this all is, but it's oddly satisfying to watch. Also note that the first 60 seconds of each video can be skipped (the video below starts at the 1:00 mark for your convenience).

I use Python virtual environments a bunch at work, and this morning I finally put together a small helper script, saved as a Gist at GitHub, that makes enabling and disabling virtual environments a lot easier. I'm not sure why I didn't do this a lot earlier. Simply type work to enable the virtual environment, and work off to disable it. This script should be in your PATH, if it's not already obvious.

Here's the script itself:

@echo off

if exist "%cd%\venv" (
    if "%1" == "off" (
        echo Deactivating virtual environment
        call "%cd%\venv\Scripts\deactivate.bat"
    ) else (
        echo Activating virtual environment
        call "%cd%\venv\Scripts\activate.bat"
) else (
    echo No venv folder found in %cd%.

Stopping CVS Robocalls

Jan 23, 2020

In the past few weeks, I've received a couple of calls each day from CVS Pharmacy. I'm not sure what has changed on their end to cause these calls to start, but it has gotten annoying enough that I did some searching on how to disable them. Here's how to do it:

  1. Call 1-800-SHOP-CVS (1-800-746-7287)
  2. Say "More choices" (you may have to say this twice to get to the right menu; I did)
  3. Say "Calls"
  4. The system will confirm your phone number
  5. Say "Stop"
  6. Say "Correct"