Spectrometer: Quick and Dirty Image Processing

Having gotten a spectrometer image from the crude camera lashup, the next task is to (figure out how to) extract some meaningful data. The general idea is to use ImageMagick and Gnuplot as much as possible, so as to avoid writing any actual software.

The original image is the high-res version of this:

First light - warm-white CFL - no adjustments
First light - warm-white CFL - no adjustments

Use ImageMagick to crop out a slice across the middle and convert it to lossless PNG:

convert -crop 2500x100+0+1000! dsc00273.jpg dsc00273-strip.png

I can’t figure out how to reset the image size using -extract, but -crop gets the job done.

The default ImageMagic PNG compression is 75, so I should include a -quality 100 option, too.

Because we have colors separated spatially, all we need is a grayscale intensity plot. The easy and, alas, wrong way to convert the color image to grayscale goes like this:

convert -colorspace GRAY dsc00273-strip.png dsc00273-strip-gray.png

That grayscale value is a weighted sum of the RGB components that preserves human-vision luminosity:

Gray = 0.29900*R+0.58700*G+0.11400*B

I think it’s better to simply add the RGB components without the weights, because we care more about the actual spectral intensity. That might allow overly high intensity in some peculiar situations, but I’ll figure that out later. First, get the red / green / blue channels into separate files:

convert -separate dsc00273-strip.png dsc00273-strip-chan%d.png

That looks better: the intensities resemble the original colors.

Then add those three files together, pixel by pixel, to produce a single grayscale file:

convert -compose plus dsc00273-strip-chan0.png dsc00273-strip-chan1.png -composite dsc00273-strip-chan2.png -composite dsc00273-strip-spect.png

Extract a one-pixel row from the middle and write it as a raw binary file. You could extract the row from the original image, but I think some blurring might be appropriate, so later is better. There’s no point in trying to display a one-pixel-tall image, so I won’t bother.

convert -crop 2500x1+0+50 dsc00273-strip-spect.png gray:dsc00273-line.bin

Fire up Gnuplot and have it plot the grayscale intensities:

plot 'dsc00273-line.bin' binary format="%uint8" record=2500x1 using 1 with lines lt 3

And there’s the spectrogram…

Gnuplot - dsc00270 - CFL
Gnuplot - dsc00270 - CFL

A quick-and-dirty bash script to persuade ImageMagick to make something similar to that happen, including all the commented-out cruft that I’ve been copying forever so I don’t forget the magick incantations when I need them again:

echo Base name is ${base}
convert -crop 2500x100+0+1000! $1 ${base}-strip.png
convert -separate ${base}-strip.png ${base}-strip-chan%d.png
convert -compose plus ${base}-strip-chan0.png ${base}-strip-chan1.png -composite ${base}-strip-chan2.png -composite ${base}-strip-spect.png
convert -crop 2500x1+0+50 ${base}-strip-spect.png gray:${base}-line.bin
export GDFONTPATH="/usr/share/fonts/TTF/"
gnuplot << EOF
set term png font "arialbd.ttf" 18 size 950,600
set output "${base}-spect.png"
set title "${base} Spectrum"
set key noautotitles
unset mouse
set bmargin 4
#set grid xtics ytics
#set xrange [0:1400]
set xlabel "Red <- Colors -> Violet"
#set format x "%3.0f"
#set logscale y
set ylabel "Light intensity"
#set format y "%3.0f"
#set yrange [0:60]
#set ytic 5
#set datafile separator "\t"
#set label 1 "mumble" at 1600,0.300 font "arialbd,18"
plot	\
	"${base}-line.bin" \
	binary format="%uint8" record=2500x1 \
	using 1 with lines lt 3
display ${base}-spect.png

Observations & ideas:

It turns out that the flat topped peak in the middle was in the original green channel data: that color was overexposed.

If I had a camera that could do RAW images, this whole thing would work even better. Using 16-bit intensity channels would be exceedingly good; the original JPG file has only 8-bit channel resolution: 1/256 = -24 dB, which isn’t anywhere near good enough. That’s assuming the camera + JPG compression has 24 dB dynamic range, which I doubt.

That blue / violet peak over on the right looks great: the optical focus is fine & dandy. I focused the spectrometer at roughly infinity, set the camera to infinity, then tweaked the spectrometer to make the answer come out right.

FWIW, I think that deep blue-violet line is the mercury G-line emission at 435 nm, which would explain why it’s so narrow. The others are rather broad phosphor emissions from the CFL tube’s surface.

LEDs can provide spectral wavelength calibration markers, although their peaks are rather broad in comparison to mercury emission lines. A 400-450 nm “UV” LED puts out a broad blue-violet blur on the left (reddish) side of the emission line. Maybe it’s really the mercury emission H-line at 404 nm?

An IR LED puts a line on the far left side, about twice the distance to the left of the red line as the green line is to its right. I don’t know the exact wavelength, but it’s around 900 nm. The camera (my old DSC-F717) can do IR + visual images, but it insists on auto-setting the exposure and focus, which wipes out the other lines. The line is barely visible with the camera’s internal (and highly effective) hot mirror in place. Maybe with a more stable setup that would work.

Diode lasers in IR, red, green, and blue? Hmmm…

ImageMagick (probably) can’t detect those LED markers and scale the output file width, as it deals with intensity over a regular XY grid. A Python script could swallow the output binary file and spit out a scaled binary file with the bump peaks set to known locations. Actually, I’d be willing to bet there’s a perverse way to get IM to do X-axis scaling, but I’m even more certain the command-line syntax would be a wonder to behold.

Inject the LED images with a beamsplitter or teeny mirror across the bottom of the spectrometer slit and get intensity calibration, too. Vary the LED intensity with a known current for decent calibration over several orders of magnitude. That could compensate for the crappy dynamic range: as long as the LEDs aren’t saturated, you can correct them to a known peak value. IM can probably do that automagically, given known regions on the input curve.

Blur the strip image to get rid of color noise and irregularities in the slit. Perhaps a vertical sum in each channel along (part of?) the entire strip, then divide by the strip height, which would completely avoid blurring along the horizontal axis. If, of course, the entrance slit is exactly vertical with respect to the camera sensor.

IM knows how to deskew / rotate images. Apply that before summing, so as to correct small misalignments?

Different cameras have different entrance pupils. A quick check shows the DSC-H5 has a much smaller entrance pupil at full zoom: the spectrum covers more than the full screen, so the spectroscope won’t work well with that camera. Normally, you’d like to fill the entrance pupil with the image, but …

Getting all the optical machinery supported and aligned and oriented will require an optical bench of some sort. Perhaps my surface plate with magnetic sticky bases?

I think this is going to work…