Installing OpenCV 4 on Windows 10

I love working with the OpenCV library, but it can be a royal pain in the butt to get it installed and running on your machine. This is especially true for Microsoft Windows. Despite a Google search turning up plenty of results for articles promising to show you how to do it, they all seem to have issues or be incomplete. This results in a rather frustrating experience when you just want to get things up and running.

The purpose of this tutorial is to walk you through, step-by-step, how to install OpenCV version 4 on your Microsoft Windows 10 system. So, let’s quit talking about it and jump right in!

Continue reading…

Let’s Write a Simple Disassembler for the Intel 8080 Microprocessor

Typically my weekends are spent hiking with my camera, but this past weekend I was knocked down by a pretty nasty cold. As a result, I found myself toying around with various programming projects I’ve had laying around. Here for a while, I’ve been curious how emulators work, so I took this opportunity to start venturing down that rabbit hole. During this excursion, I decided to start my foray into emulators by writing one for the game Space Invaders, which ran on an Intel 8080 microprocessor. It seemed only natural to get started by writing a disassembler for the 8080 to get things started.

What I came up with is a pretty simple (albeit long) bit of C code to disassemble binary code compiled for the Intel 8080. So, without further ado, let’s jump on in and see how I did it!

Continue reading…

Easily Detect Changes to All Text Boxes in Windows Form [C#]

There are a number of instances in which we might want to detect when there have been changes to text boxes within a C# Windows Forms app. As an example, I’m working on building a quick application for managing a database of natural sandstone arches within the state of Kentucky. My (very rough-draft) app has the following screen to add an arch to the database:

I want to have a way to detect if there have been changes within any of the text boxes when I hit the cancel button. This way, I can display a confirmation dialog to the user.

One way I could go about this is by individually setting the TextChanged callback for each and every textbox. This would certainly work, but it would lead to a lot of code duplication and things would get messy quite quickly. A better way would be to programmatically set the callback for each text box. This is actually really easy to do!

Step 1: Override the Form’s OnLoad Method

The first thing we need to do is override our form’s OnLoad method. This can be done quite simply:

override protected void OnLoad(EventArgs e)
{

}

Step 2: Programmatically Set TextChanged Callback for All Text Boxes

The next step is to set the TextChanged callback for all our text boxes. We can do this by adding the following to our OnLoad method:

override protected void OnLoad(EventArgs e)
{
	foreach (Control control in this.Controls)
	{
		if (control.GetType() == typeof(TextBox))
		{
			(control as TextBox).TextChanged += Form_TextChanged;
		}
	}
}

All we are doing here is iterating over all the controls in our form. If the control is of the type TextBox, then we set the TextChanged callback to be the Form_TextChanged callback.

Step 3: Build the Callback Function

Now that we have specified a callback for the TextChanged event, we need to actually build the callback function. In this case, I’m going to keep things simple and simply set the value of a boolean property to true if a text change has been detected:

private void Form_TextChanged(object sender, EventArgs e)
{
	this.hasUnsavedChanges = true;
}

Now I can use this boolean property anywhere I want to test for unsaved changes (in my case, when the cancel button is clicked).

Dumping Map Tiles from an MBTiles Database with Python

MBTiles is a database format, developed by Mapbox, for storing tiled data. It’s a relatively simple database format that allows for a convenient, portable way to store map tile data.

Here recently, I’ve been developing code that works with tiled map data, including data contained within an MBTiles database. As part of this, I’ve needed an easy way to dump the map tiles from an MBTiles database to my local disk. It turns out that we can do this quite easily with a little bit of Python, so let’s dig in!

The MBTiles File Format

The format of an MBTiles database is really pretty simple. In fact, it’s nothing but an SQLite3 database that is formatted in a particular way. This database uses the UTF-8 encoding and contains a few tables for storing the data. For our purposes, however, we only care about the Tiles table.

The Tiles Database Table

The tiles database table must be present in an MBTiles database and must contain the following columns:

  • zoom_level : integer
  • tile_column : integer
  • tile_row : integer
  • tile_data : blob

As you can see, the format of the tile data is actually quite simple!

One Gotcha

There is one gotcha here that we need to watch out for.

We are commonly familiar with a tile being identified by its (Z, X, Y) coordinates, where Z is the zoom level, X is the column and Y is the row. So, a tile would be accessed via a URL: Z/X/Y.png. This is not how the data is formatted in the MBTiles database, however.

MBTiles encodes the zoom_level, tile_column, and tile_row according to the Tile Map Service Specification. This way of encoding the data is the same in every way, except with regards to the Y-coordinate. In the TMS way of doing things, the Y-coordinate is reversed from the “XYZ” coordinate system mentioned above. This is done via the following formula:

$$ y = 2z – y – 1 $$

This just means that we need to remember to convert the Y-coordinate in our program.

Writing Some Code

Alright, with some of the theory out of the way, let’s actually jump in and start writing some code. We will start by writing the boring boilerplate code that we don’t care about so much.

Boring Boiler Plate

import argparse
import logging
import os
import sqlite3

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

# Handle command line args
parser = argparse.ArgumentParser(description="A simple utility to extract files from MBTiles")
parser.add_argument("--input", dest="mbtile_path", help="Path to the mbtile file")
parser.add_argument("--output", dest="output_path", help="Directory to dump tiles to")
args = parser.parse_args()

if not args.mbtile_path or not args.output_path:
    logger.error("You must supply an input and output!")
elif not os.path.isfile(args.mbtile_path):
    logger.error("The input file " + args.mbtile_path + " does not exist.")
elif not os.path.exists(args.output_path):
    logger.error("The output path " + args.output_path + " does not exist.")
else:
    logger.info("Dumping tiles for " + args.mbtile_path + " to " + args.output_path)
    dump_tiles(args.mbtile_path, args.output_path)

We’ll walk through this real quick. I’m not going to spend too much time here since this is just the simple boiler-plate code to get the app started.

Lines 1-4 are our imports. We need argparse to parse the command line arguments, logging to output to the console, sqlite3 for reading the MBTiles database (remember, it’s nothing but an SQLite database), and os for working with the files on disk.

On lines 6-7 we set up our logger and then the rest of the code is simply setting up the parsing for the command line arguments. As you can see on line 24, the actual task of dumping the data will be done by the dump_tiles(PATH_TO_MBTILES, PATH_FOR_OUTPUT) function. Let’s go ahead and take a look at that function.

Implementing the Functionality

def dump_tiles(mbtilePath, output_path):
    conn = sqlite3.connect(mbtilePath)
    for row in conn.execute('SELECT * FROM tiles'):
        zoom_level = row[0]
        tile_col = row[1]
        tile_row = row[2]
        tile_data = row[3]
        write_tile(output_path, zoom_level, tile_col, tile_row, tile_data)
    conn.close()

As you can see, our dump_tiles method is pretty straight-forward. On line 1, we open the MBTiles database in an SQLite connection. We then enter a for loop that loops over every entry in the tiles table and store the tile information in some variables (lines 4-8). On line 10, we call another method, write_tile, with all of this tile data passed as parameters. We then close the connection to the SQLite database.

So, with these few lines of code, we’ve managed to fetch all of the tile data from the MBTiles database. As you could probably guess, the tiles are actually written to disk in the write_tile method.

Writing the Tiles to Disk

def write_tile(output_dir, zoom_level, column, row, data):
    row = correct_y_value(row, zoom_level)
    path = os.path.join(output_dir, str(zoom_level), str(column))
    if not os.path.exists(path):
        os.makedirs(path)
    f = open(os.path.join(path, str(row) + ".jpg"), 'w+b')
    binary_fmt = bytearray(data)
    f.write(binary_fmt)
    f.close()

At this point, we are just about done. We’ve gotten the tile data from the database, now it’s time to write it out to disk. Let’s walk through this code to see what’s happening.

On line 2 you will notice that we are reassigning the row value to be the output from the correct_y_value(row, zoom_level) method. This is to correct for the differences in the y-values that we mentioned above. We will take a look at that method in a moment.

In lines 3-5, we are creating the folder structure for the tile. The structure is output_directory/zoom_level/column. Next, on line 7, we create a filehandle to the file row.jpg. We set this handle to write binary. We then convert the raw tile data into a byte array and write that binary information to the filehandle we just created. We finish up by closing the filehandle.

Fixing the Y-Value

This leaves us with just one last thing to look at, the correct_y_value(row, zoom) method.

def correct_y_value(y, zoom):
    y_max = 1 << zoom
    return y_max - y - 1

All this method is doing is flipping the Y-value, as described in the section above.

That’s It!

That’s all there is to it! With just around 50 lines of python, we’ve managed to create a command-line application that allows us to dump tile data contained in an MBTiles database to disk. It’s worth mentioning that there are a few things we could do to make this utility more robust. For one, tile data could be either JPG or PNG. In our example, we are just assuming it’s stored as JPG data. It would be useful if our utility auto-detected which format to use, but I’ll save that as a future exercise.

CS Review – Binary Numbers

Welcome to what will be the first in an ongoing series aimed at reviewing computer science topics. Today we will be diving into binary number representation. Understanding binary is an important step in gaining a better understanding of how the computer works on a lower level.

Recall that a computer is made up a whole bunch of integrated circuits, which are in turn made up of a bunch of transistors. These transitors can have one of two states: on or off. We represent these states as a 1 or a 0, respectively. This means that a computer can only work with two different values, 1s and 0s. This is where binary comes in.

Quick Review of Number Systems

Binary is nothing more than a number system. To better understand this let’s just take a moment to review number systems.

The system you and I are used to counting in as a part of everyday life is known as base-10 (which we refer to as decimal). Given a number system of base-k, we have the digits 0-k-1 available for use. So, for example, in base-10, we have the digits 0-9 available.

Binary is nothing but a base-2 numbering system. This simply means that we only have two values available for use, 0 and 1. This also means that everything in binary is represented as a power of 2.

Converting base-k to base-10

In general, we can convert any given base-k number into a base-10 number using the following formula:

$$ xyzw_e = x * e^3 + y * e^2 + z * e^1 + w * e^0 $$

Unsigned Magnitude Binary Representation

Okay, now that we understand that binary is nothing more than a base-2 numbering system, let’s look at the simplest of binary representations, unsigned magnitude. Unsigned magnitude allows us to store unsigned integers in a binary representation. NOTE: we will cover ways to represent signed numbers and floating-point numbers later in this post.

Converting From Unsigned Magnitude to Decimal

Consider that we have the binary number: 11001101 and we know it’s an unsigned magnitude binary number. How do we go about converting it back into decimal notation (base-10)? Well, it’s actually quite simple.

All we have to do is, for each 1 in the binary number, add the corresponding power of 2. This will make more sense when you actually see this in action.

Example: Convert 11001101 to decimal.

11001101
76543210

The above figure shows our binary number and the corresponding index for each bit in the number. Notice that we have a one at the following indices: 0, 2, 3, 6, and 7. This means that to convert our binary number back into decimal, all we have to do is the following:

$$ 2^7 + 2^6 + 2^3 + 2^2 + 2^0 = 128 + 64 + 8 + 4 + 1 = 205 $$

Converting Decimal to Unsigned Magnitude Binary

Now that we’ve seen how we can convert from unsigned magnitude to decimal, let’s take a look at how to do the opposite. The process is really quite simple.

In general, you find the largest power of 2 that will fit in your number and write a 1 in that column. Subtract that power of two from the number you are converting and find the next largest power of two that will fit. Do this until you reach 0. Fill all remaining places in with 0s. This will be a lot more clear when you actually see it in action.

Example: Convert 91 to unsigned magnitude.

  • The largest power of 2 that fits into 91 is 64. 91-64 = 27
  • The largest power of 2 that fits into 27 is 16. 27-16 = 11
  • The largest power of 2 that fits into 11 is 8. 11-8 = 3
  • The largest power of 2 that fits into 3 is 2. 3-2 = 1
  • The largest power of 2 that fits into 1 is 1. 1-1 = 0

This means the powers of two we used are:

$$ 2^6, 2^4, 2^3, 2^1, \mbox{ and } 2^0 $$

This means we simple place a 1 at position 6, 4, 3, 1, and 0 and a 0 at positions 5, and 2:

1011011
6543210

What About Signed Integers?

Now we’ve seen how we can represent unsigned integers using unsigned magnitude, but what how do we represent signed integers? Well, as it turns out, there are three possible ways to do this.

Representing Sign

For representing signed integers we add a sign bit to the front of our binary number. If the sign bit is a 0, the number is positive. If the bit is a 1, then the number is negative.

This means if we have an 8-bit binary number, we’ll need 9 bits to represent this to account for the sign bit.

Signed Magnitude

The first way we can do this is through a method called signed magnitude. This is the simplest method. All we do is take our unsigned magnitude representation and throw a sign bit onto the front of the number.

Example 3 = 0011 and -3 = 1011

Problems

There are two big problems with this method of representing numbers:

  1. This gives us two ways to represent 0: 0000 and 1000. This means that we lose the ability to store an extra number since we have two ways to represent 0.
  2. You can’t simply perform arithmetic operations using this method. In order to do these operations, we will have to first strip off the sign bit and then insert the correct sign bit back in. This would require extra hardware.

One’s Complement

To try to compensate for the issues with signed magnitude, we came up with another method called one’s complement. In this method, we store positive numbers just as we would in unsigned magnitude. In the case of negative numbers, however, we do things differently.

For a negative number we invert all the bits.

Example: 19 = 010011 and -9 = 101100

Problem

We still have a problem with this method: we can still represent 0 as 000000 and 111111. This is why we have yet another method.

Two’s Complement

Two’s complement is how we solve the problems with signed magnitude and one’s complement. Like one’s complement, we again represent positive numbers in the same way and represent negative numbers differently.

For negative numbers, we flip all the bits and add 1.

Example: 19 = 010011 -19 = 101101

To convert it back, we simply flip all the bits and add 1.

This method is harder, but it solves all the problems we had with the previous methods or representing signed binary numbers.