WordNet

We can use NLTK’s support for WordNet to help generate and classify text.

from nltk.corpus import wordnet as wn
from nltk.corpus import sentiwordnet as swn

def make_synset(word, category='n', number='01'):
    """Conveniently make a synset"""
    number = int(number)
    return wn.synset('%s.%s.%02i' % (word, category, number))

>>> dog = make_synset('dog')
>>> dog.definition
'a member of the genus Canis (probably descended from the common wolf) that has been domesticated by man since prehistoric times; occurs in many breeds'

A synset is WordNet’s representation of a word/concept. Looking at the definition confirms that we have the synset for canis familiaris rather than persecution or undesirability.

>>> dog.hypernyms()
[Synset('domestic_animal.n.01'), Synset('canine.n.02')]

Hypernyms are more general concepts. ‘dog’ has two of them, which shows that WordNet is not arranged in a simple tree of concepts. This makes checking for common ancestors slightly more complex but represents concepts more realistically.

>>> dog.hyponyms()
[Synset('puppy.n.01'), Synset('great_pyrenees.n.01'), Synset('basenji.n.01'), Synset('newfoundland.n.01'), Synset('lapdog.n.01'), Synset('poodle.n.01'), Synset('leonberg.n.01'), Synset('toy_dog.n.01'), Synset('spitz.n.01'), Synset('pooch.n.01'), Synset('cur.n.01'), Synset('mexican_hairless.n.01'), Synset('hunting_dog.n.01'), Synset('working_dog.n.01'), Synset('dalmatian.n.02'), Synset('pug.n.01'), Synset('corgi.n.01'), Synset('griffon.n.02')]

Hyponyms are more specific concepts. ‘dog’ has several. These may have hypernyms other than ‘dog’, and may have several hyponyms themselves.

def _recurse_all_hypernyms(synset, all_hypernyms):
    synset_hypernyms = synset.hypernyms()
    if synset_hypernyms:
        all_hypernyms += synset_hypernyms
        for hypernym in synset_hypernyms:
            _recurse_all_hypernyms(hypernym, all_hypernyms)

def all_hypernyms(synset):
    """Get the set of hypernyms of the hypernym of the synset etc.
       Nouns can have multiple hypernyms, so we can't just create a depth-sorted
       list."""
    hypernyms = []
    _recurse_all_hypernyms(synset, hypernyms)
    return set(hypernyms)

>>> all_hypernyms(dog)
>>> set([Synset('chordate.n.01'), Synset('living_thing.n.01'), Synset('physical_entity.n.01'), Synset('animal.n.01'), Synset('mammal.n.01'), Synset('object.n.01'), Synset('vertebrate.n.01'), Synset('entity.n.01'), Synset('carnivore.n.01'), Synset('domestic_animal.n.01'), Synset('canine.n.02'), Synset('placental.n.01'), Synset('organism.n.01'), Synset('whole.n.02')])

We can recursively fetch the hypernyms of a synset. since ‘dog’ has two hypernyms this isn’t a single list of hypernyms.
We can use this to find how similar different words are by searching for common ancestors.
The Python WordNet library can find common hypernyms for us though.

>>> cat = make_synset('cat')
>>> cat.common_hypernyms(dog)
[Synset('chordate.n.01'), Synset('living_thing.n.01'), Synset('physical_entity.n.01'), Synset('animal.n.01'), Synset('mammal.n.01'), Synset('vertebrate.n.01'), Synset('entity.n.01'), Synset('carnivore.n.01'), Synset('object.n.01'), Synset('placental.n.01'), Synset('organism.n.01'), Synset('whole.n.02')]
>>> steel = make_synset('steel')
>>> steel.common_hypernyms(dog)
[Synset('physical_entity.n.01'), Synset('entity.n.01')]
>>> sunset = make_synset('sunset')
>>> sunset.common_hypernyms(dog)
[Synset('entity.n.01')]

As might be expected, cats and dogs are more similar than steel or sunsets.
We can recursively fetch the hyponyms of a synset. This gives us the set of objects or concepts with a kind-of relationship to the word.

def _recurse_all_hyponyms(synset, all_hyponyms):
    synset_hyponyms = synset.hyponyms()
    if synset_hyponyms:
        all_hyponyms += synset_hyponyms
        for hyponym in synset_hyponyms:
            _recurse_all_hyponyms(hyponym, all_hyponyms)

def all_hyponyms(synset):
    """Get the set of the tree of hyponyms under the synset"""
    hyponyms = []
    _recurse_all_hyponyms(synset, hyponyms)
    return set(hyponyms)

>>> all_hyponyms(dog)
set([Synset('harrier.n.02'), Synset('water_spaniel.n.01'), Synset('standard_poodle.n.01'), Synset('dandie_dinmont.n.01'), Synset('wirehair.n.01'), Synset('toy_manchester.n.01'), Synset('puppy.n.01'), Synset('briard.n.01'), Synset('beagle.n.01'), Synset('siberian_husky.n.01'), Synset('manchester_terrier.n.01'), Synset('bloodhound.n.01'), ...

WordNet has some support for synonyms and antonyms via lemmas.

def synset_synonyms(synset):
    """Get the synonyms for the synset"""
    return set([lemma.synset for lemma in synset.lemmas])

def synset_antonyms(synset):
    """Get the antonyms for [the first lemma of] the synset"""
    return set([lemma.synset for lemma in synset.lemmas[0].antonyms()])

>>> synset_synonyms(sunset)
set([Synset('sunset.n.01')])
>>> synset_antonyms(sunset)
set([Synset('dawn.n.01')])

And we can find related concepts by getting all the hyponyms of a word’s hypernynms.

def all_peers(synset):
    """Get the set of all peers of the synset (including the synset).
       If the synset has multiple hypernyms then the peers will be hyponyms of
       multiple synsets."""
    hypernyms = synset.hypernyms()
    peers = []
    for hypernym in hypernyms:
        peers += hypernym.hyponyms()
    return set(peers)

>>> all_peers(sunset)
set([Synset('zero_hour.n.01'), Synset('rush_hour.n.01'), Synset('early-morning_hour.n.01'), Synset('none.n.01'), Synset('midnight.n.01'), Synset('happy_hour.n.01'), Synset('dawn.n.01'), Synset('bedtime.n.01'), Synset('late-night_hour.n.01'), Synset('small_hours.n.01'), Synset('noon.n.01'), Synset('sunset.n.01'), Synset('twilight.n.01'), Synset('mealtime.n.01'), Synset('canonical_hour.n.01'), Synset('closing_time.n.01')])

We use sets here so that common ancestors and children appear only once, and to allow for boolean set operations on concepts.
It’s trivial to get the the word (or words) for a synset.

def synsets_words(synsets):
    """Get the set of strings for the words represented by the synsets"""
    return set([synset_word(synset) for synset in synsets])

>>> synsets_words(all_hyponyms(dog))
set(['rottweiler', 'bull mastiff', 'belgian sheepdog', 'courser', 'brabancon griffon', 'toy terrier', 'fox terrier', 'sennenhunde', 'standard poodle', 'saluki', 'pointer', 'toy spaniel', 'setter', 'giant schnauzer', 'housedog', 'papillon', 'american foxhound', 'weimaraner', 'cocker spaniel', 'basenji', 'beagle', ...

WordNet has part/whole, group and substance relationships.

>>> body = make_synset('body')
>>> body.part_meronyms()
[Synset('arm.n.01'), Synset('articulatory_system.n.01'), Synset('body_substance.n.01'), Synset('cavity.n.04'), Synset('circulatory_system.n.01'), Synset('crotch.n.02'), Synset('digestive_system.n.01'), Synset('endocrine_system.n.01'), Synset('head.n.01'), Synset('leg.n.01'), Synset('lymphatic_system.n.01'), Synset('musculoskeletal_system.n.01'), Synset('neck.n.01'), Synset('nervous_system.n.01'), Synset('pressure_point.n.01'), Synset('respiratory_system.n.01'), Synset('sensory_system.n.02'), Synset('torso.n.01'), Synset('vascular_system.n.01')]

>>> dog.member_holonyms()
[Synset('canis.n.01'), Synset('pack.n.06')]

>>> wood = make_synset('wood')
>>> wood.substance_holonyms()
[Synset('beam.n.02'), Synset('chopping_block.n.01'), Synset('lumber.n.01'), Synset('spindle.n.02')]
>>> wood.substance_meronyms()
[Synset('lignin.n.01')]

We can use hypernyms to classify words into domains using WordNet, but there’s an existing domain classification system in the form of WordNet Domains. It can be downloaded here. Code for using this can be found on Stack Overflow. But it doesn’t seem to work with nltk 3.0 (the synset numbers don’t match).

And there’s a sentiment score system for WordNet in the form of SentiWordNet. There’s an interface for it in WordNet 3.0.

def make_senti_synset(word, category='n', number='01'):
    """Conveniently make a senti_synset"""
    number = int(number)
    return swn.senti_synset('%s.%s.%02i' % (word, category, number))

def synsets_sentiments(synsets):
    """Return the objs, pos, neg and pos - neg score sums for the synsets"""
    pos = 0.0
    obj = 0.0
    neg = 0.0
    for synset in synsets:
        try:
            pos += synset.pos_score()
            obj += synset.obj_score()
            neg += synset.neg_score()
        except AttributeError, e:
            pass
    return obj, pos, neg, pos - neg

>>> happy = make_senti_synset('happy', 'a')
>>> happy.pos_score()
0.875
>>> happy.neg_score()
0.0
>>> happy.obj_score()
0.125

synsets_sentiments([make_senti_synset(word, 'a') for word in 'happy sad angry heavy light depressing'.split()])
(2.5, 1.5, 2.0, -0.5)

Not every word has a sentiment score, hence the try/except block in synsets_sentiments.

WordNet is sensitive to senses and it’s hard to automatically resolve senses when processing arbitrary text. When generating text and using WordNet to find words, it’s important (and easier) to set the correct sense for the synset.

>>> colour = make_synset('colour', 'n', 6)
>>> all_hyponyms(colour)
set([Synset('chrome_red.n.01'), Synset('primary_color.n.01'), Synset('light_brown.n.01'), Synset('sallowness.n.01'), Synset('hazel.n.04'), Synset('iron-grey.n.01'), Synset('olive_green.n.01'), Synset('tan.n.02'), Synset('pastel.n.01'), Synset('coal_black.n.01'), Synset('pinkness.n.01'), Synset('vandyke_brown.n.01'), Synset('beige.n.01'), Synset('blue.n.01'), Synset('shade.n.02'), Synset('achromatic_color.n.01'), Synset('whiteness.n.03'), Synset('coral.n.01'), Synset('chromatism.n.02'), Synset('apatetic_coloration.n.01'), ...

This gives concepts on different levels. Maybe if we try the peers of a colour.

>>> all_peers(make_synset('red'))
set([Synset('red.n.01'), Synset('pastel.n.01'), Synset('purple.n.01'), Synset('green.n.01'), Synset('olive.n.05'), Synset('complementary_color.n.01'), Synset('brown.n.01'), Synset('blue.n.01'), Synset('blond.n.02'), Synset('yellow.n.01'), Synset('orange.n.02'), Synset('pink.n.01'), Synset('salmon.n.04')])

OK maybe if we try the children of a concept.

>>> all_hyponyms(make_synset('chromatic_color'))
set([Synset('chrome_red.n.01'), Synset('light_brown.n.01'), Synset('hazel.n.04'), Synset('olive_green.n.01'), Synset('tan.n.02'), Synset('pastel.n.01'), Synset('pinkness.n.01')

Perhaps the leaf nodes.

def _recurse_leaf_hyponyms(synset, leaf_hyponyms):
    synset_hyponyms = synset.hyponyms()
    if synset_hyponyms:
        for hyponym in synset_hyponyms:
            _recurse_all_hyponyms(hyponym, leaf_hyponyms)
    else:
        leaf_hyponyms += synset

def leaf_hyponyms(synset):
    """Get the set of leaf nodes from the tree of hyponyms under the synset"""
    hyponyms = []
    _recurse_leaf_hyponyms(synset, hyponyms)
    return set(hyponyms)

>>> leaf_hyponyms(make_synset('chromatic_color'))
set([Synset('taupe.n.01'), Synset('snuff-color.n.01'), Synset('chrome_red.n.01'), Synset('light_brown.n.01'), Synset('hazel.n.04'), Synset('olive_drab.n.01'), Synset('old_gold.n.01'), Synset('chocolate.n.03'), Synset('yellowish_pink.n.01'), Synset('yellowish_brown.n.01'), Synset('tyrian_purple.n.02'), ...

That looks good. All colours, no intermediate concepts.

We can use this set of words to choose colours, or to categorize words as colours.

I hope this demonstrates that WordNet can be a very useful resource for Generative Art and Digital Humanities projects.

Artworld Ethereum – Identity, Ownership and Authenticity

Ethereum is a distributed computing system for writing and executing smart contracts. Inspired by Bitcoin, it’s currently in development with a planned late 2014 release date. The term “smart contracts” was coined around 1993 by computer scientist Nick Szabo to describe computer-readable code that replaces lawyer-readable code to describe agreements and obligations between people.

It’s a very literal take on Lawrence Lessig’s argument that “code is law”, a libertarian attempt to reduce the costs and uncertainty of having to trust human beings and interpret ambiguous human language, or possibly a dystopian replacement of rights and safeguards with binary logic.

Smart contracts can be used to implement smart property, physical goods governed by computer code, and Distributed Autonomous Organizations, which replace written constitutions with code running on Ethereum’s Blockchain.

This series of articles will look at applications of Ethereum to the production, exhibition, critique and institutions of art. Starting with digital art as smart property.

The sample code is written in Serpent, the high level Ethereum programming language, and is current as of Ethereum POC 5. It will be revised and tested for the release version of Ethereum.

You can learn more about Ethereum here and here. You can learn more about Serpent here and here.

Storing and Identifying Digital Art In Ethereum

There are three cases of digital art as smart property. The first is the conceptual or code art case, where the code of the contract itself is or contains the artwork. The second is the Ethereum storage case, where a small digital artwork is stored in the Ethereum datastore. The third is the stored identifier case, where only an identifier or proxy for the artwork is stored with the contract.

Conceptual and Code Art

Contracts that are themselves art are a simple case. They should store their owner’s Ethereum address and ensure that transactions initiating actions that only the owner should be able to perform come from that address.

Conceptual Art

As art is defined by its inutility, a contract that does nothing must be art. ūüėČ

stop

Hot/Cold

A contract that does something, but nothing useful.

init:
    contract.storage[1000] = "hot"
    contract.storage[1001] = "cold"

code:
    // Make sure we have enough gas to run the contact
    if tx.value < tx.basefee * 100:
        // If not, stop
        stop

    // Swap
    temp = contract.storage[1000]
    contract.storage[1000] = contract.storage[1001]
    contract.storage[1001] = temp

Numbered Works

A simple generative work that creates a new, original piece for each request.

init:
    ARTWORK.NUMBER.INDEX = 1001 
    contract.storage[ARTWORK.NUMBER.INDEX] = 1

code:
    ARTWORK = "Work #"
    ARTWORK.NUMBER.INDEX = 1001
    // Make sure we have enough gas to run the contact
    if tx.value < tx.basefee * 400:
        // If not, stop
        stop

    // Get the number of the work to produce
    number = contract.storage[ARTWORK.NUMBER.INDEX]
    // Store the number to use for the next work
    contract.storage[ARTWORK.NUMBER.INDEX] = contract.storage[ARTWORK.NUMBER.INDEX] + 1
    // Return the work
    return([ARTWORK, number], 2)

Data Visualization

A simple customised generative work / data visualization. The output looks something like:

when assembled.

HEX = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]
ARTWORK = ["<svg><rect x=\"23\" y=\"23\" ", "height=\"123\" width=\"123\" ", "style=\"fill:none;stroke:#", ";stroke-width:32\" /></svg>"]
ARTWORK.LENGTH = 10
ARTWORK.INSERT.END = 8

// Make sure we have enough gas to run the contact
if tx.value < tx.basefee * 500:
    // If not, stop
    stop

// If there are enough arguments
// and the command to create the work is being given
if msg.datasize == 1 and msg.data[0] == "create":
    artwork = array(ARTWORK.LENGTH)
    artwork[0] = ARTWORK[0]
    artwork[1] = ARTWORK[1]
    artwork[2] = ARTWORK[2]
    artwork[9] = ARTWORK[3]
    // Copy the most significant hex bytes of the key as an html colour
    index = 0
    hash.bytes = msg.sender
    while index < 3:
        current.byte = hash.bytes % 256
        hash.bytes = hash.bytes / 256
        hi = HEX[current.byte / 16]
        lo = HEX[current.byte % 16]
        artwork[ARTWORK.INSERT.END - (index * 2)] = lo
        artwork[ARTWORK.INSERT.END - (index * 2) + 1] = hi
        index = index + 1
    return(artwork, ARTWORK.LENGTH)
// Otherwise
else:
    // Logical false for failure
    return(0)

Stored Art

Digital art can be stored in a contract’s bytecodes or permanent storage rather than generated by the contract’s code.

A Stored Image

ARTWORK = ['P1\n32 32\n00000000000000010000000', '00000000000000000000000010000000', '000000000000000\n0000000001000000', '00000000000000000000000001000000', '0000000000000000000000\n001100000', '00000000000000000000001100001010', '00001100001000000000100000000\n00', '01011000010000000000100000000000', '01111000100000000001000000000001', '1111\n000100000000001000000000011', '11110110000000000010000000001111', '11100010000\n00000000000000011111', '11000000000000000010000001111111', '110000001000000000\n1000001111111', '11000000000000000000000000111111', '1110000001000000000000000\n000011', '11111000000000000000100000000001', '11111100000100000000000000000111', '\n1111110000000000000010000000011', '11111100000100000000010000001111', '1111000\n000010000000000000000111', '11110000000000000000000100001111', '10000000000100\n00000000000001111', '10000000000010000000000001111100', '000000000001000000000\n0000011100', '00000000000100000000000000101000', '0000000000000000000000000000\n010', '10000000010100000000000000000000', '01010010100000000000000000000000', '000\n0110000000000000000000000000', '00000100000000000000000000000000', '0000010000\n000000000000000000000', '00000010000000000000000']

ARTWORK.LENGTH = 33

// Make sure we have enough gas to run the contact
if tx.value < tx.basefee * 1000:
    // If not, stop
    stop

// If there are enough arguments
// and the command to show the work is being given
if msg.datasize == 1 and msg.data[0] == "exhibit":
    // Just return the artwork
    return(ARTWORK, ARTWORK.LENGTH)
// Otherwise
else:
    // Logical false for failure
    return(0)

Identifiers For Digital Art

Where a work of digital art will not fit in the blockchain a more compact identifier must be stored in the contract and used to refer to the work instead.

Ideally a method for identifying unique instances of a digital artwork will be:

  • Stable. The identifier should be usable for decades. Web URLs for example can change or become inaccessible as services change how they serve content or go out of business.
  • Verifiable. The identifier should be usable as a way of verifying that a resource is the one it refers to. Cryptographic hashes for example will not work with digital images that have been resized or GPS co-ordinates that differ even fractionally.
  • Amendable. Where stability fails or a change in ownership requires a change in identifier, it should be possible to update the identifier in a trusted and verifiable way.
  • Sufficient. The information required to identify the resource should be usable directly from the contract rather than requiring external information to complete it.
  • Private. An identifier should leak as little information about the owner of the resource as possible. For example GPS co-ordinates or street addresses, while stable, do locate the resource and possibly its owner. Storing only the cryptographic hash of an identifier can mitigate this.

Some of these criteria clash and therefore any given method of identification must trade them off against each other. For example being private and verifiable, or stable and amendable.

For artworks to interact with smart contracts we need a way of identifying them in those contracts.Where a digital artwork is too large or complex to keep in the contract’s code or storage, a proxy or compact identifier that refers to the must be used.

The following identifiers have various strengths and weaknesses. We’ll use some of them in the next section.

URL

A URL, such as a web site address, is a clear public identifier. It lacks privacy and is only as stable as the service hosting it, but has the advantage of being unique. To add a veneer of privacy, only the cryptographic hash of the URL can be stored by the contract and this can be checked against the hash of a URL by anyone who wishes to check whether it is the instance of the work referred to by the contract.

For example the url:

http://robmyers.wpengine.com/wp-content/uploads/2012/10/applied_aesthetics-824×1024.png

has the SHA256 hash:

6a1811d79b46ab9e43f449beb9838e21dc5865d293e3dfb9b4ba508c7261b915

Never use a link shortening service or a consumer third party hosting service for work represented as URLs, such services are likely to go out of business or change their URL structure, rendering identifiers using them useless. When using your own site for hosting work make sure both to both keep your domain name registered and the server running and to make provisions for them to be maintained when you are no longer able or willing to do so.

File Hash

Producing a cryptographic hash of a work contained in or represented by a file is simple and uniquely identifies the data contained by the file (although any copies of the file will have the same hash). It is better to hash the contents of the file rather than the file itself: an image that has the same pixel values as a PNG or a GIF will have a very different structure on disk in each of those formats. Likewise the full-size or full-quality version of the contents of the file should be hashed rather than a thumbnail or a lossy version.

Git Repository Commit Hash

Modern decentralised version control systems use cryptographic hashes to identify commits. Hashes can identify version of works in a series within a version control repository, although they are best accompanied by a URL or other identifier for the repository.

Serial Number or UUID

A serial number or unique identifier embedded in the work’s filename or metadata can be used to identify it. Visible watermarks are the mark of the amateur, and steganographic watermarks are easily defeated.

Cryptographic Signing

When producing editions of a digital work, each can be signed by the artist to identify it as authorised.

Name

When all else fails, a unique name and description for a work is a useful identifier.

Art As Smart Propery

A Simple Owned Work

OWNER = 0x7c8999dc9a822c1f0df42023113edb4fdd543266

// Get the owner Ethereum address
return(OWNER)

A Simple Owned Work That Confirms Ownership

OWNER = 0x7c8999dc9a822c1f0df42023113edb4fdd543266

// Make sure we have enough gas to run the contact
if tx.value < tx.basefee * 100:
    // If not, stop
    stop

// If the Ethereum address sent matches the owner
if msg.data[0] == OWNER:
    // Return true
    return(1)
// Otherwise
else:
    // Return false
    return(0)

A Simple Owned Stored Work

OWNER = 0x8802b7f0bfa5e9f5825f2fc708e1ad00d2c2b5d6 // Artist initially
WORK = "The art happens here."

// Make sure we have enough gas to run the contact
if tx.value < tx.basefee * 200:
    // If not, stop
    stop

// If there are enough arguments
// and the command to return the owner address is given
if msg.datasize == 1 and msg.data[0] == "owner":
    // Return the owning Ethereum address
    return(OWNER)
// If there are enough arguments
// and the command to return the artwork is given
if msg.datasize == 1 and msg.data[0] == "work":
    // Return the work
    return(WORK)
// Otherwise
else:
    // Return logical failure
    return(0)

Simple Transferable Stored Work

init:
    ARTIST = 0x8802b7f0bfa5e9f5825f2fc708e1ad00d2c2b5d6
    OWNER = 1001
    // Initialize the owner to be the artist
    contract.storage[OWNER] = ARTIST
code:
    OWNER = 1001
    ARTWORK = "The art happens here."
    // Make sure we have enough gas to run the contact
    if tx.value < tx.basefee * 200:
        // If not, stop
        stop

    // If the message is from the current owner
    // and there are enough arguments
    // and the command to transfer is being given
    if msg.sender == contract.storage[OWNER] and msg.datasize == 2 and msg.data[0] == "transfer":
        // Transfer it to a new owner
        contract.storage[OWNER] = msg.data[1]
        return(1)
    // If there are enough arguments
    // and the command to show the work is being given
    else if (msg.datasize == 1):
        // Just return the artwork
        return(ARTWORK)
    // Otherwise
    else:
        // Logical false for failure
        return(0)

An Ownership Registry For Digital Art

if tx.value < tx.basefee * 200:     // If not enough gas, stop     stop // If data was provided, it won't overwrite the code, and the artwork is unregistered if msg.datasize == 1 and msg.data[0] > 1000 and contract.storage[msg.data[0]] == 0:
    // Set the owner to be the sender
    contract.storage[msg.data[0]] = msg.sender
    return(1)
else:
    // Do nothing
    return(0)

A Hash-based Ownership Registry For Specific Instances Digital Art

This is a registry for ownership of artworks at specific urls. Artworks are identified by the hash of their file contents and by the hash of their url. Owners are identified by Ethereum address.

if tx.value < tx.basefee * 200:
    // If not, stop
    stop

// If registration is being requested
if msg.datasize == 3 and msg.data[0] == "register":
    // If the url/work combination has not been claimed
    if ! contract.storage[msg.data[1]]:
        // Set the owner to be the provided Ethereum address
        contract.storage[msg.data[1]] = msg.sender
        // Store the artwork hash next to the url ownership information
        contract.storage[msg.data[1] + 1] = msg.data[2]
        // return success
        return(1)
    // If the sender was trying to overwrite a work they do not own
    else:
        // They cannot set it, so return failure
        return(0)
// If ownership confirmation is being requested
// Confirm that the work and url hashes match
else if msg.datasize == 4 and msg.data[0] == "confirm":
    // Check the provided hashes against the stored work and url hashes
    return((contract.storage[msg.data[2]] == msg.data[1]) and (contract.storage[msg.data[2] + 1] == msg.data[3]))
// If no action was specified
else:
    // Otherwise do nothing
    return(0)

Authenticating Art In Ethereum

Authentication, like ownership, is related to identity and contract law.

A Simple Certificate Of Authenticity For Digital Art

ARTIST = 0x8802b7f0bfa5e9f5825f2fc708e1ad00d2c2b5d6
ARTWORK.HASH = 0x76bba376ea574e63ab357b2374d1cee5aa77d24db38115e3824c5cc4f443d5f7

return((msg.data[0] == ARTIST) and (msg.data[1] == ARTWORK.HASH))
ARTIST = 0x8802b7f0bfa5e9f5825f2fc708e1ad00d2c2b5d6
WORK.HASH = 0x76bba376ea574e63ab357b2374d1cee5aa77d24db38115e3824c5cc4f443d5f7
URL.HASH = 0xa005b1625af0b6ee080dafb904c4505ad285764071ee45a8786159bd1a282634

// If there are enough arguments
if msg.datasize == 2:
    // Check the provided hashes against the stored work and url hashes
    return((msg.data[0] == WORK.HASH) and (msg.data[1] == URL.HASH))
// Otherwise
else:
    // Do nothing
    stop

Catalogue Raisonné For Digital Artists

ARTIST = 0x8802b7f0bfa5e9f5825f2fc708e1ad00d2c2b5d6

// Make sure we have enough gas to run the contact
if tx.value < tx.basefee * 200:
    // If not, stop
    stop
    
// If the message is from the artist
if msg.datasize == 1 and msg.sender == ARTIST:
    // Add the work to the catalog
    contract.storage[msg.data[0]] = 1
    return(1)
// check inclusion
else if msg.datasize == 1:
    // Check whether the artwork is in the catalog
    return(contract.storage[msg.data[0]])
// Otherwise
else:
    return(0)
Bluetooth Throwies

LED throwies are light grafitti Improvised Aesthetic Devices:

http://www.instructables.com/id/E9D2ZJ3FG0EP286JEJ/

They cost around a dollar each to make. Bluetooth 4 beacons cost a lot
more and don’t transmit much information. But good-old-fashioned
Bluetooth devices transmit at least a human readable name. You can get
Arduino-compatible ones for about five dollars:

http://www.ebay.com/sch/i.html?_sacat=0&_from=R40&_nkw=bluetooth+serial+module&rt=nc&LH_BIN=1

And an arduino-compatible chip for three:

http://www.instructables.com/id/Simplest-and-Cheapest-Arduino/

Add a battery and either a magnet or (if that doesn’t mix with the
electronics…) an adhesive pad and we can make a Bluetooth Throwie for
less than ten dollars. Call it five pounds. That’s still at least five
times too much to make them comparable to LED throwies, but maybe in bulk…

So yeah, Bluetooth Throwies. Electromagnetic grafitti Improvised
Aesthetic Devices…

Building The Kobo Reader Sources

I’ve covered this before, but the Kobo Reader sources have changed, so here’s an updated guide to installing and building them.

Create the directory structure:

cd
mkdir kobo
cd kobo
mkdir fs
mkdir tmp

Fetch the Kobo Reader sources:

git clone git clone git://github.com/kobolabs/Kobo-Reader.git KoboLabs

Set bash as your shell (you can set it back afterwards using the same command):

sudo dpkg-reconfigure -plow dash
# Choose "No"

Install the developer tools:

toolchains/gcc-codesourcery-2010q1-202.bin

If you’re on a 64-bit version of the OS, make sure you install the i386 versions of libc6 and any other missing libraries for the installer or the tools (e.g. libXext).

Make symbolic links to the toolchain under the names that Qt’s build system is expecting. Otherwise you will get weird and difficult to diagnose errors:

cd ~/CodeSourcery/Sourcery_G++/bin
for f in arm-none-linux-gnueabi-*; do n=$(echo $f|cut -b 24-); ln -s $f arm-linux-$n; done

Set required environment variables:

echo "export KOBOLABS=$HOME/kobo/KoboLabs" >> ~/.bashrc
echo "export PATH=$PATH:$HOME/CodeSourcery/Sourcery_CodeBench_Lite_for_ARM_GNU_Linux/bin" >> ~/.bashrc
source ~/.bashrc

Create the file ~/kobo/KoboLabs/build/build-config-user.sh with the following contents:

DEVICEROOT=$HOME/kobo/fs
QT_EXTRA_ARGS="--prefix=$HOME/kobo/qt -DQT_NO_QWS_CURSOR -DQT_NO_QWS_QPF2"

Start the build:

cd ~/kobo/tmp
~/kobo/KoboLabs/build/build-all.sh

And then wait…

3D Printing Sigils

100px-Sigil.svg

Sigil CC-BY-SA by bwigfield.

In chaos magick, sigils are visual embodiments of intent used to focus and actualize that intent. Within both supernatural and cognitive theories of magic the principle is the same: sigils are foci for attracting the resources (supernatural or mental) required to achieve the desire of the person who has constructed them.

Traditional sigils are drawings, two dimensional graphical forms, created using magic square or letter abstraction techniques. Contemporary mages have constructed hypermedia sigils in various formats, from comic book series to interactive multimedia installations.

Sigils created by creative computer graphics programming software can be printed cheaply using Open Source 3D printers or online 3D printing services. This opens up a new range of techniques for creating and using sigils.

Image copyright 2011 Marius Watz

Image copyright 2011 Marius Watz

The 3D printed art of Marius Watz shows how data can be modelled in aesthetically appealing three dimensional form, and how the challenges of modelling complex arbitrary data can be met while still creating easily printed models. RIG’s experiments in 3D printed models of user data by distorting pre-existing forms Chernoff Face-style to display a as christmas tree decorations. We Can use these approaches and more (such as model mash-ups and extrusion of 2D sigils) to embody the intent of sigils rather than Web 2.0 data or random numbers in 3D printed form.

azathoth_3d_sigil_letters

Image Copyright Joshua Madara 2011.

Joshua Madara’s¬†3D graphics sigil creator Processing sketch demonstrates the creation of a virtual three dimensional sigil form. The sigil is line-based, to keep its genetic link to magic square-based sigils, and would not be 3D printable in this form. But the lines could be replaced with cylinders or rectangular beams, with the angles of joins between them limited to ensure that they can be printed without support on Open Source 3D printers.

Probability-Lattice-0304-Final-pieces

Image copyright 2011 Marius Watz

Whether Watz’s organic or machinic forms, more object based approaches or something even more abstract, it is easy to see how this can be applied to the construction of sigils. The mapping of letters (or words) to formal properties or objects by software in order to encode them in forms is how 3D printed sigil models can be produced. This adds an extra dimension of reality and relationality between the virtual and the real that affords a corresponding increase in persuasiveness and richness for sigils.

Part of the efficacy of a sigil may come from the mindfulness and concentration involved in manually constructing and chargeing it. If this is the case then having a machine construct the sigil may work against the sigil’s effectiveness. Constructing the code to make the sigil, and watching the mechanical operation of Open Source 3D printers alleviate this. And a better sigil form than could be made by hand will be a better focus, whether produced by magickal or technological means.

Create 3D sigils using creative coding software such as Processing, or in 3D design software such as Blender. Make the software and model files Free Software and Free Culture (GPL the software, BY-SA the models, wherever you can) and empower others to follow in your footsteps. Upload the model to a filesharing site such as Thingiverse. Then print it using an Open Source 3D printer such as a Lulzbot or a 3D printing service such as Shapeways.

A 3D printed sigil can be used as a focus for contemplation, mediation or ritual. It can be placed in work or living space as a reminder and proof of the reality of the objective embodied in the sigil. Or it can be destroyed to release it into the imagination and the world as part of a ritual by burning or by melting using solvents (but beware toxic fumes). Uploading the sigil to a model filesharing site will spread it further into the world as both virtual and, if anyone prints it, as physical form.

GNU/Linux Kobo Build Environment Setup

I’ve bought a Kobo Touch ebook reader device to hack on.

This guide to setting up a build environment for the Kobo is good:

http://www.mobileread.com/forums/showthread.php?p=2176378#post2176378

The only change I’ve found you have to make to build the examples is to add:

LIBS += -lpng -lz

to the Qt project (.pro) file in Sketch.

The build system defaults to 4.6.2. It’s possible to change the build scripts to use Qt 4.8 (grep -r 4.6 ~/kobo/KoboLabs/build/* and change occurrences of 4.6.2 to 4.8, then update the ./configure line). Both are on the device so using 4.6.2 is fine.

I’d like to replace Sourcery G++ with something without a EULA.

Connecting To LambdaMOO Persistently

It’s easy to connect to LambdaMOO whenever you wish from mobile devices or desktop computers, but you can miss out on what’s happening when you’re not online. If you have a desktop computer that you keep permanently online you can keep a MOO client open on it, but then you cannot connect on the move.

If you have a shell account on a server (a server account that allows you to run programs on from the command line rather than just serve web pages) you can run a MOO client there and connect to it from your mobile devices or other computers. This combines the advantages of an always-on connection to the MOO with the advantages of being able to connect wherever you may be.

There are two ways of creating accessible persistent connections, each with its own advantages and disadvantages:

  1. You can run TinyFugue in a GNU Screen session, which allows you to connect securely using an SSH client. What you gain in security you may lose in spell checking on mobile devices.
  2. Alternatively you can run Mooproxy, which integrates better with dedicated MOO clients but still uses the insecure Telnet protocol to communicate with your server.

TinyFugue In A GNU Screen Session

TinyFugue can be run in an ssh session on a server, but when you disconnect it will exit. To create a persistent TinyFugue session you can run it in GNU Screen. When you run Screen, it opens a new command line terminal that will continue running even when you disconnect from the server. And anything that you run within Screen will keep running as well. You can then reconnect to Screen the next time you connect to the server, and your TinyFugue session will still be running.

Setting up SSH connections to a server is outside the scope of this article. SSH clients are available for every desktop and mobile operating system. Install one, and find out how to set up public key authentication from it to your server.

To install TinyFugue and Screen on a GNU/Linux server you will need to be able to run commands as an administrator. You install them using the command-line package manager on the server by typing something like:

sudo apt-get install tf5 screen

or

sudo yum install tf5 screen

Next, set up the .tfrc file in your home directory:

echo "/world lambdamoo lambda.moo.mud.org Guest" >> ~/.tfrc

or, if you have a registered character:

echo "/world lambdamoo lambda.moo.mud.org USERNAME PASSWORD" >> ~/.tfrc

You only need to do that once.

Then you can start a Screen session with tinyfugue running in it by typing:

screen tf5

You don’t need to quit the MOO or TinyFugue when you wish to leave, just close the SSH connection.

Then when you reconnect, type:

screen -DRRU

This will reconnect to your previous screen session. If for some reason the session has ended, for example if the server has been rebooted, you can restart TinyFugue once Screen starts by typing:

tf5

When you reconnect to Screen from a different device your new connection will inherit the TinyFugue session any other devices will lose access to it.

Mooproxy

Mooproxy is a buffering network proxy for MOO connections. It runs on your server keeping a persistent connection open to the MOO, and then you connect your MOO client to it rather than to the MOO.

To install it type:

sudo apt-get install mooproxy

or:

sudo yum install mooproxy

To configure it, generate a hash of your connection command for LambdaMOO:

mooproxy --md5crypt
connect CHARACTER PASSWORD

and make a note of the string that mooproxy prints in return.

Then create the file:

~/.mooproxy/worlds/lambdamoo

(you may need to create the ~/.mooproxy/worlds directory using:
mkdir -p ~/.mooproxy/worlds/ )

and enter the following into it:

# The port to listen to on *your* server
listenport = 8899
# The hash of your connection command, for security
auth_md5hash = "THE HASH OF YOUR CONNECTION COMMAND FROM moocode --md5hash"
# The actual MOO server address
host = "lambda.moo.mud.org"
# Tha actual MOO server port
port = 8888
# Connect to the MOO automatically on start
autologin = true
# Reconnect to the MOO if the connection is lost
autoreconnect = true
# Lines starting with this character will be interpreted as command to mooproxy
commandstring = "/"
# Mooproxy should send this many lines of history to you when you connect
context_on_connect = 100

To set it running in the background as a daemon, type:

mooproxy -w lambdamoo

This will start mooproxy using the settings from the world file ~/.mooproxy/worlds/lambdamoo .

To connect to LambdaMOO, set your MOO client to connect to ,ooproxy on your server. Use your character name and password for LambdaMOO , but rather than using LambdaMOO’s host name and port, use the host name and port of the server that you are running mooproxy on.

As with screen/TinyFugue, if you connect to mooproxy from a different device your new connection will inherit the mooproxy session any other devices will lose access to it.

To debug your mooproxy connection, you can check its logs at:

~/.mooproxy/logs/lambdamoo/

The most common errors are to not enter the correct connection string when running mooproxy --md5crypt, or to not open the port that you have set mooproxy to use in the firewall.

MakerBot Replicator: What I Have Learnt So Far

Test PrintsHere’s what I’ve learnt about the practical side of using my MakerBot Replicator so far.

If you have a dual extrusion system, make sure the two extrusion nozzles are exactly the same height when you first install them. You may need to tighten screws or insert pieces of paper to do so.

When you first adjust the build platform, take your time. Getting the
platform the right height and levelled is vital to getting a good print. Too high and the extruder nozzles will scrape the tape. Too low and the extruded plastic wont stick.

Re-level the build platform at least weekly to make sure it remains in the best possible position.

Clean the Kapton tape between prints to help with print adhesion. You should clean it with acetone, or in a pinch you can use nail polish remover.

When the Kapton tape on the platform looks like it’s ready to be
replaced, it’s very easy to do so if you use the squeegee that came in
the box. Peel off all the old tape, then unroll the new tape a strip at a time. You can lift up the tape and squeegee it down several times if
needs be to get rid of air bubbles. Just keep it taut as you unroll it
onto the platform.

Heat the build platform to at least 110 degrees celsius, possibly 115 or 120. This will help prevent curling of edges.

Don’t be tempted to try rafts with dual extrusion. There is no way that will end well, no matter how carefully you set up the model. See above.

And if you do have a problem, the MakerBot mailing list, the MakerBot forums, and MakerBot tech support are the most amazingly helpful community I’ve encountered in a long time.

Psychogeodata (3/3)

cemetary random walk

The examples of Psychogeodata given so far have used properties of the geodata graph and of street names to guide generation of Dérive. There are many more ways that Psychogeodata can be processed, some as simple as those already discussed, some much more complex.

General Strategies

There are some general strategies that most of the following techniques can be used as part of.

  • Joining the two highest or lowest examples of a particular measure.

  • Joining the longest run of the highest or lowest examples of a particular measure.

  • Joining a series of destination waypoints chosen using a particular measure.

The paths constructed using these strategies can also be forced to be non-intersecting, and/or the waypoints re-ordered to find the shortest journey between them.

Mathematics

Other mathematical properties of graphs can produce interesting walks. The length of edges or ways can be used to find sequences of long or short distances.

Machine learning techniques, such as clustering, can arrange nodes spatially or semantically.

Simple left/right choices and fixed or varying degrees can create zig-zag or spiral paths for set distances or until the path self-intersects.

Map Properties

Find long or short street names or street names with the most or fewest words or syllables and find runs of them or use them as waypoints.

Find all the street names on a particular theme (colours, saints’ names, trees) and use them as waypoints to be joined in a walk.

Streets that are particularly straight or crooked can be joined to create rough or smooth paths to follow.

If height information can be added to the geodata graph, node elevation can be used as a property for routing. Join high and low points, flow downhill like water, or find the longest runs of valleys or ridges.

Information about Named entities extracted from street, location and district names from services such as DBPedia or Freebase and used to connect them. Dates, historical locations, historical facts, biographical or scientific information and other properties are available from such services in a machine-readable form.

Routing between peaks and troughs in sociological information such as population, demographics, crime occurrence, ploitical affiliation, property prices can produce a journey through the social landscape.

Locations of Interest

Points of interest in OpenStreetMap’s data are represented by nodes tagged as “historic”, “amenity”, “leisure”, etc. . It is trivial to find these nodes to use as destinations for walks across the geodata graph. They can then be grouped and used as waypoints in a route that will visit every coffee shop in a town, or one of each kind of amenity in alphabetical order, in an open or closed path for example. Making a journey joining each location with a central base will produce a star shape.

Places of worship (or former Woolworth stores can be used to find https://en.wikipedia.org/wiki/Ley_line using linear regression or the techniques discussed below in “Geometry and Computer Graphics”.

Semantics

The words of poems or song lyrics (less stopwords), matched either directly or through hypernyms using Wordnet, can be searched for in street and location names to use as waypoints in a path. Likewise named entities extracted from stories, news items and historical accounts.

More abstract narratives can be constructed using concepts from The Hero’s Journey.

Nodes found using any other technique can be grouped or sequenced semantically as waypoints using Wordnet hypernym matching.

Isomorphism

Renamed Tube maps, and journeys through one city navigated using a map of another, are examples of using isomorphism in Psychogeography.

Entire city graphs are very unlikely to be isomorphic, and the routes between famous locations will contain only a few streets anyway, so sub-graphs are both easier and more useful for matching. Better geographic correlations between locations can be made by scoring possible matches using the lengths of ways and the angles of junctions. Match accuracy can be varied by changing the tolerances used when scoring.

Simple isomorphism checking can be performed using The NetworkX library’s functions . Projecting points from a subgraph onto a target graph then brute-force searching for matches by varying the matrix used in the projection and scoring each attempt based on how closely the points match . Or Isomorphisms can be bred using genetic algorithms, using degree of isomorphism as the fitness function and proposed subgraphs as the population.

The Social Graph

Another key contemporary application of graph theory is Social Network Analysis. The techniques and tools from both the social science and web 2.0 can be applied directly to geodata graphs.

Or the graphs of people’s social relationships from Facebook, Twitter and other services can mapped onto their local geodata graph using the techniques from “Isomorphism” above, projecting their social space onto their geographic space for them to explore and experience anew.

Geometry and Computer Graphics

Computer geometry and computer graphics or computer vision techniques can be used on the nodes and edges of geodata to find forms.

Shapes can be matched by using them to cull nodes using an insideness test or to find the nearest points to the lines of the shape. Or line/edge intersection can be used. Such matching can be made fuzzy or accurate using the matching techniques in “Isomorphism”.

Simple geometric forms can be found – triangles, squares and quadrilaterals, stars. Cycle bases may be a good source of these. Simple shapes can be found – smiley faces, house shapes, arrows, magical symbols. Sequences of such forms can be joined based on their mathematical properties or on semantics.

For more complex forms, face recognition, object recognition, or OCR algorithms can be used on nodes or edges to find shapes and sequences of shapes.

Classic computer graphics methods such as L-sytems, turtle graphics, Conway’s Game of Life, or Voronoi diagrams can be applied to the Geodata graph in order to produce paths to follow.

Geometric animations or tweens created on or mapped onto the geodata graph can be walked on successive days.

Lived Experience

GPS traces generated by an individual or group can be used to create new journeys relating to personal or shared history and experience. So can individual or shared checkins from social networking services. Passenger level information for mass transport services is the equivalent for stations or airports.

Data streams of personal behaviour such as scrobbles, purchase histories, and tweets can be fetched and processed semantically in order to map them onto geodata. This overlaps with “Isomorphism”, “Semantics”, and “The Social Graph” above.

Sensor Data

Temperature, brightness, sound level, radio wave, radiation, gravity and entropy levels can all be measured or logged and used as weights for pathfinding. Ths brings Psychogeodata into the realm of Psychogeophysics.

Conclusion

This series of posts has made the case for the concept, practicality, and future potential of Psychogeodata. The existing code produces interesting results, and there’s much more that can be added and experienced.

(Part one of this series can be found here, part two can be found here . The source code for the Psychogeodata library can be found here .)

Psychogeodata (2/3)

derive_sem

Geodata represents maps as graphs of nodes joined by edges (…as points joined by lines). This is a convenient representation for processing by computer software. Other data can be represented in this way, including words and their relationships.

We can map the names of streets into the semantic graph of WordNet using NLTK. We can then establish how similar words are by searching the semantic graph to find how far apart they are. This semantic distance can be used instead of geographic distance when deciding which nodes to choose when pathfinding.

Mapping between these two spaces (or two graphs) is a conceptual mapping, and searching lexicographic space using hypernyms allows abstraction and conceptual slippage to be introduced into what would otherwise be simple pathfinding. This defamiliarizes and conceptually enriches the constructed landscape, two key elements of Psychogeography.

The example above was created by the script derive_sem, which creates random walks between semantically related nodes. It’s easy to see the relationship between the streets it has chosen. You can see the html version of the generated file here, and the script is included with the Psychogeodata project at https://gitorious.org/robmyers/psychogeodata .

(Part one of this series can be found here, part three will cover potential future directions for Psychogeodata.)