fusion – THE HYPERTEXT http://www.thehypertext.com Thu, 10 Dec 2015 06:10:15 +0000 en-US hourly 1 https://wordpress.org/?v=5.0.4 Candidate Image Explorer http://www.thehypertext.com/2015/09/17/candidate-image-explorer/ Thu, 17 Sep 2015 15:53:26 +0000 http://www.thehypertext.com/?p=700 For this week's homework in Designing for Data Personalization with Sam Slover, I made progress on a project that I'm working on for Fusion as part of their 2016 US Presidential Election coverage.

Read More...

]]>
For this week’s homework in Designing for Data Personalization with Sam Slover, I made progress on a project that I’m working on for Fusion as part of their 2016 US Presidential Election coverage. I began this project by downloading all the images from each candidate’s Twitter, Facebook, and Instagram account — about 60,000 in total — then running those images through Clarifai‘s convolutional neural networks to generate descriptive tags.

With all the images hosted on Amazon s3, and the tag data hosted on parse.com, I created a simple page where users can explore the candidates’ images by topic and by candidate. The default is all topics and all candidates, but users can narrow the selection of images displayed by making multiple selections from each field. Additionally, more images will load as you scroll down the page.

Screen Shot 2015-09-17 at 11.24.47 AM

Screen Shot 2015-09-17 at 11.30.30 AM

Screen Shot 2015-09-17 at 11.20.50 AM

Screen Shot 2015-09-17 at 11.28.45 AM

Screen Shot 2015-09-17 at 11.25.42 AM

Screen Shot 2015-09-17 at 11.19.53 AM

Unfortunately, the AI-enabled image tagging doesn’t always work as well as one might hope.

Screen Shot 2015-09-17 at 11.23.49 AM

Here’s the page’s JavaScript code:

var name2slug = {};
var slug2name = {};

Array.prototype.remove = function() {
    var what, a = arguments, L = a.length, ax;
    while (L && this.length) {
        what = a[--L];
        while ((ax = this.indexOf(what)) !== -1) {
            this.splice(ax, 1);
        }
    }
    return this;
}

Array.prototype.chunk = function(chunkSize) {
    var array=this;
    return [].concat.apply([],
        array.map(function(elem,i) {
            return i%chunkSize ? [] : [array.slice(i,i+chunkSize)];
        })
    );
}

function dateFromString(str) {
	var m = str.match(/(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)Z/);
	var date = new Date(Date.UTC(+m[1], +m[2], +m[3], +m[4], +m[5], +m[6]));
	var options = {
	    weekday: "long", year: "numeric", month: "short",
	    day: "numeric", hour: "2-digit", minute: "2-digit"
	};
	return date.toLocaleTimeString("en-us", options);
}

function updatePhotos(query) {
	$.ajax({
		url: 'https://api.parse.com/1/classes/all_photos?limit=1000&where='+JSON.stringify(query),
		type: 'GET',
		dataType: 'json',
		success: function(response) {
			// console.log(response);
			$('#img-container').empty();

			var curChunk = 0;
			var resultChunks = response['results'].chunk(30);

			function appendPhotos(chunkNo) {

				resultChunks[chunkNo].map(function(obj){
					var date = dateFromString(obj['datetime'])
					var imgUrl = "https://s3-us-west-2.amazonaws.com/electionscrape/" + obj['source'] + "/400px_" + obj['filename'];
					var fullImgUrl = "https://s3-us-west-2.amazonaws.com/electionscrape/" + obj['source'] + "/" + obj['filename'];
					$('#img-container').append(
						$('<div class=\"grid-item\"></div>').append(
							'<a href=\"'+fullImgUrl+'\"><img src=\"'+imgUrl+'\" width=\"280px\"></a><p>'+slug2name[obj['candidate']]+'</p><p>'+date+'</p><p>'+obj['source']+'</p>'
						) // not a missing semicolon
					);
					// console.log(obj['candidate']);
					// console.log(obj['datetime']);
					// console.log(obj['source']);
					// console.log(obj['filename']);
				});

			}

			appendPhotos(curChunk);

			window.onscroll = function(ev) {
			    if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
			        curChunk++;
			        appendPhotos(curChunk);
			    }
			};


		},
		error: function(response) { "error" },
		beforeSend: setHeader
	});
}

function setHeader(xhr) {
	xhr.setRequestHeader("X-Parse-Application-Id", "ID-GOES-HERE");
	xhr.setRequestHeader("X-Parse-REST-API-Key", "KEY-GOES-HERE");
}

function makeQuery(candArr, tagArr) {

	orArr = tagArr.map(function(tag){
		return { "tags": tag };
	})

	if (tagArr.length === 0 && candArr.length > 0) {
		var query = {
			'candidate': {"$in": candArr}
		};
	}
	else if (tagArr.length > 0 && candArr.length === 0) {
		var query = {
			'$or': orArr
		};
	}
	else if (tagArr.length === 0 && candArr.length === 0) {
		var query = {};
	}
	else {
		var query = {
			'candidate': {"$in": candArr},
			'$or': orArr
		};
	}

	updatePhotos(query);

}

(function(){

$('.grid').masonry({
  // options
  itemSelector: '.grid-item',
  columnWidth: 300
});

var selectedCandidates = [];
var selectedTags = [];

$.getJSON("data/candidates.json", function(data){
	var candNames = Object.keys(data).map(function(slug){
		var name = data[slug]['name'];
		name2slug[name] = slug;
		slug2name[slug] = name;
		return name;
	}).sort();

	candNames.map(function(name){
		$('#candidate-dropdown').append(
			'<li class=\"candidate-item\"><a href=\"#\">'+name+'</a></li>'
		);
	});

	$('.candidate-item').click(function(){
		var name = $(this).text();
		var slug = name2slug[name];
		if ($.inArray(slug, selectedCandidates) === -1) {
			selectedCandidates.push(slug);
			makeQuery(selectedCandidates, selectedTags);
			console.log(selectedCandidates);
			$('#selected-candidates').append(
				$('<button class=\"btn btn-danger btn-xs cand-select-btn\"><span class=\"glyphicon glyphicon-remove\" aria-hidden=\"true\"></span>'+name+'</button>')
					.click(function(){
						$(this).fadeOut("fast", function(){
							selectedCandidates.remove(name2slug[$(this).text()]);
							makeQuery(selectedCandidates, selectedTags);
							console.log(selectedCandidates);
						});
					}) // THIS IS NOT A MISSING SEMI-COLON
			);
		}
	});
});


$.getJSON("data/tags.json", function(data){
	var tags = data["tags"].sort();
	tags.map(function(tag){
		$('#tag-dropdown').append(
			'<li class=\"tag-item\"><a href=\"#\">'+tag+'</a></li>'
		);
	});

	$('.tag-item').click(function(){
		var tag = $(this).text();
		if ($.inArray(tag, selectedTags) === -1) {
			selectedTags.push(tag);
			makeQuery(selectedCandidates, selectedTags);
			console.log(selectedTags);
			$('#selected-tags').append(
				$('<button class=\"btn btn-primary btn-xs tag-select-btn\"><span class=\"glyphicon glyphicon-remove\" aria-hidden=\"true\"></span>'+tag+'</button>')
					.click(function(){
						$(this).fadeOut("fast", function(){
							selectedTags.remove($(this).text());
							makeQuery(selectedCandidates, selectedTags);
							console.log(selectedTags);
						});
					})
			);
		}
	});
});

makeQuery(selectedCandidates, selectedTags);

})();

 

 

]]>
Summer Projects http://www.thehypertext.com/2015/09/09/summer-projects/ Wed, 09 Sep 2015 04:16:21 +0000 http://www.thehypertext.com/?p=622 I made some bizarre and wondrous things this summer.

Read More...

]]>
This summer, I wrote a TF-IDF clustering library for my internship at Ufora, and I’m currently working on a long-term project for Fusion to track and analyze online data from candidates in the 2016 US Presidential Election.

Needless to say, those two projects have kept me busy, but I also built a few random things for fun in my spare time. And at this point, the total number of things I’ve built has become too cumbersome for an image-oriented portfolio, so I switched to a Google Spreadsheet-powered method, trusted by Darius Kazemi and (as I’ve learned from my Fusion co-conspirator Daniel McLaughlin) countless data hackers at news and media organizations across the country, whose routine CMS systems may require knowledge of COBOL, or worse, Microsoft Sharepoint.

The personal projects I completed over the summer were:

  • MeterMap | Maps clauses from a text corpus onto the metrical structure of a poem.
  • itpbot | IRC bot for the #itp channel on irc.freenode.com
  • PaletteKnife | Tool that extracts color palettes from photographs, created entirely in front-end JavaScript using p5.js
  • Dick Fractal | Personal website of Richard “Dick” Fractal, Ph.D.
  • Four Oh Four | A URLae
  • @BizarroMOMA | Twitter bot tweeting fictional artworks generated from the Museum of Modern Art’s collections data

Certain folks (namely, my parents) have complained to me for a long time that my website does not contain very much information about what I actually make. For their sake, and my own sanity, I added a list of projects below the introductory letter. I did this by writing a Python script that parses a Google Spreadsheets-generated CSV into a JSON file — I plan to implement the script in JavaScript soon, so that my website updates automatically.

We were also supposed to dig into some JSON for Designing for Data Personalization with Sam Slover, so this task fulfilled multiple roles for me.

Here’s the Python script:

import csv
import json
import datetime
import requests

CSV_URL = ""

response = requests.get(CSV_URL)

with open('projects.csv', 'w') as outfile:
	outfile.write(response.text)

fileObj = open('projects.csv', 'r')

reader = csv.DictReader(fileObj)

projList = []

for row in reader:
	projList.append(row)

fileObj.close()

def get_date(projObj):
	m, d, y = map(int, projObj['Date of Completion'].split('/'))
	dateObj = datetime.datetime(y, m, d, 0, 0)
	return -(dateObj - datetime.datetime(1970,1,1)).total_seconds()

projList = sorted(projList, key=get_date)


with open('projects.json', 'w') as outfile:
	json.dump(projList, outfile)

And here’s the raw JSON:

[
   {
      "Code URL":"https://github.com/rossgoodwin/itpbot",
      "XS S M L XL":"M",
      "Name":"itpbot",
      "Timestamp":"8/16/2015 12:45:21",
      "Documentation URL":"",
      "Date of Completion":"8/14/2015",
      "Main URL":"http://rossgoodwin.com/itpbot",
      "Output URL":"",
      "Description":"IRC bot for the #itp channel on irc.freenode.com"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/paletteknife",
      "XS S M L XL":"S",
      "Name":"Palette Knife",
      "Timestamp":"8/16/2015 1:00:47",
      "Documentation URL":"",
      "Date of Completion":"7/30/2015",
      "Main URL":"http://rossgoodwin.com/paletteknife/",
      "Output URL":"",
      "Description":"Tool that extracts color palettes from photographs, created entirely in front-end JavaScript using p5.js"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/bizarromoma",
      "XS S M L XL":"XS",
      "Name":"@BizarroMoMA",
      "Timestamp":"8/16/2015 12:05:27",
      "Documentation URL":"",
      "Date of Completion":"7/23/2015",
      "Main URL":"https://twitter.com/bizarromoma",
      "Output URL":"",
      "Description":"Twitter bot tweeting fictional artworks generated from the Museum of Modern Art's collections data"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/metermap",
      "XS S M L XL":"M",
      "Name":"MeterMap",
      "Timestamp":"8/17/2015 18:28:55",
      "Documentation URL":"",
      "Date of Completion":"7/21/2015",
      "Main URL":"http://rossgoodwin.com/metermap/",
      "Output URL":"http://rossgoodwin.com/faulkner_wasteland.txt",
      "Description":"Maps clauses from a text corpus onto the metrical structure of a poem."
   },
   {
      "Code URL":"https://github.com/rossgoodwin/tweetingpoints/tree/gh-pages",
      "XS S M L XL":"S",
      "Name":"Tweeting Points",
      "Timestamp":"8/17/2015 18:40:42",
      "Documentation URL":"",
      "Date of Completion":"7/21/2015",
      "Main URL":"http://rossgoodwin.com/tweetingpoints/",
      "Output URL":"",
      "Description":"Real-time word clouds of the most recent tweets from candidates in the 2016 U.S. Presidential Election"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/dickfractal",
      "XS S M L XL":"S",
      "Name":"Dick Fractal",
      "Timestamp":"8/16/2015 12:18:28",
      "Documentation URL":"",
      "Date of Completion":"7/5/2015",
      "Main URL":"http://dickfractal.com",
      "Output URL":"",
      "Description":"Dick Fractal, Ph.D."
   },
   {
      "Code URL":"https://github.com/rossgoodwin/fourohfour",
      "XS S M L XL":"S",
      "Name":"Four Oh Four",
      "Timestamp":"8/16/2015 12:22:14",
      "Documentation URL":"",
      "Date of Completion":"6/18/2015",
      "Main URL":"http://rossgoodwin.com/fourohfour.pdf",
      "Output URL":"",
      "Description":"A URLae is a poetic form consisting of a list of URLs. Four Oh Four is a URLae composed entirely of generated links."
   },
   {
      "Code URL":"",
      "XS S M L XL":"XL",
      "Name":"The Traveler's Lamp",
      "Timestamp":"8/17/2015 18:35:21",
      "Documentation URL":"http://www.thehypertext.com/tag/travelers-lamp/",
      "Date of Completion":"5/8/2015",
      "Main URL":"http://www.thehypertext.com/2015/05/08/travelers-lamp-part-ii/",
      "Output URL":"",
      "Description":"3D-printed cities inspired by those from Invisible Cities by Italo Calvino, which light up in sequence as a computer solves the Traveling Salesman Problem for the distances between them"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/photosynthesis",
      "XS S M L XL":"XL",
      "Name":"word.camera",
      "Timestamp":"8/16/2015 12:10:53",
      "Documentation URL":"http://www.thehypertext.com/tag/word-camera/",
      "Date of Completion":"4/11/2015",
      "Main URL":"https://word.camera",
      "Output URL":"https://word.camera/i/nxDYEXM9R",
      "Description":"As artificial intelligence technology changes our world, it grants us creative possibilities not previously thought possible. word.camera explores one particular AI technology, convolutional neural networks that can generate descriptive words from images, and realizes possibilities for a new type of camera and a new type of photography."
   },
   {
      "Code URL":"https://github.com/rossgoodwin/wraithhimself",
      "XS S M L XL":"XS",
      "Name":"@WraithHimself",
      "Timestamp":"8/16/2015 21:45:54",
      "Documentation URL":"",
      "Date of Completion":"3/9/2015",
      "Main URL":"https://twitter.com/WraithHimself",
      "Output URL":"",
      "Description":"Twitter bot tweeting the entirely of Infinite Jest by David Foster Wallace, one word at a time, as the first word of retweets"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/gutenflag",
      "XS S M L XL":"M",
      "Name":"@GutenFlag",
      "Timestamp":"8/16/2015 21:44:05",
      "Documentation URL":"http://www.thehypertext.com/2015/03/10/gutenflag/",
      "Date of Completion":"3/7/2015",
      "Main URL":"https://twitter.com/GutenFlag",
      "Output URL":"",
      "Description":"Twitter bot that recommends Project Gutenberg ebooks based on topics extracted from users' most recent tweets"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/drgonzo",
      "XS S M L XL":"M",
      "Name":"Dr. Gonzo",
      "Timestamp":"8/17/2015 19:00:17",
      "Documentation URL":"",
      "Date of Completion":"2/17/2015",
      "Main URL":"http://www.thehypertext.com/2015/02/19/dr-gonzo/",
      "Output URL":"",
      "Description":"Hunter S. Thompson therapist chat bot"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/clock/tree/gh-pages",
      "XS S M L XL":"S",
      "Name":"Text Clock",
      "Timestamp":"8/16/2015 12:29:06",
      "Documentation URL":"http://www.thehypertext.com/2015/04/11/text-clock/",
      "Date of Completion":"12/25/2014",
      "Main URL":"http://rossgoodwin.com/clock/",
      "Output URL":"",
      "Description":"The complete Project Gutenberg corpus as a clock"
   },
   {
      "Code URL":"",
      "XS S M L XL":"XS",
      "Name":"Cut-Up",
      "Timestamp":"8/16/2015 21:48:55",
      "Documentation URL":"",
      "Date of Completion":"12/15/2014",
      "Main URL":"https://github.com/rossgoodwin/cutup",
      "Output URL":"",
      "Description":"The Cut-Up Method of Brion Gysin, as described by William S. Burroughs, implemented in Python"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/mtg",
      "XS S M L XL":"XL",
      "Name":"The Mechanical Turk's Ghost",
      "Timestamp":"8/16/2015 22:06:09",
      "Documentation URL":"http://www.thehypertext.com/tag/chess/",
      "Date of Completion":"12/8/2014",
      "Main URL":"http://www.thehypertext.com/2015/01/05/the-mechanical-turks-ghost-part-v/",
      "Output URL":"",
      "Description":"Chessboard that generates music based on computational analysis of a game in real time, and shakes pieces off the board when it determines either player is within range of checkmate"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/ficgen",
      "XS S M L XL":"L",
      "Name":"Fiction Generator",
      "Timestamp":"8/16/2015 12:34:49",
      "Documentation URL":"http://www.thehypertext.com/tag/fiction-generator/",
      "Date of Completion":"12/3/2014",
      "Main URL":"http://fictiongenerator.com",
      "Output URL":"http://rossgoodwin.com/tricks_of_the_trade.pdf",
      "Description":"Generates novels according to user-defined parameters using content scraped from gutenberg.org, tvtropes.org, scp-wiki.net, and erowid.org."
   },
   {
      "Code URL":"https://github.com/rossgoodwin/typingtutor",
      "XS S M L XL":"L",
      "Name":"Stenogloves",
      "Timestamp":"8/17/2015 18:45:32",
      "Documentation URL":"http://www.thehypertext.com/tag/keyboard/",
      "Date of Completion":"11/25/2014",
      "Main URL":"http://www.thehypertext.com/2014/12/09/stenogloves-part-iii/",
      "Output URL":"",
      "Description":"10-key chorded keyboard with alternative typing scheme"
   },
   {
      "Code URL":"",
      "XS S M L XL":"L",
      "Name":"che55",
      "Timestamp":"8/17/2015 18:04:52",
      "Documentation URL":"http://www.thehypertext.com/2014/09/29/general-update/",
      "Date of Completion":"11/2/2014",
      "Main URL":"https://github.com/rossgoodwin/che55",
      "Output URL":"",
      "Description":"Chess GUI and visualizer, written in Processing"
   },
   {
      "Code URL":"https://github.com/dothething/maze",
      "XS S M L XL":"M",
      "Name":"Scary Maze Game",
      "Timestamp":"8/17/2015 18:50:33",
      "Documentation URL":"",
      "Date of Completion":"10/13/2014",
      "Main URL":"http://www.thehypertext.com/2014/10/25/scary-maze-game/",
      "Output URL":"",
      "Description":"A biofeedback game that gets harder when you get scared (with Jerllin Chang and Changyeon Lee)"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/poetizer",
      "XS S M L XL":"L",
      "Name":"Poetizer",
      "Timestamp":"8/17/2015 18:57:00",
      "Documentation URL":"",
      "Date of Completion":"8/20/2014",
      "Main URL":"http://www.thehypertext.com/2014/08/31/poetizer/",
      "Output URL":"http://www.thehypertext.com/2014/09/02/more-poetizer-output/",
      "Description":"Generates free or fixed verse poetry from any text corpus"
   },
   {
      "Code URL":"https://github.com/rossgoodwin/hmap",
      "XS S M L XL":"S",
      "Name":"hmap",
      "Timestamp":"8/17/2015 18:15:16",
      "Documentation URL":"",
      "Date of Completion":"6/12/2014",
      "Main URL":"http://rossgoodwin.com/hmap/",
      "Output URL":"",
      "Description":"Image histogram remapping tool; maps the colors from one image onto another image (created with Anthony Kesich)"
   }
]

And here’s the JavaScript that parses the JSON to my website:

(function(){

$.getJSON("projects.json", function(data){

    $.each(data, function(ix, obj) {

        if (obj['Documentation URL'] != '') {
            var docChunk = ' <a target=\"_blank\" class="btn btn-xs btn-default" href=\"' + obj['Documentation URL'] + '\">documentation</a>';
        } else {
            var docChunk = '';
        }

        if (obj['Output URL'] != '') {
            var outChunk = ' <a target=\"_blank\" class="btn btn-xs btn-default" href=\"' + obj['Output URL'] + '\">output</a>';
        } else {
            var outChunk = '';
        }

        if (obj['Code URL'] != '') {
            var codeChunk = ' <a target=\"_blank\" class="btn btn-xs btn-default" href=\"' + obj['Code URL'] + '\">code</a>';
        } else {
            var codeChunk = '';
        }

        $('#'+obj['XS S M L XL']).append(
            '<p class="project-desc"><a target=\"_blank\" class="btn btn-xs btn-danger" href=\"'+obj['Main URL']+'\">'+obj['Name']+'</a>'+ docChunk + outChunk + codeChunk + '<br>' + obj['Description']+'</p>'
        );

    });

});

})();

 

]]>