Our first spike took a bit longer than we had hoped, but we successfully interpreted a BMP file. We worked to improve our code, while people wrote to us to tell us how ignorant we are. Fun all around.

Improving the Code

We spoke last time about focusing more on the shot pattern as a collection of hits, rather than so closely emulating the picture file. A number of people wrote to us making that point. However, we weren’t imagining that we had a good pattern for an object – we were trying to reduce our ignorance and fear about how to get the file read in.

As it turns out, we did that poorly as well, according to a few readers. They wanted to know why we hadn’t used this or that available library for accessing graphics files. There were two reasons: Chet had done some web research, and found mostly things that he found hard to understand. (And so did I when they were shown to me.) Bill Wake pointed us to some useful tutorials on the Sun site, which we’ll be following up on shortly. But not being big Java users, we weren’t aware of those tutorials, and didn’t happen to find them.

The other reason was that I have done lots of graphical software in the distant past, long before there were many useful libraries for the hardware I was using, so I used to have pretty good chops when it comes to zipping and slicing through the bits. So I fell into the trap of imagining that I could still do that pretty well, and that I didn’t need no stinking library. Truth is, I scarcely even looked for libraries – I was hot to dig into that file on my own.

Now I would like to argue that that’s not as crazy as it sounds – that there can be real advantages to working close to the iron. That way, you can build up the abstractions you want, rather than be saddled with the abstractions that have been invented to solve a whole bunch of problems. Libraries like that are often jungles, that you have to hack through for a long time to find a decent path.

It’s going to turn out, however, that the libraries we are now using, to be seen in the next couple of articles, are pretty darn good, and while I’m not sorry we spent a couple of hours digging into the file by hand, we’re not going to keep doing that.

Coding Session

But we’re getting ahead of our selves. First let’s take a look at the code Chet and I did in our next session, after we had decided that we needed a shotgun pattern object that understood hits, not bits. We moved our current tests and objects to a different package, keeping them in the same project for reference. We figure we can delete them later, after we scrounge them for parts.

Then we created new tests for our ShotPattern object.

public class CenterOfMassTest {

    @Before
    public void setUp() throws Exception {
    }

    @Test
    public void trivial() {
        ShotPattern shotPattern = new ShotPattern();
        assertEquals(new Hit(0,0), shotPattern.CenterOfMass());
    }

    @Test
    public void x4Y9() {
        ShotPattern shotPattern = new ShotPattern();
        shotPattern.addHit(new Hit(4,9));
        assertEquals(new Hit(4,9), shotPattern.CenterOfMass());
    }

    @Test
    public void centerOfMore() {
        ShotPattern shotPattern = new ShotPattern();            
        shotPattern.addHit(new Hit(-3,+2));
        shotPattern.addHit(new Hit(0, +2));
        shotPattern.addHit(new Hit(+3, +2));
        shotPattern.addHit(new Hit(-3,0));
        shotPattern.addHit(new Hit(0,0));
        shotPattern.addHit(new Hit(+3,0));
        shotPattern.addHit(new Hit(-3,-2));
        shotPattern.addHit(new Hit(0,-2));
        shotPattern.addHit(new Hit(+3,-2));

        assertEquals(new Hit(0,0), shotPattern.CenterOfMass());
    }

    @Test
    public void leftOfCenter() {
        ShotPattern shotPattern = new ShotPattern();                
        shotPattern.addHit(new Hit(-6,+2));
        shotPattern.addHit(new Hit(0, +2));
        shotPattern.addHit(new Hit(+3, +2));
        shotPattern.addHit(new Hit(-6,0));
        shotPattern.addHit(new Hit(0,0));
        shotPattern.addHit(new Hit(+3,0));
        shotPattern.addHit(new Hit(-6,-2));
        shotPattern.addHit(new Hit(0,-2));
        shotPattern.addHit(new Hit(+3,-2));

        assertEquals(new Hit(-1,0), shotPattern.CenterOfMass());
    }

}

These were created one at a time, of course. We started with an empty picture, deciding that its CenterOfMass would be at zero zero, and so on. We posited the ShotPattern class, and decided that it would hold Hits, which have x and y, and will surely have other knowledge later.

We realized, upon reflection, that we do know how to calculate the center of mass in a two-dimensional system, because the x and y coordinates are not linked. The center of gravity is just a point consisting of the average of all the x coordinates, average of all the y’s. So we went ahead with those tests, and wound up with this:

public class ShotPattern {
    ArrayList<Hit> hits = new ArrayList<Hit>();

    public ShotPattern(ArrayList<Hit> hits) {
        this.hits = hits;
    }

    public ShotPattern() {
    }

    public Hit CenterOfMass() {
        if (hits.size() == 0) return new Hit(0,0);
        return new Hit(xCenter(), yCenter());
    }

    private int yCenter() {
        int sum = 0;
        for (Hit hit : hits) {
            sum += hit.getY();
        }
        return sum/hits.size();
    }

    private int xCenter() {
        int sum = 0;
        for (Hit hit : hits) {
            sum += hit.getX();
        }
        return sum/hits.size();
    }

    public void addHit(Hit hit) {
        hits.add(hit);
    }

}

This went fairly smoothly, and would have gone more so had Chet not said “add x to sum” and typed sum = hit.getX(). Some our tests worked with this code, and when we got to one that didn’t, we were very confused for a while.

This simple code supported our theory at lunch, that we needed to move away from the physical file notion, and build up an abstraction that made more sense for what we’re trying to do. This isn’t much of a surprise, I suppose … except that we see code all the time that is dealing with files or database records, instead of domain concepts. So … word to the wise.

Moving Right Along ...

We each did an hour or so of programming after we finished the above. Chet wrote some tests and code to read our files and create a ShotPattern instance with hits, and I did a calculation this morning around density. I’ll put the density material in the next article. Let’s look at Chet’s I/O.

We got a hot tip from Bill Wake on the tutorials, as I mentioned, and Chet wrote some tests to try out the Raster object. Here they are, with a tweak I put in to make them run on my computer. We still haven’t figured out the best way to point to things like files, since Chet organizes his machine a bit differently from mine. Anyway, here are the tests::

public class RasterTest {
//  final String folder = "C:\\Documents and Settings\\Ron\\My Documents\\Data\\eclipseworkspace\\"; 
    final String folder = "C:\\Documents and Settings\\Ron\\My Documents\\Data\\eclipseworkspace\\"; 

    @Test
    public void x4y9File() {
        int[] sampleArray = null;
        BufferedImage img = null;
        try {
            img = ImageIO.read(new File(folder + "patternProject\\x4y9on7x12.bmp"));
        } catch (IOException e) {
        }

        Raster raster = img.getData();
        assertEquals(7, raster.getWidth());
        assertEquals(12, raster.getHeight());

        assertEquals(0, (raster.getPixel(4, 9, sampleArray))[0]);
        assertEquals(1, (raster.getPixel(5, 5, sampleArray))[0]);        
    }

    @Test
    public void useRasterToCreateShotPatternBiggerFile() {
        ShotPattern  shotPattern = new ShotPattern(folder + "patternProject\\PB270011.bmp");
        assertEquals(new Hit(123,-281), shotPattern.CenterOfMass());
    }

    @Test
    public void check4x9Center() {
        ShotPattern  shotPattern = new ShotPattern(folder + "patternProject\\x4y9on7x12.bmp");
        assertEquals(new Hit(1,3), shotPattern.CenterOfMass());
    }

    @Test
    public void checkChetCenter() {
        ShotPattern  shotPattern = new ShotPattern(folder + "patternProject\\PB270010.bmp");
        assertEquals(new Hit(167,-229), shotPattern.CenterOfMass());
    }

}

We both added the checkChetCenter test … that’s my version. What’s interesting there is that the picture shows the center of mass is clearly above the center line. This tells us that Chet’s new code apparently isn’t taking the fact that BMP files are “upside down” into account. We’ll work on that Monday, when we get together. Here’s the code that makes the I/O work:

   public ShotPattern(String fileName) {
        Raster farian = raster(fileName);
        int width = farian.getWidth();
        int height = farian.getHeight();
        int yOffset = height / 2;
        int xOffset = width / 2;    

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                addHit(farian, yOffset, xOffset, y, x);
            }
        }
    }

    private void addHit(Raster raster, int yOffset, int xOffset, int y, int x) {
        if (isHit(raster, y, x)){
            this.hits.add(new Hit(x - xOffset,y - yOffset));
        }
    }

    private boolean isHit(Raster raster, int y, int x) {
        int[] sampleArray = null;
        return (raster.getPixel(x, y, sampleArray))[0] == 0;
    }

    private Raster raster(String fileName) {
        BufferedImage img = null;
        try {
            img = ImageIO.read(new File(fileName));
        } catch (IOException e) {
        }

        Raster raster = img.getData();
        return raster;
    }

Here Chet gets a Raster on the file name, and just spins through all the bits, recording all the ones that represent hits. The interface to Raster is odd, in that you have to think in terms of “samples”, which are certainly news to me when it comes to graphics. Like with many libraries, you just follow the cookbook.

Chet’s not entirely happy with this code, though it is much nicer than the first version he sent me, after he refactored it a bit with the isHit and addHit method extracted out. But it does seem to be working just fine.

Distribution of Shot

Presumably one wants a fairly smooth distribution of shot across the target area. Around the point of aim, there would be a higher density, with density fading out uniformly as distance from the point of aim increases. A graphic display of that density might show up non-uniformity as spots that look too light, or too dark, compared to spots around them.

The first problem here will be to divide the target area into segments of equal area, and count the shot in each such area. Then we’ll want to figure out how to display the information. It would be, in principle, possible to choose the areas dynamically, based on the size of the input picture, but since our product vision includes control over the picture size, we can safely start with a partitioning algorithm that is provided the definition of the areas to scan.

Even if we go to a dynamic determination, this seems like the right place to start. Given a method that takes the area definition, we can always use that method with our automatic determination, should we ever build one. As always, starting simply is likely to pay off.

A Spike

Over the weekend, I had a little free time, so I decided to try a spike at calculating density. I drew a little grid on a card, put 1, 2, 3, and 4 marks in the four quadrants, then coded up a test. My first attempt at the test, I couldn’t even think of a good way to ask the question.

I finally envisioned a method, analyzeDensities, taking an inner grid size, and a number of grids, returning an array of ints, representing the number of hits in each cell of the grid. (I was disappointed to have to say how many cells to place across and down the picture, but at this point, the ShotPattern doesn’t know its width or height. (It could, and should, but I was just barely clinging to this idea by my nails, so I didn’t want to digress to improve the object, just for a spike. The test looks like this:

   @Test
    public void fourQuadrants() {
        p.addHit(1,1);
        p.addHit(3,1);
        p.addHit(4,1);
        p.addHit(0,3);
        p.addHit(1,3);
        p.addHit(3,3);
        p.addHit(4,3);
        p.addHit(0,4);
        p.addHit(1,4);
        p.addHit(3,4);
        int[] densities = p.analyzeDensity(2,2,3,3);
        assertEquals(1, densities[0]);
        assertEquals(2, densities[1]);
        assertEquals(4, densities[2]);
        assertEquals(3, densities[3]);
    }

Then I proceeded to write a bit of very procedural code to compute the densities array. I was envisioning moving my cell across the picture, counting the hits in it at each location, and cumulating them into the array. The code isn’t pretty, nor am I proud of it. It does have at least one lesson in it, and it was a valid, if clumsy, first implementation. And, by the way, it does work. It looks like this:

   public int[] analyzeDensity(int numX, int numY, int deltaX, int deltaY) {
        int[] result;
        result = new int[numX*numY];
        int areaY = 0;
        for (int y = 0; y < numY; y++) {
            int areaX = 0;
            for (int x = 0; x < numX; x++) {
                int location = numX*y+x;
                result[location] = density(areaX, areaY, numX, numY);
                areaX += deltaX;
            }
            areaY += deltaY;
        }
        return result;
    }

    private int density(int areaX, int areaY, int numX, int numY) {
        int count = 0;
        for (Hit hit : hits) {
            int x = hit.getX();
            int y = hit.getY();
            if (x >= areaX && x < areaX + numX && y >= areaY && y < areaY + numY)
                count++;
        }
        return count;
    }

The tests run, so I’ve accomplished what I wanted, which was to do a spike focusing on shot density. I’ve learned a bit about how to do it. But look at that code! It’s hideous! All that looping and subscripting and comparing … UGGGLEEE. Looks like something some aging FORTRAN programmer would write.

That’ll need cleaning up, or perhaps rewriting. I have some ideas that I’ll mention here, and this will be on our list for tomorrow as well.

What’s fundamentally happening here is that we have a small cell (3x3 in my tests), that’s “replicated” across and down the ShotPattern, counting the number of hits in each occurrence of the cell. It’s all being calculated by incrementing x’s and y’s in loops. But the idea itself is simple: a rectangle is placed on the picture, and the hits inside it are counted.

As written the code goes over all hits in the picture, for every cell. And the code is intricate and far from clear. This calls for something different.

First of all, we should create a Rectangle, and position it where we need it, and ask the Rectangle to tell us whether the hit is inside it or not. That should encapsulate some of that ugly if statement, and clean up the looping as well.

Second, instead of looping again and again, why not go over the hits just once, asking each one to identify which Rectangle it belongs in (since it only belongs in one), and then cumulating the results somehow. That, too, should make things simpler.

And, that too, is for tomorrow. For now, thanks for tuning in, and keep those cards and letters coming. We’ll actually use some graphics stuff tomorrow, I expect!