Thursday, December 29, 2011

Pythagoras: Structural Update

Solving the Flex
After dealing with the flexing aluminum supports that allowed the whole robot to wobble for so long, I finally went ahead and bought steel tubing. The legs are also longer, allowing expansion of the drawing field. Unfortunately, they are also taller, so for now, the paper needs a little booster to get it to the optimal drawing height.

Phone books to lift the paper to the pen

The tubing is connected by gusset plates, made of .03" galvanized steel.
Slots in the plates for adjusting exact lengths

Now the bot is so sturdy I could probably stand on it and it would hold.

Pythagoras Bitmap Mode: How It's Done

Introduction

Bitmap mode involves shading in the color of images, as opposed to just making the outlines. For some images, this makes vastly superior output compared to outlines. Faces are a prime example. When outlined, edges of shadows appear the same as edges of objects, causing a mess of lines to be drawn on a face. With shading, shadows and lower contrast features remain in the image, improving the familiarity of the face.
Its my face!
The downside is that the output resolution is greatly reduced, and the time it takes to draw is greatly increased.

The Concept

How do I generate levels of grey scale when my pen only creates a line of constant darkness? The answer: squiggles. Each pixel forms a 2D square on the paper. The more that square is shaded in by the pen, the darker the pixel. This idea can be clearly seen in one of the earlier test pictures. Ramp function patterns are drawn on the paper to fill the square. Then I tested with square functions, then triangle waves.

Since the shading depends on the width of the pen stroke, the pixel size on paper becomes a factor. Smaller pixels make smoother borders around regions on the output image, but also decreases the number of squiggles that can be fit into a pixel before the pixel becomes solid dark. So at one extreme, the pixel is completely filled by a single stroke of the pen, while at the other extreme, the entire image is a single pixel.
Through testing, I found the best compromise between greyscale levels and output resolution is to have an image width of between 120 and 160 pixels, for a 7.5 inch (19 cm) square drawing area.

Tweaking Waveforms

First was experimenting with different waveforms. After creating ramp functions, I tried to do square waves. However, this created odd behavior whenever the squares shifted in and out of phase with the rows above and below them. It was quickly abandoned, apologies for the lack of pictures. What I settled on was triangle waves, which were always in phase like the ramp functions, yet more uniform like square waves, closer to the ideal of sine waves.

In an attempt to squeeze out more greyscale levels, I considered sharing squiggles across multiple pixels, up to four pixels. This way I could get even less dense pen strokes and therefore lighter colored intensities. The results were not so great though. This image allowed up to four combined pixels.
The pixel combining can be observed near the center of the drawing.
The pixel combining created awkwardly shaded areas in an image. Even when combining less pixels.
A series of test drawings under various brightness settings. Scribbling in the corner is unrelated.
Pixel combining didn't create evenly lighter areas, just awkward gaps. The solution to this is to use amplitude modulation. To make a lighter region, the amplitude of the squiggle is reduced, instead of the number of squiggles. This can be seen clearly below.
Just below the center is an example of amplitude modulation of the squiggle.
This fills the gap in shading between one squiggle per pixel and no squiggle, making nicer transitions for lighter colors.

Color Mapping

Originally, color was mapped linearly to number of squiggles in each pixel. This caused for pretty dark images, as seen below.
Can barely make out differences in the greys.
The first step to fixing this is to add gamma correction. Since human eyes are more sensitive to darker colors than lighter colors, color values in images are shifted toward the darker regions according to an exponential rule. This way, more bits are devoted toward the darker colors, instead of highlights that eyes cannot differentiate anyways. 
To correct for gamma, simply raise the pixel value, mapped from 0 to 1, to the (1/2.2) power to get the actual color intensity. This has an effect of making the output image brighter. Technically, any image processing should be done after gamma correction, for proper mixing of pixel values. However, this will involve a lot of going between gamma encoded and decoded, so I let it slide. I do scaling before gamma correction, and gamma correct once, right before converting the pixel value to squiggles. This makes the picture a little brighter.
The same picture, with gamma correction. A little brighter.
The next step in color mapping is to fix the mapping of intensity to squiggles. The mapping is not linear, and is depending on the size of the pixel. The mapping I got from experimenting is intensities 0.7 and less are linearly mapped to squiggle density. Between 0.85 and 0.7 intensity is the region for amplitude modulation of the squiggles. Testing was done with printing gradients and comparing the result to the computer image.


Software

The image processing software was written in Python, using the OpenCV libraries for image processing and simple GUIs. One slider lets the user change the output resolution until the aliasing looks alright, and a second slider lets the user change the gamma value from the default of 2.2, to change the brightness of an image.

There is a subtlety in the image preview. While the screen can display the full 256 values of greyscale, the robot can only print a much smaller range of greyscale, which varies as a function of pixel size. This can be especially misleading for lighter colored regions, since full white pixels cover color intensities of 0.85 and 0.7, and for darker colored regions, where seemingly differently colored regions are both shaded the same.

To fix this, the output pixel values are grouped into buckets based on the number of squiggles in that pixel. This way, the output image has the same number of greyscale levels as the printed image. With some funky mapping, the colors on screen are closer to the colors as printed. This way, it is much easier to adjust the brightness and scaling and predict the actual results. Following are screenshots of the program in action after bucketing. You can see how there are significantly less than 256 levels of grey.
Default scaling of 120 pixels and gamma of 2.2 (220 / 100).
Darker image.
Brighter image.


Appendix





Monday, December 26, 2011

3D Video of Pythagoras

While visiting at Lake Brantley High School, one of the students took a delightful video of Pythagoras in 3D as it sketched Pikachu in raster mode.

Tuesday, December 6, 2011

Pythagoras Bitmap Mode

Just finished cooking up a new drawing mode, letting Pythagoras draw raster images. A writeup will come soon, for now a bunch of drawings.

First test
Second test
Third test
Before gamma correction
After some gamma correction. Teehee moire patterns.
In progress

Monday, December 5, 2011

LightDial: Done!

I cooked up a quick Solidworks model of the clock. Protip: don't set your parts' appearances to transparent plastic until you're done. Basically working in wireframe the entire time.

The clock is planned to have a five inch diameter face, with two buttons and an RPG (rotary pulse generator) to adjust the time.

Using the Invention Studio's laser cutter, the pieces were cut out without hiccups. Holes were also cut out in the pieces for the LED leads. The LEDs were slotted in and wired together with a common cathode. This is when the true hairiness of 36 individual wires came into play, and we realized what we were getting ourselves into.

A digression: Through a random experiment, we discovered that a backlit clock face looks better than a frontlit clock face. Thus, we reversed the order of the clock rings, so instead of two LED rings and a face, it was a face and then two LED rings. This is why the actual build doesn't look like the Solidworks model.

Populating the board was easy enough. Until I realized the wire I have doesn't fit inside the connector, and the Molex connector crimper I can use is too big for the 1.25 mm pitch connectors I have. Begrudgingly, my lab partner and I decide in the interest of time to solder the LED wires directly onto the board. This we did.
The horror! The horror!
Then I found out the LED drivers (the ones underneath all the wires) were soldered on backwards. Desolder and resolder all the wires!

There was a good thing that came out of all the soldering. In code, the lights were ordered B7G7R7B6G6R6...B0G0R0. In circuitry, the green and red channels were swapped, as in B7R7G7. Headaches were had all around, but they were wired in order to simplify programming.

After that, the major hardships were over.


First light!
Wiring done, all that was left was a matter of coding. Using the SPI peripheral was a matter of setting registers. Time was kept by counting program cycles. I did add a watch crystal to the board, but due to time, I didn't bother enabling it, so the timekeeping accuracy is only good enough for short term use.

In terms of human interface, I used two buttons and a rotary pulse generator. Holding down one button and turning the RPG adjusts minutes, the other hours.

Hour lights
And so, putting it all together,
Hours and minutes. Woohoo!
Rats nest

Blue LEDs look funny on camera
Appendix
Source Code: 
Eagle Design Files:  
Solidworks Models: