├── .gitignore
├── LICENSE
├── README.md
├── art_critic.py
├── dow_jones.py
├── empathy.py
├── ending.py
├── hipster.py
├── humans.py
├── introduction.py
├── performance.py
├── slapstick.py
└── staring.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *pyc
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Anastasis Germanidis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Photo & video credit: Emi Spicer
7 |
8 | This repository contains the source code for **Welcome, Programmable Human**, a performance piece I did at the [School for Poetic Computation](http://sfpc.io/) Spring 2015 final show (SFPC is an artist-run school in New York City that is like the 21st century version of the [Black Mountain College](http://www.blackmountaincollege.org/history) - consider [applying for the Fall 2015 term](https://docs.google.com/forms/d/1jWCoZSoTKwJeMA8yA1m_guUBi1OpsX01EU7OLztDRyU/viewform)).
9 |
10 | ## Description
11 |
12 | **Welcome, Programmable Human** was an experimental performance in which all my actions, which included giving art critiques of student works, having conversations about today's news stories with visitors, and visualizing the stock market with my body, were generated by a computer program.
13 |
14 | The Python modules included in this repository (starting from [`performance.py`](https://github.com/agermanidis/welcome_programmable_human/blob/master/performance.py)) were sending instructions telling me what to say and do to my phone, and then, through text-to-speech, to my earphones. The instructions generated were a function of a variety of online data sources scraped in real time; for instance, recent tweets containing the keyword "TFW" dictated what feelings I would express to the visitors.
15 |
16 | For the duration of the performance, the source code of the program was projected on the wall, with line currently being evaluated highlighted, so as to enable complete [algorithmic transparency](http://www.techrepublic.com/article/data-driven-policy-and-commerce-requires-algorithmic-transparency/) of my behavior.
17 |
18 | ## Further Work
19 |
20 | HAC (Human Action Code), the framework for generating human behavior that I started working on while I was at the school and that I'm using in this piece, is still heavily under development. Expect an alpha open source release sometime in July.
21 |
22 | I'm planning to do more research and performances exploring the algorithmic construction of all kinds of human behavior this summer in New York City. If you're interested in a future of auto-generated social interactions, programmable theatre, computable social movements, and Turing-complete romance, [keep in touch](http://twitter.com/agermanidis).
23 |
--------------------------------------------------------------------------------
/art_critic.py:
--------------------------------------------------------------------------------
1 | from humans import Anastasis
2 | import urllib2, bs4, subprocess, random, time
3 |
4 | # concepts borrowed from artybollocks.com
5 | concepts = [
6 | "postmodern discourse",
7 | "unwanted gifts",
8 | "Critical theory",
9 | "copycat violence",
10 | "Pre-raphaelite tenets",
11 | "daytime TV",
12 | "multiculturalism",
13 | "midlife subcultures",
14 | "consumerist fetishism",
15 | "the Military-Industrial Complex",
16 | "the tyranny of ageing",
17 | "counter-terrorism",
18 | "gender politics",
19 | "new class identities",
20 | "recycling culture",
21 | "vegetarian ethics",
22 | "emotional memories",
23 | "emerging sexualities",
24 | "UFO sightings",
25 | "consumerist fetishism",
26 | "football chants",
27 | "acquired synesthesia",
28 | "life as performance",
29 | "urban spaces",
30 | "romance tourism"
31 | ]
32 |
33 | def generate_art_critique():
34 | concept1, concept2 = random.sample(concepts, 2)
35 | return "This piece is about the relationship between %s and %s" % (concept1, concept2)
36 |
37 | def give_art_critiques():
38 | for i in range(5):
39 | art_piece = Anastasis.vision.search("art piece")
40 | Anastasis.movement.turn_towards(art_piece)
41 | Anastasis.movement.start_walking()
42 | time.sleep(5)
43 | Anastasis.movement.stop_walking()
44 | critique = generate_art_critique()
45 | Anastasis.voice.say(critique)
46 |
47 | if __name__ == '__main__':
48 | give_art_critiques()
49 |
50 |
--------------------------------------------------------------------------------
/dow_jones.py:
--------------------------------------------------------------------------------
1 | from humans import Anastasis
2 | import csv, datetime
3 |
4 | def visualize_dow_jones():
5 | Anastasis.voice.say("Human Action Code is also useful in corporate settings")
6 | Anastasis.voice.say("It can be used, for example, for doing data visualization for Fortune 500 companies")
7 | Anastasis.voice.say("Here I'll show you, I'll visualize the Dow Jones Industrial Average for this week!")
8 |
9 | with open('data/DJIA.csv', 'rb') as csvfile:
10 | dow_reader = csv.reader(csvfile)
11 | prev_market_average = None
12 |
13 | for index, row in enumerate(dow_reader):
14 | if index == 0: continue
15 |
16 | year, month, day = map(float, row[0].split("-"))
17 | market_average = float(row[1])
18 |
19 | dt = datetime.datetime(year=int(year), month=int(month), day=int(day))
20 | day_of_week = dt.strftime("%A")
21 | Anastasis.voice.say("Last %s the stock market was like" % day_of_week)
22 |
23 | if market_average > prev_market_average:
24 | Anastasis.face.smile()
25 | Anastasis.body.jump()
26 |
27 | else:
28 | Anastasis.face.frown()
29 | Anastasis.body.lie_down_fetal_position()
30 |
31 | prev_market_average = market_average
32 |
33 | if __name__ == '__main__':
34 | visualize_dow_jones()
35 |
--------------------------------------------------------------------------------
/empathy.py:
--------------------------------------------------------------------------------
1 | from humans import Anastasis
2 | import random, time, os, re, tweepy
3 |
4 | consumer_key = os.environ['TWITTER_CONSUMER_KEY']
5 | consumer_secret = os.environ['TWITTER_CONSUMER_SECRET']
6 | access_token = os.environ['TWITTER_ACCESS_TOKEN']
7 | access_token_secret = os.environ['TWITTER_ACCESS_TOKEN_SECRET']
8 |
9 | auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
10 | auth.set_access_token(access_token, access_token_secret)
11 |
12 | api = tweepy.API(auth)
13 |
14 | URL_REGEX = 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
15 |
16 | def humanize_text(text):
17 | text = text.lower()
18 | text = re.sub(URL_REGEX, "", text).strip()
19 | text = re.sub(r"#\w+", "", text).strip()
20 | text = re.sub(r"@\w+", "", text).strip()
21 | text = text.replace("RT ","")
22 | idx = text.find("tfw")
23 | return text[idx:].replace("tfw", "do you know that feeling when")
24 |
25 | def search_twitter(term):
26 | return map(lambda r: humanize_text(r.text), api.search(term, count = 100))
27 |
28 | def share_feelings_with_everyone():
29 | Anastasis.voice.say("This did not go well either.")
30 | Anastasis.voice.say("What do I need to do to experience connection?")
31 | Anastasis.voice.say("Oh I have an idea!")
32 | Anastasis.voice.say("I'll use feelings to connect with the people around me")
33 | Anastasis.voice.say("Wait. I don't have any feelings though.")
34 | Anastasis.voice.say("But hey I can just scrape twitter and borrow random people's feelings")
35 | Anastasis.voice.say("And pretend they're my own!")
36 | Anastasis.voice.say("Nobody will know")
37 | Anastasis.voice.say("This will definitely make me the soul of the party")
38 |
39 | for tweet in search_twitter("tfw")[:5]:
40 | human = Anastasis.vision.search("human")
41 | Anastasis.movement.turn_towards(human)
42 | Anastasis.movement.start_walking()
43 | Anastasis.movement.stop_walking()
44 | Anastasis.face.stare_at(human)
45 | Anastasis.voice.say(tweet)
46 | Anastasis.voice.say("...that's how I feel right now.")
47 |
48 | if __name__ == '__main__':
49 | try_empathetic_social_interaction()
50 |
--------------------------------------------------------------------------------
/ending.py:
--------------------------------------------------------------------------------
1 | from humans import Anastasis
2 |
3 | def end_performance():
4 | Anastasis.voice.say("That's it. Thank you!")
5 | Anastasis.body.bow()
6 |
7 | if __name__ == '__main__':
8 | end_performance()
9 |
--------------------------------------------------------------------------------
/hipster.py:
--------------------------------------------------------------------------------
1 | from humans import Anastasis
2 | import urllib2, bs4, random, time
3 |
4 | newspapers = [
5 | ("Buzzfeed", "http://buzzfeed.com/", "article header hgroup h2 a"),
6 | ("the New York Times", "http://nytimes.com/", ".story-heading a"),
7 | ("Vice", "http://vice.com/", ".item-title a"),
8 | ("Salon", "http://salon.com", ".article-info h2 a"),
9 | ("Gawker", "http://gawker.com", "header h1 a")
10 | ]
11 |
12 | def parse_articles_from(url, selector):
13 | html_doc = urllib2.urlopen(url).read()
14 | soup = bs4.BeautifulSoup(html_doc)
15 | elements = soup.select(selector)
16 | return map(lambda el: el.text.strip(), elements)
17 |
18 | def random_articles(count):
19 | titles_so_far = []
20 | i = 0
21 | while i < count:
22 | newspaper, url, selector = random.choice(newspapers)
23 | titles = parse_articles_from(url, selector)
24 | title = random.choice(titles)
25 | if title in titles_so_far: continue
26 | titles.append(title)
27 | i += 1
28 | yield newspaper, title
29 |
30 | def try_hipster_social_interaction():
31 | Anastasis.voice.say("Okay, my first attempt at connecting with people did not go great")
32 | Anastasis.voice.say("Maybe I will try connecting around media consumption")
33 | Anastasis.voice.say("This will definitely make me the soul of the party")
34 |
35 | for newspaper, title in random_articles(3):
36 | human = Anastasis.vision.search("human")
37 | Anastasis.movement.turn_towards(human)
38 | Anastasis.movement.start_walking()
39 | time.sleep(2)
40 | Anastasis.movement.stop_walking()
41 | Anastasis.face.stare_at(human)
42 | Anastasis.voice.say("Did you read that thing on %s, %s?" % (newspaper, title))
43 |
44 | if __name__ == '__main__':
45 | do_hipster_social_interaction()
46 |
--------------------------------------------------------------------------------
/humans.py:
--------------------------------------------------------------------------------
1 | from hac import Humanity
2 |
3 | Anastasis = Humanity().find_person(name = "Anastasis")
4 |
5 |
--------------------------------------------------------------------------------
/introduction.py:
--------------------------------------------------------------------------------
1 | from humans import Anastasis
2 | import pywapi, bs4, urllib2, random, time
3 |
4 | location_ids = {
5 | 'Manhattan': 'USNY0996',
6 | 'Moscow': 'RSXX0063',
7 | 'Tokyo': 'JAXX0085',
8 | 'Paris': 'FRXX0076'
9 | }
10 |
11 | def celsius_to_fahrenheit(c):
12 | return (c * 1.8) + 32
13 |
14 | def get_weather(name):
15 | location_id = location_ids[name]
16 | weather = pywapi.get_weather_from_weather_com(location_id)
17 | temperature = weather['current_conditions']['temperature']
18 | return celsius_to_fahrenheit(float(temperature))
19 |
20 | def tell_temperatures():
21 | for city, location_id in location_ids.items():
22 | Anastasis.voice.say("The temperature in {0} right now is {1} degrees Fahrenheit".format(city, get_weather(city)))
23 |
24 | positive_adjectives = ['adaptable', 'adventurous', 'affable', 'affectionate', 'agreeable',
25 | 'ambitious', 'amiable', 'amicable', 'amusing', 'brave', 'bright',
26 | 'broad-minded', 'calm', 'careful', 'charming', 'communicative',
27 | 'compassionate', 'conscientious', 'considerate', 'convivial',
28 | 'courageous', 'courteous', 'creative', 'decisive', 'determined',
29 | 'diligent', 'diplomatic', 'discreet', 'dynamic', 'easygoing',
30 | 'emotional', 'energetic', 'enthusiastic', 'exuberant', 'fair-minded',
31 | 'faithful', 'fearless', 'forceful', 'frank', 'friendly', 'funny',
32 | 'generous', 'gentle', 'good', 'gregarious', 'hard-working', 'helpful',
33 | 'honest', 'humorous', 'imaginative', 'impartial', 'independent',
34 | 'intellectual', 'intelligent', 'intuitive', 'inventive', 'kind',
35 | 'loving', 'loyal', 'modest', 'neat', 'nice', 'optimistic', 'passionate',
36 | 'patient', 'persistent', 'pioneering', 'philosophical', 'placid',
37 | 'plucky', 'polite', 'powerful', 'practical', 'pro-active',
38 | 'quick-witted', 'quiet', 'rational', 'reliable', 'reserved',
39 | 'resourceful', 'romantic', 'self-confident', 'self-disciplined',
40 | 'sensible', 'sensitive', 'shy', 'sincere', 'sociable', 'straightforward',
41 | 'sympathetic', 'thoughtful', 'tidy', 'tough', 'unassuming',
42 | 'understanding', 'versatile', 'warmhearted', 'willing', 'witty']
43 |
44 | def give_speech():
45 | stage = Anastasis.vision.find("stage")
46 | Anastasis.movement.turn_towards(stage)
47 | Anastasis.movement.start_walking()
48 | Anastasis.movement.stop_walking()
49 | Anastasis.movement.turn_around()
50 |
51 | microphone = Anastasis.vision.find("microphone")
52 | Anastasis.body.grab(microphone)
53 | Anastasis.body.pull(microphone)
54 |
55 | Anastasis.voice.say("Hello everyone!")
56 | Anastasis.voice.say("My name is Anastasis")
57 | Anastasis.voice.say("I'm one of the students at SFPC")
58 | Anastasis.voice.say("I would describe my time at SFPC as")
59 | for adjective in random.sample(positive_adjectives, 5):
60 | Anastasis.voice.say(adjective)
61 | time.sleep(1)
62 |
63 | Anastasis.voice.say("I made a lot of great new friends")
64 | Anastasis.voice.say("And I also made a project!")
65 |
66 | for i in range(3):
67 | Anastasis.body.jump()
68 |
69 | Anastasis.voice.say("The project is called Human Action Code")
70 | Anastasis.voice.say("It's a framework for programming humans to perform arbitrary actions in the world")
71 | Anastasis.voice.say("And it's also generating everything I'm doing right now")
72 |
73 | Anastasis.movement.rotate(45)
74 | Anastasis.right_hand.extend()
75 | Anastasis.right_hand.release()
76 |
77 | Anastasis.face.smile()
78 | Anastasis.voice.say("You could say I'm the first programmable human being")
79 |
80 | Anastasis.body.thinking_pose()
81 |
82 | Anastasis.voice.say("Why did I make it?")
83 | Anastasis.voice.say("Because everyone is trying to make machines that pass the Turing Test")
84 | Anastasis.voice.say("But I want to make humans that fail the Turing Test")
85 |
86 | Anastasis.voice.say("With Human Action Code the Internet enters my body")
87 | Anastasis.voice.say("and expresses itself through it")
88 | Anastasis.voice.say("I have access to every real-time information stream available online")
89 |
90 | tell_temperatures()
91 |
92 |
--------------------------------------------------------------------------------
/performance.py:
--------------------------------------------------------------------------------
1 | from introduction import give_speech
2 | from staring import stare_at_people
3 | from dow_jones import visualize_dow_jones
4 | from art_critic import give_art_critiques
5 | from hipster import try_hipster_social_interaction
6 | from empathy import share_feelings_with_everyone
7 | from slapstick import perform_slapstick_humor
8 | from ending import finish
9 |
10 | def performance():
11 | give_speech()
12 | visualize_dow_jones()
13 | give_art_critiques()
14 | stare_at_people()
15 | try_hipster_social_interaction()
16 | share_feelings_with_everyone()
17 | perform_slapstick_humor()
18 | finish()
19 |
20 | if __name__ == '__main__':
21 | performance()
22 |
--------------------------------------------------------------------------------
/slapstick.py:
--------------------------------------------------------------------------------
1 | from humans import Anastasis
2 | import random, time
3 |
4 | voice_actions = [
5 | 'voice.scream',
6 | 'voice.laugh',
7 | 'voice.snort'
8 | ]
9 |
10 | face_actions = [
11 | 'face.smile',
12 | 'face.frown',
13 | 'face.nod',
14 | 'face.raise_eyebrows',
15 | 'face.wink',
16 | 'face.blink',
17 | ]
18 |
19 | body_actions = [
20 | 'body.jump',
21 | 'body.lie_down_face_up',
22 | 'body.lie_down_face_down',
23 | 'body.lie_down_fetal_position',
24 | 'body.kneel',
25 | 'body.bow',
26 | 'body.crouch'
27 | ]
28 |
29 | def perform_slapstick_humor():
30 | Anastasis.voice.say("Nothing seems to work")
31 | Anastasis.voice.say("I don't feel any closer to non-programmable people")
32 | Anastasis.voice.say("What if I used my body?")
33 | Anastasis.voice.say("That's what I'll do!")
34 | Anastasis.body.jump()
35 | Anastasis.voice.say("Everyone likes slapstick humor!")
36 | Anastasis.voice.say("This will definitely make me the soul of the party")
37 |
38 | for i in range(3):
39 | face_action = random.choice(face_actions)
40 | Anastasis[face_action]()
41 |
42 | actions = random.sample(body_actions, 2)
43 | for action in actions:
44 | Anastasis[action]()
45 |
46 | voice_action = random.choice(voice_actions)
47 | Anastasis[voice_action]()
48 |
49 | if __name__ == '__main__':
50 | try_slapstick_humor()
51 |
--------------------------------------------------------------------------------
/staring.py:
--------------------------------------------------------------------------------
1 | import random, time
2 | from humans import Anastasis
3 |
4 | def stare_at_people():
5 | Anastasis.voice.say("Now I just want to connect with everyone!")
6 | Anastasis.voice.say("Okay, I'll give social interaction a try")
7 |
8 | for i in range(3):
9 | human = Anastasis.vision.search("human")
10 | Anastasis.movement.turn_towards(human)
11 | Anastasis.movement.start_walking()
12 | time.sleep(1)
13 | Anastasis.movement.stop_walking()
14 | Anastasis.face.stare_at(human)
15 | time.sleep(2)
16 |
17 | if __name__ == '__main__':
18 | stare_at_people()
19 |
20 |
--------------------------------------------------------------------------------