Forum

Welcome Guest 

Show/Hide Header

Welcome Guest, posting in this forum requires registration.





Pages: [1]
Author Topic: Understanding the flow from image to action
zenwebb
Newbie
Posts: 3
Permalink
Post Understanding the flow from image to action
on: January 15, 2012, 23:19
Quote

I've been trying to understand the polargraph project for the last few weeks and have come into a number of problems. The most important of which is how a raster image is translated into motor actions and ultimately drawn.

I do not mean to be harshly critical, but the software component of the polargraph project is quite frenetic and difficult to follow. I completely understand how code can go from being simple and functional and evolve into something unweildy and complex, but perhaps now is as good a time as any to address that trend. For example, the user interface (and the code) contains many bits and pieces that don't appear to be used, or at least are not as important as others. The lack of documentation of the UI makes the experience somewhat bewildering.

Since I have not been able to clearly understand the roles and function of the Processing sketch and Arduino sketch (beyond the obvious), I am working on my own implementation of the software systems, just so I can understand it all better.

What I am having difficulty understanding right now is which pieces of software are involved in the process of converting a raster image into what the polargraph ultimately outputs. I'd really like to get a high-level, abstracted discussion going about the theory and thoughts behind the process. For example, could you walk me through the journey of a single pixel of an image?

Here is what I have gathered so far:
1. Raster image is loaded into Processing sketch
2. Processing sketch iterates through each pixel of the image, noting each pixel's brightness. Does the Processing app, therefore, only deal with grayscale images?
3. At some point (perhaps at the same time the image is initially analyzed?), a list of commands are generated and queued up for the polargraph hardware to carry out. How are these commands created, exactly?
4. Polargraph hardware consumes and executes one command at a time via the USB serial connection to the Processing sketch. However, it is not clear to me whether the Arduino sketch does any further processing or work with these commands. For example, the Arduino sketch contains many loose references to functions related to 'roving'. What ends up actually determining what a pixel will end up looking like on the physical canvas, the Processing app or the Arduino firmware?

I feel like I have a great deal more questions, since I'm having a really hard time following the code at the moment. But hopefully once I understand the core theory of how pixels are analyzed and created, I can understand more things in the process.

sandy
Administrator
Posts: 1317
Permalink
sandy
Post Re: Understanding the flow from image to action
on: January 16, 2012, 11:45
Quote

Hey zenwebb, I'll help if I can.

You're right it's somewhat fractured, particularly the server code, and no documentation. You should be working from the most recent codebase though - there's no roving in the new code, and the new controller code is much more clearly separated by function (model / presentation).

Roving was actually the feature that I was trying to use the SD card with - I had it just drawing endlesslessly from one random point to another, great big swirls and wiggles, quite impressive actually, but it was planned that it would be continuously calculating the density under the pen, and dropping or raising it (the pen) accordingly - so that the image emerged from scribbles and gradually became more and more distinct.

I'll briefly describe the elements of the program:
Controller
polargraphcontroller_zoom.pde - This is concerned with drawing the contents of each tab, and all the serial communication between board and computer, as well as the "raw" parts of the keyboard and mouse input.

tabSetup & controlsSetup - Contains all the code to initialise the controls (buttons, numberboxes, toggles etc), setting max, min values, initial values.

controlsActions - These are the methods that get hit when any control is activated, the name of the method is the same as the name of the control. That is, pressing the button with the name "button_mode_renderSquarePixel" will hit the method button_mode_renderSquarePixel() in this.

Rectangle - This is a helper class that describes a rectangular area, along with .getWidth(), getLeft(), surrounds() type accessor methods.

Panel - This is a class that can hold a collection of controls, along with their positions and sizes. At least one of these Panels is created for each tab.

Machine - This is a class that models a polargraph machine, this is the essential part of the application. It has a couple of properties - machine size, page size, image size, position etc, along with a couple of utility methods like asNativeCoords(), asCartesianCoords(), inMM(), inSteps(). It also contains the algorithm that converts from cartesian -> polargraph coords, and the root method for extracting all the pixels.

DisplayMachine - This subclasses Machine and is a kind of machine that knows how to draw itself on screen, translate on-screen positions to on-machine positions.

drawing - This contains most of the code that builds the ASCII messages that get put into the queue, including working out the order that big sets of pixels should get sent in (which corner to start in, what direction to go in etc). Most of the methods are called "send...()" because they send the commands.

I have been fairly sloppy about globals here, because the Processing IDE treats everything as one big class anyway. I have tried to encapsulate where possible, but it's all a bit of a moot point with Processing - it's just not made to do that. You might find it easier to read if you've loaded it into a more featured environment like eclipse that has method trees and will let you click through the method calls.

You've almost got the process right, but the image doesn't come first. The main "thing" that the controller does is hold an instance of Machine - a model of a polargraph machine. It is the logical model. All the rest is presentation really.

The main pixelly method here is Machine.getPixelsPositionsFromArea() that returns a Set<PVector> containing the cartesian positions of all the polargraph pixels in the area specified.

  /**      This takes in an area defined in cartesian steps,
and returns a set of pixels that are included      
in that area.  Coordinates are specified       
in cartesian steps.  The pixels are worked out       
based on the gridsize parameter. d*/    
Set<PVector> getPixelsPositionsFromArea(PVector p, PVector s, 
float gridSize, float sampleSize)    
{      
  // work out the grid      
  setGridSize(gridSize);      
  float maxLength = getMaxLength();      
  float numberOfGridlines = maxLength / gridSize;      
  float gridIncrement = getMaxLength() / numberOfGridlines; 
  List<Float> gridLinePositions = getGridLinePositions(gridSize);       
  Rectangle selectedArea = new Rectangle (p.x,p.y, s.x,s.y); 
  // now go through all the combinations of the two values.
  Set<PVector> nativeCoords = new HashSet<PVector>();
  for (Float a : gridLinePositions) 
  {        
    for (Float b : gridLinePositions)
    {
      PVector nativeCoord = new PVector(a, b);          
      PVector cartesianCoord = asCartesianCoords(nativeCoord);
      if (selectedArea.surrounds(cartesianCoord))          
      {
        if (!isChromaKey(cartesianCoord))
        {
          if (sampleSize >= 1.0)
          {
            float brightness = getPixelBrightness(cartesianCoord, sampleSize);
            nativeCoord.z = brightness;
          }
          nativeCoords.add(nativeCoord);
        }
      }
    }
  }
  return nativeCoords;    
}  

So rather than iterating through pixels of the image, this iterates through a grid of possible polargraph pixel positions, and for each one works out the cartesian position. The grid is based on the gridSize (formerly rowSize). Once the cartesian position has been worked out, then the ones that fall outside the desired area are chucked out. When the pixel falls on top of the image, then the brightness of the underlying image pixel is extracted (or an average of a few pixels). I should really do brightness(pixelColor), but I do red(pixelColor) instead. Then I put that brightness value into a PVector along with the native positions. I use the z value to store the brightness, and the x and y to store the a-string-length and b-string-length (distance from motor A and distance from motor B).

This is basically re-rasterising in the new coordinates system.

DisplayMachine has something very similar but it re-examines the set produced above and converts it all to the cartesian coords and scaled it to be displayed on the screen.

When you hit "render square" or somesuch, it fires a method in controlsAction:

void button_mode_renderSquarePixel()  
{
  if (isBoxSpecified())
  {
    // get the pixels
    Set<PVector> pixels = getDisplayMachine()
.extractNativePixelsFromArea(getBoxVector1(), 
getBoxVectorSize(), getGridSize(), sampleArea);
    sendSquarePixels(pixels);
  }
}

Which hits up DisplayMachine to get the pixels from the selected area in the native machine coords. It then sends this big collection of pixels (PVectors) to sendSquarePixels() which is in the drawing file.

void sendSquarePixels(Set<PVector> pixels)
{
  sendPixels(pixels, CMD_DRAWPIXEL, DRAW_DIR_SE, getGridSize(), false);
}

Which itself hits the generalised pixel sorting/sending method sendPixels(). This sorts the pixels into rows, then sorts each row alternately forwards and backwards, and eventually iterates throught this sorted collection of pixels and for each one, adds a new command to the command queue.

The command queue is just as you've seen - simple. Actions you do in the controller cause commands to be added to the end of it, and the hardware asks for them one by one. It can be paused or cleared or saved or loaded.

What the pixel looks like on the paper is decided entirely by the firmware. The controller says "use a specific algorithm to render a patch with x brightness, make it y steps wide, and place it at z position", but It doesn't know any more than that. The controller can see the big picture, but doesn't know how the details will be rendered.

The hardware can only work on one command at a time, so it can't look ahead or really do any processing. It knows the pen's current location and size, and it knows where it's next task is, and works out how many waves can fit into that area to get the desired brightness and what direction it needs to go to get there.

What I'm working on now is making the hardware modal - so I can switch it into a "buffering" mode and it'll then store the commands locally rather than executing them immediately. Switching to "play" mode will start drawing from it's internal memory. No reason why this is a challenge, but I'm reluctant to build features that don't work on the regular ATMEGA328s.

Let me know if there's anything I can illuminate, cheers!
Sandy Noble

Pages: [1]
Mingle Forum by cartpauj
Version: 1.0.34 ; Page loaded in: 0.027 seconds.