PinholePainter
Monday, December 17, 2007
The website has been overhauled.
posted by Amanda @ 12/17/2007 02:29:00 AM,
,
PinholePainter Camera
Saturday, December 15, 2007
The PinholePainter Camera has a website, albeit still under a bit of construction. Tomorrow is the winter show, and though I wish the camera could be in it, will be amazing nonetheless.
posted by Amanda @ 12/15/2007 08:09:00 PM,
,
Wednesday of this week was the last day of PComp, and the semester has come to a close. Chris, Eddie and I presented the PinholePainter camera, which is not yet in working order. We worked really hard to get to where we did, and a lot of the components of the project are working, but not the key image creation component.
We have decided to continue with the project into next semester. The steps we are planning/thinking of pursuing are:
- We need a few new multiplexers - the one on our board got fried yesterday
- Need to rewire the sensor (photocell) head. Our wiring needs to be in better order as we continue.
- Photocells v. Photodiodes - need to do some experiments
- Use a lense? If so, what kind - convex-convex?
- Flood a room with ambient type light / work with different lights and reflectors
- Resistors! We haven't come to agreement on base resistors yet, and they seem to make a big difference in the sensitivity of photocells.
---
On the software side, we are interested in writing a new program that represents the additive nature of film a little bit. For the moment, though, we are trying to focus on getting any meaningful values in a light-proof environment.
We decided that we aren't far enough along to present in the show this weekend which was, of course, disappointing. We are taking the weekend off from the project to get a little perspective and rest. Going to continue talking about it next week.
The other projects that were presented in class this week and last were pretty amazing.
Will, Matt and Sunghun made a 3D visualization system:
3D
3D
Kacie and Tom's Morgen, the Networked alarm clock
Petra's Museum Talk
posted by Amanda @ 12/14/2007 11:59:00 PM,
,
Pinhole Painter Camera
Wednesday, December 12, 2007
I changed the concept for my final twice. In the end, because it ended up being more programming heavy than we had originally intended, I combined my PComp and ICM finals - working solely on the Pinhole Painter Camera with Chris Cerrito and Eduardo Lytton.
Our development Wiki can be found here:
http://pcomp.wetpaint.com/
The Pinhole Painter Camera is a lo-fi digital camera intended to combine aspects of digital and film photography. Our team was interested in the affordability and accessibility of digital imaging, but also think that it brings up some interesting concerns.
Our goals were to:
Learn more about programming
Learn more about sensors and electricity
Understand the basic ways in which cameras (and ccds) work
Bring serendipity and the unexpected to digital photography
Play with the ways in which users can become more involved in picture taking
Add a time element to digital picture taking - in early photography, taking a picture took a long time. Our thinking was that this commitment to the time that photographing took informed the experience of image capture.
Our process began with a lot of discussion around photography and the aspects of it that we appreciate in both film and digital. We agreed that we appreciate the unknowing of film photography, as well as the time it takes to craft an image and to receive the endeavor's results.
We approached the process from a number of different ways in the beginning. We were unclear about whether we would hack an existing digital camera, create a camera obscura and place a digital camera inside of it, or hack a scanner. In the end we agreed to create our own rudimentary ccd out of a grid of 16 (4x4) photocells. The image plane would be projected through the pinhole, onto the image plane (as an inverted image) and be recorded as light values (0-255) by an array of photo cells. Instead of having a grid of 1000 or so photo cells, we decided to use only sixteen, and to allow the user to have a part in moving that sensor around to capture the full image in the way that they want.
As the project stands, it leaves room for adding color values to the light readings by switching color filters in and out (R,G,B) and composing the images out of those values using processing.
The programming in this project was three-fold really. After crafting the array of photocells, we needed to write a program in Arduino that would send values to processing. The program is constantly sending sets of 18 ASCII values to processing. They are delineated twice, by an asterisk separating the photocell values from the IR readings, and then by commas separating the photocell values themselves. The Arduino program takes five readings for the IR values and prints the average in DEC, to be read by processing.
The Arduino code is below:
//ARDUINO CODE -- ASCII SENDER
int val[16];
int analog1[5];
int h, j;
long average1;
int analog2[5];
int p, q;
long average2;
int a= 3; // SELECT PIN A0 GOING INTO PIN 2
int b= 4; // SELECT PIN A1 GOING INTO PIN 3
int c= 5; // SELECT PIN A2 GOING INTO PIN 4
int d= 6; // SELECT PIN A3 GOING INTO PIN 5
//we are using three analog pins
int analog0 = 0;
//int analog1 = 0;
//int analog2 = 0;
void setup() {
Serial.begin(9600);
pinMode(analog0, INPUT);
pinMode(a,OUTPUT);
pinMode(b,OUTPUT);
pinMode(c,OUTPUT);
pinMode(d,OUTPUT);
//pinMode(analog1, INPUT);
//pinMode(analog2, INPUT);
}
void loop() {
for (int i = 0; i < 16; i++) {
// determine the four address pin values from i:
// mask off bit 0:
int pinOne = 1 & i;
// shift value 1 bit to the right, and mask all but bit 0:
int pinTwo = 1 & (i >> 1) ;
// shift value 2 bits to the right, and mask all but bit 0:
int pinThree = 1 & (i >> 2);
// shift value 3 bits to the right, and mask all but bit 0:
int pinFour = 1 & (i >> 3);
// set the address pins:
digitalWrite(a,pinOne);
digitalWrite(b,pinTwo);
digitalWrite(c,pinThree);
digitalWrite(d,pinFour);
// read the analog input and store it in the value array:
val[i] = analogRead(analog0);
//val[i] = i;
//Serial.print(val[i], DEC);
if (i < 15) {
//Serial.print(",");
//For IR 1
analog1[(h++ %5)] = analogRead(1);
average1 = 0;
for (j =0; j<5; j ++){
// add samples
average1 += analog1[j];
}
average1 = average1 / 5;
//For IR 2
analog2[(p++ %5)] = analogRead(2);
average2 = 0;
for (q =0; q<5; q ++){
// add samples
average2 += analog2[q];
}
average2 = average2 / 5;
//End Averaging
}
}
Serial.print("*");
Serial.print(average1, DEC);
Serial.print(",");
Serial.println(average2, DEC);
}
--------------------------------------------
The Processing program had several incarnations throughout the project. The first program was coded using hard numbers and calling each separate place in the array. The x & y positions of the large "pixels" (which were really 20x20) were coded using sensorValue1 and sensorValue2 as x and y respectively, and then offsetting each subsequent value by 20 pixels to create the grid effect. The results of this program were fine and the coding was simple, but far from dynamic. If we had wanted to re-scale the image or change the pixel size, we would have needed to change all of these values each time. It was important and helpful to begin in this way because it helped us to identify each separate cell and to know exactly when we were calling the value, dividing it, assigning it a variable, etc.. The later iteration of the program uses object orientation and arrays to achieve a similar visual effect, while being more dynamic and scalable.
The Processing code as it stands now is below (still in progress):
---
The A-Mazing Pinhole Painter Camera Processing Code ! ! ! !
Project by Amanda Bernsohn, Chris Cerrito, Eduardo Lytton
Fall 2007 ITP
// 3 arrays: PicPixArray (the big picture), cursorArray (our photosensor cursor), and vals
// range of values coming in from our photo sensors
import processing.serial.*;
Serial myPort;
//Declare your main string5
String inString;
boolean gotFirstString = false;
int cols = 40; //# of columns
int rows = 40; //# of rows
int videoScale = width/cols; //resolution of the overall picture grid - how wide a "bigPixel" is
int[] vals = new int[16]; //Arduino array of gray values coming in serially
int irOne; //IR sensor value for X
int irTwo; //IR sensor value for Y
int []pots; //the string leftover, what will contain IR x,y values
Cell[][] PicPixArray; //2D array of objects -- the overall grid of our image plane
Cell[][] cursorArray; //2D array of objects -- the "cursor" array
int gridScale; //resolution of the overall picture grid - how wide a "bigPixel" is
int gridCols = 4; //Number of columns in the grid
int gridRows = 4; //Number of rows in the grid
//int grayGridMasterValue = 5; //for NON-SERIAL COMMUN
//PImage img;
void setup() {
size (800, 800);
background(255);
println(Serial.list());
myPort = new Serial(this, Serial.list()[0], 9600);
//set a buffer - serialEvent will not happen until \n (new line feed)
//so serialEvent will always start on the first of our values
myPort.bufferUntil('\n'); // SERIAL COMMUNICATION
gridScale = width/cols;
cursorArray = new Cell[gridCols][gridRows];
//this is where we initialize the cursorArray a.k.a "cursor" or "photo sensors array"
for (int i = 0; i < gridCols; i++) {
for (int j = 0; j < gridRows; j++) {
//intialize each object
//int locLinearArray = i+j*4;
cursorArray [i][j] = new Cell(i*gridScale,j*gridScale,gridScale,gridScale,-1);//values[locLinearArray]);
//cursorArray[i][j].getArduinoValues();
//print(values[i] + "\t");
}
}
PicPixArray = new Cell[cols][rows];
//this is where we initialize the PicPixArray a.k.a "image plane" or "the big picture"
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
int x = i*gridScale; //x position
int y = j*gridScale; //y position
int tempbigPixelGray = 150;
PicPixArray [i][j] = new Cell(x, y, gridScale, gridScale, tempbigPixelGray);
//PicPixArray [i][j].fillinPicPix();
}
//img = createImage(width,height,RGB);
}
//failed attempts / other ideas below
// PicPixArray[x][y] = values[(x*4) + y];
//PicPixArray [values [16] / 10 + x][values [17] / 10 + y] = cursorArray [x][y];
}
void serialEvent(Serial myPort) {
String myString = myPort.readStringUntil('\n');
if(myString != null) {
myString = trim(myString);
//println(myString);
//split your longer string, using the asterisk as a delimiter, creating two smaller strings
String[] strings = split(myString,'*');
if (strings.length > 1) {
//make an array of vals by splitting the first string wherever there is a commma
//you know it's the first string cause it's called strings[0]
vals = int(split(strings[0], ','));
//do the same things for the pots, split the second string up
//wherever there is a comma
pots = int(split(strings[1], ','));
//irOne is a variable, whose value is going to be the first value from the pots array
irOne = pots[0];
//irTwo is a variable, whose value is going to be the second value from the pots array
irTwo = pots[1];
//same thing here with val0, etc... they will eventually be val0-val15, which are variable
println(vals + "\t");
for(int i = 0; i < vals.length; i++){
vals[i] = vals[i]/4;
print(vals[i] + "\t");
}
println();
println("IRS");
println(irOne + "\t" + irTwo);
println();
//then print whichever cell values you would like to look at
println(vals[0] + "\t" + vals[1]);
}
}
}
//void keyPressed() {
void mousePressed() {
// img.save("test.jpg");
}
void draw() {
// background (0);
//irOne = mouseX; //change to include IR sensor value array value [16] - x
//irTwo = mouseY; //change to include IR sensor value array value [17] - y
//img.loadPixels();
noStroke();
for (int i = 0; i < gridCols; i++) {
for (int j = 0; j < gridRows; j++) {
int locLinearArray = i+j*4;
cursorArray [i][j].setArduinoValue(vals[locLinearArray]);
//cursorArray [i][j].setArduinoValue(150);
cursorArray [i][j].setPixel();
}
}
//img.updatePixels();
//image(img,0,0);
for (int i = 0; i < gridCols; i++) {
for (int j = 0; j < gridRows; j++) {
cursorArray [i][j].displayCursor();
}
}
/* for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
PicPixArray [i][j].display();
}
}*/
/*
for (int i = 0; i < gridCols; i++) {
for (int j = 0; j < gridRows; j++) {
int locLinearArrayCursor = i+j*4;
vals [i] = ((vals[locLinearArrayCursor])/4);
//this is the line that needs to be change to receive arduino bytes!
//vals[i] = int(random(255));//to test NON-SERIAL COMMUNICATION
// vals[i] = int(random(255)); //SERIAL COMMUNICATION
}
}*/
}
========
THE "CURSOR CELL" TAB
//a cell object
class Cell {
float xcell,ycell; //x,y location
float w,h; //width and height
int grayGridValue; // gray value for fill for each cursor Cell
//cell constructor
Cell(float tempxcell, float tempycell, float tempW, float tempH, int tempGrayGridValue) {
xcell = tempxcell;
ycell = tempycell;
w = tempW;
h = tempH;
grayGridValue = tempGrayGridValue;
}
void setArduinoValue(int temp) {
grayGridValue = temp;
}
void setPixel() {
//rect(irOne + xcell, irTwo + ycell,gridScale,gridScale); //replace mouseX with irOne value
/*int x = irOne + int(xcell)/gridScale;
int y = irTwo + int(ycell)/gridScale;
int loc = x+y*width; // NOT CORRECT
img.pixels[loc] = color(grayGridValue);*/
PicPixArray[int(xcell+irOne)/gridScale][int(ycell+irTwo)/gridScale].setArduinoValue(grayGridValue);
}
//void fillInPicPix() {
// //PicPixArray[i][j] = PicPixArray[irOne / videoScale + x][irTwo/ videoScale + y];
//
void displayCursor () {
noStroke(); //color and move to mouse position
color c = grayGridValue;
fill(c);
rect(irOne + xcell, irTwo + ycell,gridScale,gridScale); //replace mouseX with irOne value
}
void display () {
noStroke(); //color and move to mouse position
color c = grayGridValue;
fill(c);
rect(xcell,ycell,gridScale,gridScale); //replace mouseX with irOne value
}
}
Several images documenting our process and our results can be found below:
One of Chris Cerrito's early drawings of the concept.

We started with five photocells on the board, using analog ins, but no multiplexer.

Building the photocell array (something we ended up doing in its entirety twice, and are still tweaking)
We needed to use a multiplexer to increase the number of analog ins on the Arduino. we used one, which gave us an additional 15 analog ins, and only used one analog pin (and four digital). The code for the MUX (which involved bitshifting) ended up taking several days to work out.

We tried a number of different sensors to track the x and y positions of the cells inside the camera body. We ended up using IR sensors, one of which is shown below.

An early screenshot from the processing program, using ellipses with brightness values corresponding to available light. This is unfocused, ambiant light and the values are quite dark.
posted by Amanda @ 12/12/2007 02:29:00 AM,
,
Progress Update
Thursday, December 6, 2007
This week we have gotten a lot done on the camera, including hooking up all the sensors, writing the Arduino code to read through the multiplexer (16 values) and two IR sensors. We wrote a basic processing program (with Tom helping us sort out the best method for the serial communication), and things are up and running in a very basic way.
posted by Amanda @ 12/06/2007 02:44:00 PM,
,
Lessons
Monday, December 3, 2007
Last week in class we touched briefly on some of our readings. Though ultimately the discussion turned in the direction of how people perceived the class more generally.
I found the readings in the course pack helpful. I came to ITP without having ever considered what one's priorities should be in designing a product or system. From the very beginning, in reading about the design of everyday things, I benefitted from the text.
On a practical note, I would say that there are some very basic things I try to keep in mind at this point when considering the user. Here are some of the lessons I took in during the semester:
Let them know the thing is on
Newness - if the interface is completely novel, incorporate elements that will make it understandable
Duration - or, that there should be a narrative arc to use (e.g., touch, something happens, stop touching).
Feedback is important
Is what you are doing related in any way to what is goign to happen? (does pushing 'up' on an elevator button imply that you will go up?) what motion, if any, would be more appropriate?
There is power in serendipity and the unexpected (Trash talk)
There is power in the beautiful
Sometimes simplicity is just as robust a tool as complexity
Beware of feature creep
And of course, though not about the user, Tom's rule #3 of love and networking - always listen more than you talk.
posted by Amanda @ 12/03/2007 09:27:00 PM,
,
Serially?
Sunday, December 2, 2007
Today was incredibly frustrating, but we made some small strides. I managed to get all 16 of the MUX values, and the additional IR analog values reading through Arduino. I couldn't, however, get the values to run properly through Processing.
Here's my Arduino code. In an attempt to stop any extraneous values from coming into the processing program, I commented out all of the printlns that I could.
*****
int val[16];
int a= 3; // SELECT PIN A0 GOING INTO PIN 2
int b= 4; // SELECT PIN A1 GOING INTO PIN 3
int c= 5; // SELECT PIN A2 GOING INTO PIN 4 //variables in this code have been changed from their original value of Int
//to BYTE.
int d= 6; // SELECT PIN A3 GOING INTO PIN 5
int analog0 = 0;
//NEW INFORMATION
int analog1 = 0;
int analog2 = 0;
void setup() {
Serial.begin(9600);
pinMode(analog0, INPUT);
pinMode(a,OUTPUT);
pinMode(b,OUTPUT);
pinMode(c,OUTPUT);
pinMode(d,OUTPUT);
//NEW INFORMATION
pinMode(analog1, INPUT);
pinMode(analog2, INPUT);
}
void loop() {
for (int channelNum = 0; channelNum < 16; channelNum ++) {
// determine the four address pin values from the channelNum:
// mask off bit 0:
int pinOne = 1 & channelNum;
// shift value 1 bit to the right, and mask all but bit 0:
int pinTwo = 1 & (channelNum >> 1) ;
// shift value 2 bits to the right, and mask all but bit 0:
int pinThree = 1 & (channelNum >> 2);
// shift value 3 bits to the right, and mask all but bit 0:
int pinFour = 1 & (channelNum >> 3);
// set the address pins:
digitalWrite(a,pinOne);
digitalWrite(b,pinTwo);
digitalWrite(c,pinThree);
digitalWrite(d,pinFour);
// read the analog input and store it in the value array:
val[channelNum] = analogRead(analog0)/4;
// as a unique value to punctuate the sentence:
//if (val[channelNum] == 255) {
//val[channelNum] = 254;
//}
// print the values as a single tab-separated line:
//Serial.print("VALUE");
//Serial.print(val[channelNum], BYTE);
//Serial.print("\t");
//Serial.println();
//delay(5);
}
// print a carriage return at the end of each read of the mux:
//Serial.println();
//NEW INFORMATION
analog1 = analogRead(1)/4;
analog2 = analogRead(2)/4;
if (analog1 == 255) {
analog1 = 254;
}
//Serial.print("AN1 ");
Serial.print(analog1, BYTE);
// if (analog2 == 255) {
// analog2 = 244;
//}
//Serial.print("AN2 ");
Serial.print(analog2, BYTE);
//Serial.print("Done");
//Serial.println();
//Serial.print(255, BYTE);
delay(10);
//Serial.println();
//Serial.print("Start ");
}
****
Initially I was using the punctuation method for Processing/Arduino and then I tried call and response. I'm hoping that sleeping on it will give me an advantage tomorrow.
The val array is for the first 16 MUX values (on analog pin0), then there are an additional 2 values for analog1 and analog2. The last, 19th, value is for the punctuation -- in this case 255, or 1023 divided by 4.
I can't seem to get processing to read the values in any meaningful way though. I can see, in the serial port, hat the values are coming through accurately and in order. In processing I have an array of 19 values.
posted by Amanda @ 12/02/2007 09:41:00 PM,
,