Mechanisms Midterm Concept: TagBot
Some of the most interesting mechanical solutions seem to have emerged from the transition period between manual and automated means of production — the process of adapting tasks long performed by hand to operate under machine power for the first time. The initial iterations of the adaptation process usually result in endearingly anthropomorphic machines, since the process of abstracting human motions out of the process isn’t yet complete. (Examples include electric typewriters, sewing machines, automated assembly lines, etc.)
I’m interested in working through this process of converting hand power to mechanical power myself. A Dymo tapewriter represents an unlikely but possibly satisfying platform to turn into an automatic, electronic device.
I’m also interested in unintended and unknown physical consequences for actions taken online. The stream of new tag data on sites like Flickr could provide interesting source text, and would force the idea of a tag into the physical world – e.g. here’s a machine that involuntarily spits out sticky pieces of tape with letters on them that could, conceivably, tag real-world objects.
Thus, the TagBot. A mechanized, automatic Dymo tapewriter which scrapes new tag data from Flickr in real time, and generates labels accordingly.
A slightly more ambitious variant could be built with mobility in mind — you could position it somewhere in the city, and it would spit out tags from photographs taken in the vicinity.
Mechanically, several factors need to be accounted for:
- Rotation and indexing of the character wheel — a stepper motor would probably suffice.
- The space — a light squeeze on the handle advances the tape without printing a letter. A strong motor or solenoid could manage this.
- Character printing — a harder squeeze on the handle.
- Cut — a hard squeeze on another lever.
- Pull — a motor to pull the finished tag out of the printer.
- Tape reloading — Dymo tape rolls are pretty short, some kind of automated reloading system would be great, but probably beyond the scope / time available for the midterm.
Code and control will require the following:
- An Arduino to coordinate the motions of each mechanical element.
- An interface with the Flickr API to fetch the latest tags. (Either serially from a laptop, or directly over a WiShield.)
- Code to reduce the character set to those present on the character wheel.
Line Weight
NOC Midterm Concept: A physics-based drawing tool.
POS Shuffler
Homework #2: The digital cut-up. Write a program that reads in and creatively re-arranges the content of one or more source texts. What is the unit of your cut-up technique? (the word, the line, the character? something else?) How does your procedure relate (if at all) to your choice of source text? Feel free to build on your assignment from last week.
I wanted to build a cut-up machine that was as grammatically and syntactically non-invasive as possible, while still significantly munging the source text. So I decided to shuffle the text in a way that treated parts of speech as sacred — the words could fall where they may, but if the first word was a noun in the source text, it had better be replaced with a noun in the output. If the second word was a verb, nothing but a verb from elsewhere in the text should take its place, and so on.
Programmatically deriving a word’s part of speech turns out to be a major issue, so I leaned on NLTK to take care of this. It actually does a pretty decent job. From there it’s just a matter of storing lists of the words in a dictionary keyed to each part of speech, shuffling them, and then reconstituting the text.
I ran Obama’s most recent state of the union address through my algorithm. It’s formal enough and carefully constructed enough so as not to pose a significant challenged to NLTK’s Brown Corpus trained part of speech tagger. Also, I was struck by how much the output resembles the famous Bushisms, albeit with a lingering Obama-esque tinge.
Here’s some output:
Bloody Biden, succeed. Run Constitution, convictions from America, were moments, But great Allies: tested. union. declares during on time to look, the prosperity must time to union. history of the struggle at our For Sunday one leaders, our people have fulfilled all Bull Union chose done so of hesitations despite progress And America and we are turned again on the war that call and midst; in marchers as inevitable anything and fellow state. they’s tempting to move very of the guests and president at our victory distinguished civil, that President were back done to Tuesday and when the Beach was tested back about Our Omaha but these Americans great beaten that duty. Congress, nation crashed forward so of These Madame the future was of certain. tranquility. And first years periods was landed because doubt. Again, the depression was assume and Congress Speaker was rights on destined the market of our moments and the courage in our Black and at this our fears and divisions, our disagreements and our members, Vice prevailed that we have to answer always of one strife And 220 times.
They, It have When and much, we shall give information’s strength.
Sample output from the full text of Obama’s 2010 state of the union is also available here. The original text is also available for comparison.
The source code follows. Pretty simple, NLTK does most of the heavy lifting.
- import sys
- import nltk
- import random
- import re
- # Grab a file from standard input, dump it in a string.
- source_text = sys.stdin.read()
- # Use NLTK to make some guesses about each word’s part of speech.
- token_text = nltk.word_tokenize(source_text)
- pos_text = nltk.pos_tag(token_text)
- # Set up a dictionary where each key is a POS holding a list
- # of each word of that type from the text.
- pos_table = dict()
- for tagged_word in pos_text:
- # Create the list, if it doesn’t exist already.
- if tagged_word[1] not in pos_table:
- pos_table[tagged_word[1]] = list()
- pos_table[tagged_word[1]].append(tagged_word[0])
- # Scramble the word lists.
- for pos_key in pos_table:
- random.shuffle(pos_table[pos_key])
- # Rebuild the text.
- output = str()
- for tagged_word in pos_text:
- # Take the last word from the scrambled list.
- word = pos_table[tagged_word[1]].pop()
- # Leave out the space if it’s punctuation.
- if not re.match("[.,;:’!?]", word):
- output += " "
- # Accmulate the words
- output += word
- # Remove white space.
- output = output.strip()
- print output
Vinyl as Visualization
A vinyl record, magnified. From Chris Supranowitz’s OPT 307 Final Project.
One of Arthur C. Clarke’s laws of prediction states that “any sufficiently advanced technology is indistinguishable from magic.” There’s something bootstrappy about one sufficiently advanced technology (SEM) laying bare the magic from a formerly advanced technology (Vinyl). In this case, to see the waveform etched in the vinyl is to understand how the medium works in a more-than-conceptual way. No magic required.
Yet the magnifier doesn’t shed the same revelatory light on a compact disc. There’s another layer of abstraction — and it’s arguably beyond visualization. (Still, it’s unusual treat to see the atoms behind those etherial bits… given our tendency to segregate the two.)
Via Noise for Airports.
Weight of Your Words
Assignment: Use a physics library.
Physics libraries like Box2D tend to use extremely rational language in extremely literal ways (mass, friction, gravity, etc.) — I wanted to build on this language by overloading its meaning and taking it in an absurdist direction. Electrons, in the quantities pushed around by our machines, certainly don’t carry much physical weight… how, then, can we weigh a string of characters?
Google seems to have this sorted out… just about any conceivable string of text can be quantified, weighed, and perhaps valued by the number of results it dredges up.
So I whipped up an app that literally uses Google’s search result count to determine how game elements behave — with the intention to pressure a player into testing their own judgment of the worth of a word against Google’s. It looks like this:
How does our understanding of how much a word weighs depart from Google’s absolutism? How much weight can you balance?
The game mechanics are pretty basic… Box2D manages the interactions between the words and the tilting ledge below. The ledge is attached to a joint in the middle of the screen, and if words are distributed unevenly it will tilt and send terms sliding into the abyss.
The cloud floating above has a text input box which sends search queries off to Google. A bit of code scrapes the resulting HTML to figure out how many results exist for the query. This runs in its own thread so as not to block the rendering / physics work. After the query count comes back from google, the term you entered drops from the cloud onto the ledge. (You can influence where it will land by positioning the cloud with the mouse beforehand.) The more results a term has, the higher its density — this means that major search terms will help you load extra weight on the ledge, but their extra mass also means they’re more likely to tilt the ledge past the point of recovery. This, I hope, forces the player to estimate the weight of a term before they drop it from the cloud.
Here are a few more screenshots from development and testing:
The game doesn’t work very well as an Applet (it uses the net library, which requires code signing), so it’s probably easiest to download the zip below if you’d like to give it a try.
The source is a bit long to embed here, so I’ve attached it below.
Blender != CAD
Assignment: Spend a handful of hours evaluating your assigned 3D modeling software (Blender)
Blender doesn’t seem like a reasonable substitute for Alibre or Solidworks… I’m not even sure CAD suites are on the radar of competing products for the Blender Dev team. Instead, they’re positioning Blender as an alternative to Maya or 3Ds Max, and extending functionality into game design while basic CAD functionality (measurements, assemblies, alignment) is neglected.
Still, it represents a huge amount of open-source work towards the basic infrastructure required by both 3D Modeling / Animation tools and 3D Drafting / Design tools. Several efforts have been made to implement CAD functionality within Blender, but none of them seem mature enough for production work (or even sustained dabbling).
Of all the small side-projects and forks attempting to take Blender in a more CAD-like direction, the only one with any enduring momentum seems to be BlenderCAD — which proved too unstable for completing even basic work.
This pretty much describes the experience:
I wish the developers luck, though, since the lack of serious open-source CAD tools is a shame. Something Blender-based is probably the last great hope.
Interstitial Wasteland
Homework #1. Create a program that behaves like a UNIX text processing program (such as cat, grep, tr, etc.). Your program should take text as input (any text, or a particular text of your choosing) and output a version of the text that has been filtered and/or munged. Be creative, insightful, or intentionally banal.
Choose one text that you created with your program to read in class.
Bonus: Use the program that you created in tandem with another UNIX command line utility.
I try to avoid destroying data. I draw upon the usual set of justifications: Storage is only getting cheaper, an empty HD occupies the same physical space as a full HD, yadda yadda.
Whether or not this policy is for the best, it’s left me with over a million lines of instant messenger conversation logs from my earlier years — mundane, personal conversations between myself and a handful of friends. (Running from about Jr. High to end end of High School.) If not of much general interest, the contents of these (often painfully angst-ridden) logs are a personally surreal thing to revisit.
In response to the first assignment, I wanted to draw from this well of text in some way. I’m particularly interested in the idea of accidental art — the daily, unintended collisions with things that might be formally construed as art.
I wrote a quick algorithm to put my variation of the Infinite monkey theorem to the test. Can enough angsty teens, given enough time to type, eventually produce something resembling a traditionally recognized “great work”?
I decided to pit my adolescent conversations against T.S. Eliot’s The Waste Land. I wasn’t interested in simply recreating the poem verbatim, instead I used the first and last words of each line as anchors between the poem and my logs, and anything in the middle would be filled in from my conversations based on nearby words.
So, the algorithm takes the first and last word from each line of the poem, and then looks for matches in my conversation logs. If it finds a match for both words in my logs, then it will walk forward from the first word, and backward from the last word, to create a line of text which starts and ends identically to a line in The Waste Land.
Finally, if the resulting line is too long, it will cut words out of the middle until the length of the output line matches the length of the line in The Waste Land. Currently, lines that are shorter than their equivalents in the original poem are just printed as-is. (It would be nice to find a reasonable way to beef these up to size.)
When matches aren’t found, the line is dropped. Only about 60% of the poem could be reconstructed from my million lines of conversation text. (e.g., words like “abolie” never turned up in my logs, and therefore were not available to reconstruct that line of The Waste Land.)
The code is happy to work with any plain text files. Supply it with a model text (in this case, Eliot’s poem) and a source text (in this case, my conversation logs), and it will do its best to shape the source text into the model text.
The first argument to the program is the source text, and the second is the model text. For example, from the command line, this would use aim.txt as the source and wasteland.txt as the model, and save the results to a text file named aim-wasteland.txt:
python interstitial-wasteland.py aim.txt wasteland.txt >> aim-wasteland.txt
It takes a while to run, and you won’t get decent results unless the source text is huge.
Here’s the full output: interstitial-wasteland-output.txt
And the output with the original poem in parallel: interstitial-wasteland-output-parallel.txt
A small excerpt of the raw output:
I… wasn’t going us, he’s DEAD!
April one is designed for inbreeding,
Memory Lane and stirring
Winter Olympic Games. of recovering
Earth, yet we and feeding
And let me tell for like an hour..by
Individuals can be dangerous though
I”m confused http://winter.squaw.com/html/squawcambig.html-What are the current problems problems?- it grows
Out man,
The same excerpt in parallel with the model text:
I. THE BURIAL OF THE DEAD
I… wasn’t going us, he’s DEAD!April is the cruellest month, breeding
April one is designed for inbreeding,Memory and desire, stirring
Memory Lane and stirringWinter kept us warm, covering
Winter Olympic Games. of recoveringEarth in forgetful snow, feeding
Earth, yet we and feedingAnd drank coffee, and talked for an hour.
And let me tell for like an hour..byIn the mountains, there you feel free.
Individuals can be dangerous thoughI read, much of the night, and go south in the winter.
I”m confused http://winter.squaw.com/html/squawcambig.htmlWhat are the roots that clutch, what branches grow
-What are the current problems problems?- it growsOut of this stony rubbish? Son of man,
Out man,
And the source code:
- import sys
- args = sys.argv
- # I hard coded these for my local testing.
- #args = [‘self’, ‘aim.txt’, ‘wasteland.txt’]
- # Set to true if you want extra output for debugging.
- # TK turn this into a command line parameters.
- verbose = 0
- # Set to true if you want to show the original line above the munged one.
- # TK turn this into a command line parameters.
- print_original = 0
- if verbose: print args
- if verbose: print ‘Take the text from ‘ + args[1] + ’ and model it after ‘ + args[2]
- # Pull the filenames from stdin.
- source_file_name = args[1]
- model_file_name = args[2]
- # Open each file. (Error handling would be good here…)
- source_file = open(source_file_name, ‘r’)
- model_file = open(model_file_name, ‘r’)
- # Read each line of each file into a list.
- source_lines = source_file.readlines()
- model_lines = model_file.readlines()
- # Removes usernames from the start of a line, e.g. removes "OBRIGADO:"
- def anonymize(line):
- if ’:’ in line:
- colon_index = line.index(’:’) + 1
- anonymous_line = line[colon_index:len(line)]
- return anonymous_line.strip()
- return line
- # Clean up line breaks.
- def remove_breaks(line):
- line = line.replace(’\n’,”)
- line = line.replace(’\r’,”)
- return line
- # Gives index of element containing word.
- # Less strict than .index(string) since it finds partial matches.
- def word_at(string, list):
- index = 0
- for item in list:
- if string in item:
- return index
- break
- index += 1
- return -1
- # Go through the model and look for matches to the first and last words.
- index = 0
- for line in model_lines:
- # Make sure it’s not a blank line.
- line = line.strip()
- # Put in line breaks if it is blank.
- if len(line) == 0:
- print ”
- # Otherwise, start processing.
- if len(line) > 1:
- # Place each word in a list.
- line_list = line.split(’ ‘)
- first_word = line_list[0];
- last_word = line_list[-1];
- if verbose: print ‘––––––––––––’
- if verbose: print ‘Line ‘ + str(index) + ’ starts with "’ + first_word + ‘" ends with "’ + last_word + ‘"’
- # Find first instance of first word in source file.
- for first_word_line in source_lines:
- if first_word in first_word_line:
- # We found the starting word, now find the ending word.
- for last_word_line in source_lines:
- if last_word in last_word_line:
- # We have both a starting and ending word match!
- # Clean up, remove line breaks and attribution.
- # TK problem if match was in name?
- first_word_line = anonymize(remove_breaks(first_word_line))
- last_word_line = anonymize(remove_breaks(last_word_line))
- # For the first line, save from the word forward.
- first_line_list = first_word_line.split(’ ‘)
- first_word_index = word_at(first_word, first_line_list)
- first_line_list = first_line_list[first_word_index:len(first_line_list)]
- # For the last line, save from the word backward.
- last_line_list = last_word_line.split(’ ‘)
- last_word_index = word_at(last_word, last_line_list)
- last_line_list = last_line_list[0:last_word_index + 1]
- # TK remove blank stuff.
- complete_line_list = first_line_list + last_line_list
- if verbose: print complete_line_list
- # Construct a sentence as close to the original length as possible.
- model_line_length = len(line_list);
- # remove words until we have the desired length.
- # TK single word line problems?
- while len(complete_line_list) > model_line_length:
- # Pop from the middle.
- complete_line_list.pop(int(len(complete_line_list) / 2))
- complete_line = ’ ‘.join(complete_line_list)
- # Print the original above the munged line.
- if print_original: print line
- print complete_line
- # Print add some line breaks for readability.
- if print_original: print ”
- break
- break
- index += 1
Egg Dissolve
The mechanisms class was charged with creating egg-cracking Rube Goldberg machines for the first week's assignment. Rather than breaking the egg shell through brute force, we tossed around the idea of dissolving the egg shell entirely.
Luckily there was some muriatic acid lying around the shop, so we ran a quick experiment. The shell dissolves, but it takes about 20 minutes, and what's left isn't a broken egg, but a layer of internal membrane which keeps the de-shelled egg intact. Interesting, but not well suited to the purposes of our machine-to-be, so we shelved the idea. (Maybe it was too clever by half to begin with.)
(Science music by Boards of Canada.)
Triangle Soup
A quick assignment for GLART, drawing an animated field of triangles.
Here’s the code:
- package mika;
- import java.awt.event.MouseEvent;
- import javax.media.opengl.*;
- import jocode.*;
- /**
- * DemoBasicGeometry.java
- * <P>
- * Demonstrate six types of geometry using glBegin()…glEnd()
- * <P>
- * napier at potatoland dot org
- */
- public class Week1 extends JOApp {
- // Set the mouse position in a way that’s
- // useful for translating objects at 0 Z
- public float screenCursorX;
- public float screenCursorY;
- public float tempValue;
- public float sizeMult;
- /**
- * Start the application, Run() initializes the OpenGL context, calls setup(),
- * handles mouse and keyboard input, and calls draw() in a loop.
- */
- public static void main(String args[]) {
- // create the app
- Week1 demo = new Week1();
- // set title, window size
- windowTitle = "Hello World";
- displayWidth = 1440;
- displayHeight = 900;
- // start running: will call init(), setup(), draw(), mouse functions
- demo.run();
- }
- /**
- * Initialize settings. Will be called once when app starts. Called by
- * JOApp.init().
- */
- @Override
- public void setup() {
- // set a background color
- gl.glClearColor(0f, 0f, 0f, 1f);
- // Move over to my second monitor for testing…
- // TODO Disable this IRL
- frame.setLocation(-1440, 150);
- }
- /**
- * Render one frame. Called by the JOApp.display() callback function.
- */
- @Override
- public void draw() {
- // Clear screen and depth buffer
- gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
- // Select The Modelview Matrix (controls model orientation)
- gl.glMatrixMode(GL.GL_MODELVIEW);
- gl.glEnable(GL.GL_BLEND);
- gl.glBlendFunc(GL.GL_SRC0_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
- // gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE);
- // Reset the Modelview matrix
- // this resets the coordinate system to center of screen
- gl.glLoadIdentity();
- // Where is the ‘eye’
- glu.gluLookAt(0f, 0f, 10f, // eye position
- 6f, 1f, 0f, // target to look at
- 0f, 1f, 0f); // which way is up
- gl.glColor4f(1f, 1f, 1f, 0.5f);
- // gl.glColor3f(1f, 1f, 1f); // color will affect all the following verts
- sizeMult = 3;
- tempValue = (tempValue + .02f) % 1000;
- for (int i = 0; i < 200; i++) {
- gl.glRotatef(tempValue, tempValue, tempValue, tempValue);
- gl.glTranslatef(.1f, .1f, .1f);
- gl.glBegin(GL.GL_TRIANGLES);
- {
- gl.glVertex3f(0f, 0f, 0f); // top
- gl.glVertex3f(-0.5f * sizeMult, -1f * sizeMult, 0f); // lower left
- gl.glVertex3f(0.5f * sizeMult, -1f * sizeMult, 0f); // lower right
- }
- gl.glEnd();
- }
- // reset vertex color to white
- gl.glColor3f(1f, 1f, 1f);
- }
- @Override
- public void mouseMoved(MouseEvent _event) {
- // Call the parent method since it actually gives us the
- // Better just to copy the whole method?
- super.mouseMoved(_event);
- screenCursorX = cursorX / (displayWidth / 10f);
- screenCursorY = cursorY / (displayHeight / 10f) - 3f;
- }
- }
Window Drop
I spent an unreasonable portion of my childhood in the back seat of a car, staring out the window. Rainy days, in particular, allowed for one of the more confounding means of passing the time: attempting to predict and understand the movements of water drops on the window.
Attachments
Market Penetration
The 4 Big Myths of Profile Pictures: a straight-faced analysis of how profile pictures of various types correlate with "success" on a dating website. The text's self-help tone is a bit cheeky, but the conclusions are based on several thousand profiles worth of data, and the charts and graphs are pure gold:













