Sunday, August 26, 2007

Suppose you want to sample regularly along a line...

Line profiles are useful tools. They allow you to get a sense of the intensities in the image, specifically, under the line itself. When attempting to determine the resolution of a system, line phantoms are often used; these phantoms can be seen in almost any camera review at dpreview. Using a line profile tool, you can get a sense of the amount of contrast at the resolution extinction point of the detector; ie, the peak to peak distance determines the pixel-by-pixel resolution, and the peak to trough distance determines the contrast at that resolution.

I recently wrote one of these, and became very frustrated with the lack of straightforward code to do so. So, here's my code, hopefully to save someone else the headache.

First things first-- you need to define where pixels are located. One convention has the pixels at the intersections of lines on a grid. This convention is most commonly used in math applications, solutions of PDEs on grids, and so forth. Another convention holds that the pixels are in the space inside the intersections of the pixels; that is, the grid designates the borders of the pixels, and the interior space of the grid is the location of the intensity value of a given pixel. This convention more commonly reflects the actual nature of a CCD or CMOS imager (those used in digital cameras). The flux of light across a certain element is measured in a process that converts photons into electrons, and those electrons are then converted to a digital signal via an analog/digital converter. Thus, this second convention more accurately reflects the physical reality from which the pixel grid was derived.

The implications of the selection of convention have to do with how many pixels you will sample to determine the value of points along the line. If you choose the first convention, many common formulae for the interpolation of data on a grid (nearest neighbor, linear, bicubi, Lanczos) are all available to you in their conventional forms. If you choose the second convention, while perhaps more accurately reflecting reality, the formulae all have to be converted into this new convention. Thus, I opt for the first, if only so that I can keep with mathematical convention and not rewrite everyone's formulae.

So, once that decision is out of the way, the line itself must be drawn. Presumably, the user provides start and end points, and it is up to you to sample along the line. You may be tempted to use a Bresenham approach, especially if you come from a graphics background. However, remember that we're trying to get numbers that reflect reality, we are not drawing and creating our own reality. The Bresenham approach is useful for drawing, but not for sampling, because you lose the actual floating-space locations of pixels along the line. Not only that, the division of values by the delta-Y term requires the use of a special case when the line is drawn entirely vertically, and avoiding special cases makes programming easier.

The code to sample along the line, then, will depend on using the length of the hypotenuse to determine the change in x and y that should be performed for each pixel. The code looks like:

float dxPrime = (mCurrEndPoint.X - mCurrStartPoint.X);

float
dyPrime = (mCurrEndPoint.Y - mCurrStartPoint.Y);
float
hyp = (float)Math.Sqrt(dxPrime * dxPrime + dyPrime * dyPrime);

if
(hyp > 0) {
float
dx = dxPrime / hyp;

float
dy = dyPrime / hyp;
float
[] xcoord = new float[(int)Math.Ceiling(hyp)];

float
[] ycoord = new float[(int)Math.Ceiling(hyp)];

xcoord[0] = (int)mCurrStartPoint.X;
ycoord[0] = (int)mCurrStartPoint.Y;

int
i = 1;
for
(i = 1; i < hyp; i++) {

xcoord[i] = xcoord[i - 1] + dx;

ycoord[i] = ycoord[i - 1] + dy;
}
}



Now that you've sampled, you need to get intensity values along those lines. There are three approaches.

Nearest neighbor (just choose the closest pixel as the value to use, cheapest in time and writing, worst accuracy):
mLineVals = new float[(int)Math.Ceiling(hyp)];

int
xval, yval;
float
a, b;
float
c, d, e, f, g, h; //for bicubic

for (i = 0; i < hyp; i++) {

xval = (int)Math.Floor(xcoord[i]+0.5f);

yval = (int)Math.Floor(ycoord[i]+0.5f);

mLineVals[i] = inImage.GetPixelAt(xval, yval);
}



Linear Interpolation (chooses the four closest pixels as the values to use, second cheapest in time and a bit more accurate):
mLineVals = new float[(int)Math.Ceiling(hyp)];

int
xval, yval;
float
a, b;
float
c, d, e, f, g, h; //for bicubic

for (i = 0; i < hyp; i++) {

xval = (int)Math.Floor(xcoord[i]);
yval = (int)Math.Floor(ycoord[i]);

a = xcoord[i] - (float)xval;
b = ycoord[i] - (float)yval;

if
(xval >= 0 && xval+1 < inImage.XSize &&

yval >= 0 && yval+1 < inImage.YSize) {

mLineVals[i] =
(
1 - a) * (1 - b) * inImage.GetPixelAt(xval, yval) +

a * (1 - b) * inImage.GetPixelAt(xval + 1, yval) +
(
1 - a) * b * inImage.GetPixelAt(xval, yval + 1) +

a * b * inImage.GetPixelAt(xval + 1, yval + 1);

}
}





Bicubic interpolation (chooses the 16 closest pixels as the values to use, most expensive in time but still fast enough on modern hardware, and the smoothest of all, and what I use):

mLineVals = new float[(int)Math.Ceiling(hyp)];

int
xval, yval;
float
a, b;
float
c, d, e, f, g, h; //for bicubic

for (i = 0; i < hyp; i++) {

xval = (int)Math.Floor(xcoord[i]);
yval = (int)Math.Floor(ycoord[i]);

a = xcoord[i] - (float)xval;
b = ycoord[i] - (float)yval;

c = 1.0f - a;
d = 1.0f - b;

e = a + 1.0f;//backwards distance is a + 1 pixel
f = b + 1.0f;

g = 2.0f - a;//forwards distance is 2 pixels - a;
h = 2.0f - b;

if
(xval-1 >= 0 && xval + 2 < inImage.XSize &&

yval-1 >= 0 && yval + 2 < inImage.YSize) {

mLineVals[i] = //inImage.GetPixelAt(xval, yval);
(1 - 2 * a * a + a * a * a) * (1 - 2 * b * b + b * b * b) * inImage.GetPixelAt(xval, yval) +
(
1 - 2 * c * c + c * c * c) * (1 - 2 * b * b + b * b * b) * inImage.GetPixelAt(xval + 1, yval) +
(
1 - 2 * a * a + a * a * a) * (1 - 2 * d * d + d * d * d) * inImage.GetPixelAt(xval, yval + 1) +
(
1 - 2 * c * c + c * c * c) * (1 - 2 * d * d + d * d * d) * inImage.GetPixelAt(xval + 1, yval + 1) +

//top line
(4 - 8 * e + 5 * e * e - e * e * e) * (4 - 8 * f + 5 * f * f - f * f * f) * inImage.GetPixelAt(xval - 1, yval - 1) +
(
1 - 2 * a * a + a * a * a) * (4 - 8 * f + 5 * f * f - f * f * f) * inImage.GetPixelAt(xval, yval - 1) +
(
1 - 2 * c * c + c * c * c) * (4 - 8 * f + 5 * f * f - f * f * f) * inImage.GetPixelAt(xval + 1, yval - 1) +
(
4 - 8 * g + 5 * g * g - g * g * g) * (4 - 8 * f + 5 * f * f - f * f * f) * inImage.GetPixelAt(xval + 2, yval - 1) +

//bottom line
(4 - 8 * e + 5 * e * e - e * e * e) * (4 - 8 * h + 5 * h * h - h * h * h) * inImage.GetPixelAt(xval - 1, yval + 2) +
(
1 - 2 * a * a + a * a * a) * (4 - 8 * h + 5 * h * h - h * h * h) * inImage.GetPixelAt(xval, yval + 2) +
(
1 - 2 * c * c + c * c * c) * (4 - 8 * h + 5 * h * h - h * h * h) * inImage.GetPixelAt(xval + 1, yval + 2) +
(
4 - 8 * g + 5 * g * g - g * g * g) * (4 - 8 * h + 5 * h * h - h * h * h) * inImage.GetPixelAt(xval + 2, yval + 2) +

//left side
(4 - 8 * e + 5 * e * e - e * e * e) * (1 - 2 * b * b + b * b * b) * inImage.GetPixelAt(xval - 1, yval) +
(
4 - 8 * e + 5 * e * e - e * e * e) * (1 - 2 * d * d + d * d * d) * inImage.GetPixelAt(xval - 1, yval + 1) +

//right side
(4 - 8 * g + 5 * g * g - g * g * g) * (1 - 2 * b * b + b * b * b) * inImage.GetPixelAt(xval + 2, yval) +
(
4 - 8 * g + 5 * g * g - g * g * g) * (1 - 2 * d * d + d * d * d) * inImage.GetPixelAt(xval + 2, yval + 1);

}
}




(there's a fourth, Lanczos, but I'm ignoring it, because bicubic is good enough, I think).

And there you have it. From there, you need to be able to draw the line profile. However, that drawing code is specific to each platform. To do it in C#, I would have a class that inherits from PictureBox and overwrites the draw routine, like so:


private
void JustPaint() {

//start from 1% of the edge and go to 99% of the edge
//round down for the start, up for the end, but want to make sure leave some space

if
(mValues == null) return; //no drawing yet

if (mValues.Length == 0) return; //no drawing yet


if
(Image == null || mOldWidth != Width || mOldHeight != Height)

Image = new Bitmap(Width, Height);
Graphics g = Graphics.FromImage(Image);

mOldWidth = Width;
mOldHeight = Height;

int
theStart = (int)((float)Width * 0.01f);

int
theEnd = (int)((float)Width * 0.99f + 0.5f);

int
theMaxHeight = (int)((float)Height * 0.01f);

int
theMinHeight = (int)((float)Height * 0.99f);

float
theHeightRange = (int)(theMinHeight - theMaxHeight);

g.Clear(Color.White);

//rescale the values to be between 0 and 1
//note that mRescale is set with mLineVals, so that when this code
//is called during redraws that are not relevant to line moving, less work is done

//the x step is from theStart to theEnd, scaled by the length of the line.
//clearly, we drop some points for long lines.
//so, we have to get a sampling rate from mRescaled.length vs theEnd-theStart
float theRange = theEnd - theStart;
float
theSample = 1;

float
theDrawStep = 1;
if
(theRange > mValues.Length) {

theSample = 1.0f;
theDrawStep = (float)theRange / (float)mValues.Length;
}
else {

theSample = (float)mValues.Length / (float)theRange;
theDrawStep = 1.0f; //every pixel gets hit

}
float
i = theSample;
float
theDrawStart = theStart;

float
theDrawSpot = (theStart) + (theDrawStep);
int
intDrawStart = (int)(theDrawStart + 0.5f);

int
intDrawSpot = (int)(theDrawSpot + 0.5f);
while
((int)(i+0.5f) < mValues.Length){

g.DrawLine(mBluePen,
new
Point((int)(theDrawStart + 0.5f),

theMinHeight - (int)(mValues[(int)((i - theSample)+0.5f)] * theHeightRange + 0.5f)),

new
Point((int)(theDrawSpot + 0.5f),
theMinHeight - (int)(mValues[(int)(i+0.5f)] * theHeightRange + 0.5f)));

theDrawStart = theDrawSpot;
theDrawSpot += theDrawStep;

i += theSample;
}

}


There you have it. Assuming you can draw a line, you can now display a graph of that line.

Monday, August 20, 2007

Some Flower Pictures

I see a lot of flower pictures turning up and around on the web. Some are pretty simple things, with the flower straight on. Then you do something where you put the flower to the side, and maybe you get some nice light on it, and everything's awesome. Trick is, to make the flower image stand out as a unique image, as one that will make people sit up and take notice, rather than say, "Ah, good, you've learned something in your photography class."

There are, of course, things you can do to get the flower to be interesting. One good ploy is to have a bug on it, like so:

That's pretty neat (or at least, I like to think so), but it's not about the flower, it's about the bug and the flower. It might also have depth of field issues, if you want to be a total perfectionist, but that's another subject for another time.

But, eventually, there is something in the beauty of a flower that can be pulled out. For those of you shooting with Nikons, I highly recommend the 60mm 2.8 macro lens, as it can give ridiculously sharp results, even after having been dropped on concrete (my bad).

As an example:


Unfortunately, these small images don't do the detail in the image justice. The rightmost petal, up close, looks like:


But how do you do it? Here's the simple steps:

1) Get a tripod. A nice sturdy one. There are loads of tripods out in the world, and plenty of reviews. The Manfrotto 3021 seems to be pretty popular; that's what I use, but it's by no means perfect.
2) Get a flower. Preferably one you like.
3) Arrange the flower and the light in such a way that some aspect of the flower is emphasized, some part of it you want to expose to the viewer, so that it's more than just a flower, but also has your expression on it. This step is really where the 'art' part comes in, and you can futz with this for a long time. Suffice it to say, the above shots all have several decisions that went into making them. I chose black and white for the rose, for instance, because I really like the detail in the shot, and the way the flower petals overlap with one another, coupled with the softness of the light, makes for an image I like to look at. Others might want to emphasize the color, or spray some mist on it, or something.
4) Set your camera to a timer. This step ensures that you won't affect the camera or the tripod when the shot is taken; if you have your hands on the thing, twitching might move the camera, or the force of pressing the button might change your composition.
5) If you're shooting digital, go to the lowest ISO you can. You're on a tripod; there's no reason not to take your time.
6) Check your depth of field. One of the reasons I like true macro lenses is the ability to go to f/22 or something ridiculous; except, it's not ridiculous when close up. The depth of field shrinks the closer you focus on something, and if you're only an inch or two away from your subject, an f/4 aperture is paper thin. The selection of the aperture is really part of composition from step 3, and should be given due consideration (and considerable experimentation when learning).

I prefer natural light, frankly, because I'm lazy. I also happen to think flowers look just fine in natural light, but I'm not the final word.

(all of these shots, btw, can be found on http://www.markmroden.com)

Sunday, August 12, 2007

One year, w00t!

One year anniversary! Yay!

Check out the photos from Granrose's 80th, in Pottsville, where we celebrated:

http://www.markmroden.com/gallery/3292544#182808129

Friday, August 3, 2007

Since when does the government get to fire me?

So I read this article here in this morning's LA Times, and I am pretty angry about it.

Since when does the Federal Government have the ability to fire me? Am I now employed by them, or employed by my employer?

How do they know that workers with inconsistencies in their SSN's and their W-2's are, in fact, illegal? Have they done any studies to show the correlation? It appears, from the article, that two thirds of people who have these discrepancies are, in fact, native born, so how does that square up?

What's the process for correcting the problem? Will it end up being something as messed up as Medicare, part D, which requires a deep and intimate knowledge of Parseval's Theorem and it's solution in some kind of n-dimensional Hilbert space? I'm not even sure I know if those mathematical concepts go together, but I'm pretty sure they do, like I'm pretty sure that since I've been working 3 jobs to support myself in graduate school, I'm gonna be one of the flagged workers.

This administration started out with claims that all come from Reagan, that we're going to make government smaller, and that more government is the problem and not the solution. It seems to have caught up to them that all of the social problems they want to fix, be it showing nipples at football games, underage sex, illegal aliens, underage sex with illegal aliens while watching naked football, or any other hobgoblins that keep such God-fearing busybodies awake at night, all require some kind of government intervention.

So what's it gonna be, Republicans? More government to fix your perceived ills of society, or less government and keeping your promises?

Wednesday, August 1, 2007

"My Recent Documents" rant

So, I like to get the images I'm working on at the moment via the Recent Documentations option in the directory tree.

Unfortunately, for reasons that are beyond me, this dialog is, by default, sorted in ascending date order, instead of descending date order, so that you get the oldest documents you've worked with, instead of the newest.

Like anyone has _ever_ wanted that feature in a Recent Documents list.  

While I'm on the subject of this dialog box, how hard is it to save the state of the damn thing from opening to opening?  If I'm using the 'details' view, the next time I open the dialog, I want the 'details' view up.  Not difficult.  Why revert to some standard template that I don't want?  Would it be possible to get around this by keeping a copy of the dialog box around, so that I can open and close it at will, but flush the previous selection, and maybe copy settings to another dialog, so I can have different options for save and load?

And why don't other programs do that, if it's so easy?

Walking across America

I would walk five hundred miles
and I would walk five hundred more
Just to be the man who walked a thousand miles
To end this stupid war.

http://www.marchforpeace.info/

With apologies to The Proclaimers.

Return to C++!

Finally, I get to return to C++.

Not that C# is all bad, mind you. It's pretty decent when it comes to UI and the day-to-day meanderings and wanderings of what needs to happen to keep a program running. Perhaps its nicest feature is the ease with which I can attach an outside dll and use that, because now that I need to make sure I have some serious horsepower, C# is stepping out of the way.

One thing that irritates me: how hard it is to debug unmanaged C++ from inside a managed C# environment. But mmr, I hear you cry, the whole freakin' point of C# is that everything can be managed for you, and the IDE will hold your hand. But sometimes, I don't want my hand held, I want to run free. Free as the wind blows, free as the grass grows.

To attach a dll, just do this:
using System.Runtime.InteropServices;

[
DllImport("mydll.dll")]

public static extern
void MyFunc(int inImagePtr, int outImagePtr, int inYSize, int inXSize);



private
unsafe ushort[] UseMyFunc(ushort[] inChannelData, int inRows, int inColumns) {

ushort[] theOutData = new ushort[inChannelData.Length];

fixed (ushort* inBufferPtr = &inChannelData[0]) {
fixed (ushort* outBufferPtr = &theOutData[0]) {

MyFunc((int)inBufferPtr, (int)outBufferPtr, inRows, inColumns);
}
}


return
theOutData;
}
}


Essentially, you need to have a function exposed by the dll, and then you can use that function. So you get the speed of C++ inside the function, but the handiness of C# outside the function. Not a bad combination.