Moved and seconded

https://thefederalist.com/2014/01/17/the-death-of-expertise/

Great summation of the nature of discourse in the information age, especially online/social media:

I fear we are witnessing the “death of expertise”: a Google-fueled, Wikipedia-based, blog-sodden collapse of any division between professionals and laymen, students and teachers, knowers and wonderers – in other words, between those of any achievement in an area and those with none at all. 

By Tom Nichols

And new fuel for comment battles (which I do not partake in ever since being on IRC at 12, knowing how that goes and at a time in my life when I thought I knew much about the world but this was in fact true:

 the dumber you are [with regards to knowledge on an erudite subject – PJT], the more confident you are that you’re not actually dumb

by Cornell University psychologists David Dunning

(first new post in rebuilding blog, posted link in 26/01/2014 – 10:19 but found so pertinent today that had to add a few details)

Face Recognition REST API

FaceDiff facediff.com will soon launch with a full REST API for developers who need face recognition in their apps, sites, or hardware.

An preliminary example from JavaScript (JQuery plugin that calls the API) is this:

$.ceus = {
	faceDiffQueue: [],
	faceDiffResults: [],
	faceDiffCallback: null,
	
	sendIndex: 0,

	sendNext: null,
	
	errorCallback: function (err) {
		$.ceus.faceDiffQueue = [];
		console.log("ERROR:" + err.data);
	},
	/* @function faceDiff - given a photo of a crowd returns face locations and predicted person
	returns: {
	[coords]
	{"face": 1, "predicted" : 95, "confidence": 15648.969559, "label":"Joe Smith"}, 
	{"face": n, "predicted" : 104, "confidence": 8588.871966, "label":"Jane Doe"}
	}

		coords (see photoInfo)

		face: index of face in coords
		predicted: person match
		label: person's name for convenience

	 * required: clientData, photoUrl
	*/
	faceDiff: function (opt, retFunc) {
    	$.post('service', {action: 'faceDiff', photoUrl: opt.photoUrl, clientData: opt.clientData}, function (response) {
    		var response = JSON.parse(response);
    		if (response.success != 1) {
    			response.data = data;
    			errorCallback(response);
    			return;
    		}
    		retFunc(response);
    	});
	},

	/* @function addFbSource -- adds a new source for people to match with type of facebook friends
	 * returns: {source}
	 * required: fbuid
	 */
	addFbSource: function (fbuid, retFunc) {
    	$.post('service', {action: 'addSource', type: SOURCE_TYPE_FB, data: JSON.stringify({uid:fbuid})}, function (response) {
	    		var response = JSON.parse(response);
	    		if (response.success != 1) {
	    			response.data = data;
	    			errorCallback(response);
	    			return;
	    		}
	    		retFunc(response);
	    	}
    	);
    },


	/* @function toggleSourceItem - adds a new person or toggles source item if person exists
	 * returns: {person,facecoords,status}
	 * required: source, sourceref, photoUrl, name
	 */
	toggleSourceItem: function (opt, retFunc) {
		$.post('service', {
    		action: 'toggleSourceItem', 
    		source: opt.source, 
    		sourceref: opt.sourceref,
    		photoUrl: opt.photoUrl,
    		name: opt.name
    			}, function (data) {
	    		var response = JSON.parse(data);
	    		if (response.success != 1) {
	    			// error
	    			response.data = data;
	    			errorCallback(response);
	    			return;
	    		}
	    		retFunc(response);
    		});
	},

	/* @function togglePersonItem - adds a new match photo to a person or toggles if exists
	 * returns: {photo,facecoords,status,results}
	 * required: person, sourceref, photoUrl
     */
	togglePersonItem : function (opt, retFunc) {
		$.post('service', {
    		action: 'togglePersonItem', 
    		person: '' + opt.person,
    		sourceref: '' + opt.sourceref,
    		photoUrl: opt.photoUrl
    			}, function (response) {
	    		var response = JSON.parse(response);
	    		if (response.success != 1) {
	    			// error
	    			response.data = data;
	    			errorCallback(response);
	    			return;
	    		}
	    		retFunc(response);
    		});
	},
	
	/* @function getPhotoInfo - gets coordinates of faces in a photo
	 * returns: {clientData,photo,status,image,facecoords}
	 * clientData: clientData you pass
	 * photo: photo ID
	 * status: photo status
	 * image: image ID
	 * facecoords: array of w,h,x,y,w2,h2,x2,y2,etc
	 * required: person, sourceref, photoUrl
	 * sourceref is the id of the photo, not the person
	 */
	getPhotoInfo: function (opt, retFunc) {
		$.post('service', {
    		action: 'getPhotoInfo', 
    		person: '' + opt.person,
    		sourceref: '' + opt.sourceref,
    		photoUrl: opt.photoUrl,
    		clientData: opt.clientData
    			}, function (response) {
	    		var response = JSON.parse(response);
	    		if (response.success != 1) {
	    			// error
	    			$.ceus.faceDiffQueue = [];
	    			response.data = data;
	    			errorCallback(response);
	    			return;
	    		}
	    		retFunc(response);
    		});
	},

	/* @function togglePhotoFace - tells facediff that a specific rectangle within a person's photo is that person's face
	 * these photos are used as the basis for all matching functions, specifically faceDiff api calls
	 *
	 * returns: {clientData,photo,status,image,facecoords}
	 * 
	 * if photo is excluded because togglePersonItem result unknown,
	 * call will result in togglePersonItem when sourceref doesn't exist
	 * (hence photoUrl argument)
	 * 
	 * required: person, [photo], sourceref, photoUrl, src_[w,h,x,y]
	 * 
	 * photoUrl is the url for the source photo of the rect
	 */
	togglePhotoFace : function (opt, retFunc) {
		$.post('service', {
	   		action: 'togglePhotoFace', 
	   		person: '' + opt.person,
	   		photo: '' + opt.photo,
			sourceref: '' + opt.sourceref,
			photoUrl: '' + opt.photoUrl,
	   		src_w: opt.src_w,
	   		src_h: opt.src_h,
	   		src_x: opt.src_x,
	   		src_y: opt.src_y
	   			}, function (response) {
		    		var response = JSON.parse(response);
		    		if (response.success != 1) {
		    			// error
		    			response.data = data;
		    			errorCallback(response);
		    			return;
		    		}
		    		retFunc(response);
 			});
	}
	
};

$.extend($.ceus, {

	queueFaceDiff: function (opt) {
		$.ceus.faceDiffQueue[$.ceus.faceDiffQueue.length] = opt;
	},
	
	sendQueue: function (retFunc) {
		console.log('send queue');
		$.ceus.sendIndex = 0;
		$.ceus.faceDiffCallback = retFunc;
		$.ceus.sendNext();
	},
	
	sendNext: function () {
		if ($.ceus.sendIndex == $.ceus.faceDiffQueue.length) {
			$.ceus.faceDiffQueue = [];
			$.ceus.faceDiffCallback($.ceus.faceDiffResults);
			return;
		}
		console.log('sending ' + $.ceus.sendIndex + ' of ' + $.ceus.faceDiffQueue.length)
		$.ceus.faceDiff($.ceus.faceDiffQueue[$.ceus.sendIndex], function (ret) {
			$.ceus.faceDiffResults[$.ceus.sendIndex] = ret;
			$.ceus.sendIndex++;
			setTimeout(function () {
				$.ceus.sendNext();
			}, 1);
		});
	}

});

With this plugin I was able to quickly create an app whereby, when connected to a visitors FaceBook account:
1) For each friend with a large number of photos
2) Have user select 3 that are certainly the user (API does object detection, returns coords of faces, this was used to put a selectable box around faces)
3) From there, for those friend’s photos (and could do all other friends…) it detects relatively accurately which other photos contain that person’s face and where in the photo (x, y, width, height) that person’s face occurs

Pretty neat stuff, one of the most interesting projects I’ve worked on!

Update on Triton

I’ve started working on my Ticketing and Project Planning system, Triton, again. I found a new Gantt chart control that is a world better (jqGantt) than the one that existed at the time I built Triton. Beyond that I decided to sit it on top of the Redmine schema rather than Mantis. It is not a Redmine fork, mind you (I would never touch Ruby!), the only part it uses is the database schema. It is basically OOTB QCubed (PHP framework) with a tiny application layer.

When I was originally working on it the main feature I wanted to add to a ticketing system was a drag and drop gantt tool. Beyond that I also had ideas that voice commands should be available and heavily integrated, and that a VNC connection between QA staff and developers would be extremely valuable, and also facilitate the recording of video. Beyond those features it also dawned on me as I was working on it that the UI I built was perfect for multi-touch.

Now some of these things are kind-of-sort-of supported by browsers. The gantt chart for sure is a pleasure to use. Voice command support in browsers is coming along. VNC connection is a definate no-no, though there are java plugin VNC viewers. Multi-touch is in its infancy even with desktop apps, browser vendors (or should we call them volunteers since most of them are working on them for free…) haven’t even scratched the surface in adding in multi-touch support.

So the culmination of all these things is that while yes I do want it to be a web app, it would be a lot better if it had a companion desktop app that has those capabilities in only the way a desktop app fully could.

So over the course of a couple of days I have a desktop app that has webkit running in it, and am going to start adding voice streaming to enable voice commands (all the voice recognition processing will occur on the server!) and later find a good multi-touch touch screen to add that in and have something to test it on (more on that to come).

The idea is that a 24-inch all-in-one touch screen system can be had for under $600, and could sit on the desk of QA staff, developers and project managers (etc) next to their usual rig. They would be able to talk to Triton, such as “Triton Open Bugs” to get a list of tasks for the day, and edit the schedule for projects through a Gantt UI using touch with multiple fingers. And if I could say “Triton start recording” and have a capture of what I’m doing within an app taken, that would be nice. And saying, “Triton share my screen with Joe” that would also rock. Those last two are made infinately easier by having a box sitting on the local network that has a standard set of software on it, dedicated to running Triton and maybe some additional multi-touch awesomeness productivity applications.

So without further ado, this is Triton:

Triton as of August 2013

FaceDiff

FaceDiff is a new concept I’m working on applying face recognition technology in a way that is useful and hopefully also beneficial to society. The idea is people are walking around in crowded public places all the time and constantly there are faces in their view, such as friends or celebrities, that they would like to know are nearby. FaceDiff will take an image from a video camera connected to their smart phone, break it into faces, and “diff” it against a library of friends and other people of interest they’ve gathered.

Not only that, all the technology that empowers the smartphone app and server software will be available to software developers in API form. That will allow face detection and face matching without any of the tricky code to write, the server to set up, or the infrastructure to support. A few simple calls can put together a library of people and then match a photo of another person, group of people in a crowd, live video – the possibilities are endless.

As a bonus, users of the app will be able to opt-in to have their crowd images scoured for matches of images of missing children. If enough people use the service, it could end up solving a case (hopefully many). Depending on how well that turns out working, I’m hoping to include a system for people to donate money that goes into an escrow “bounty” which – if a missing child case is solved due to leads from users of the app – will be rewarded to those user(s). This encourages people to opt-in, increasing the chance of lives being saved.

So far I’ve gotten the face detection and rudimentary face matching working, along with creating an account and adding facebook friends as “persons of interest” which included detecting faces in the photos of facebook users and guessing who is who within the various photos. All of this is accomplished by calling my own API which will be the same one available to developers.

For those interested in the technology I built a PHP extension that wraps C++ code that does the legwork of the system where needed, the PHP handles most of the basic application layer, and an exciting part is that I’ve written a JavaScript library for calling the API.

JQuery Calendar Code

Of all the code floating around the web calendar drawing code is usually messy, hard to maintain, and inflexible. A while back I got permission to open-source calendar drawing code that I wrote. It’s client-side JavaScript code as a JQuery plug-in but I plan to port it to both PHP and C# (the first version used Prototype’s psuedo object oriented framework so porting to classical OO languages will be a cinch).

/*global jQuery: false */
/*jslint undef: true, nomen: true, bitwise: true, regexp: true, strict: true, newcap: true */
"use strict";
(function ($) {

//
// jqCal
//
$.fn.jqCal = function(options) {
	// build main options before element iteration
	var opts = $.extend({}, $.fn.jqCal.defaults, options);

	function optDateEqual(d1, d2) {
		return d1[0] == d2[0] &&
				d1[1] == d2[1] &&
				d1[2] == d2[2];
	}

	function dateHasAppts(date) {
		for (var i=0; i" +
						"
" +
						opts.events[i].desc + "
";
				}
			}
			return apptsHtml;
		case $.fn.jqCal.modes.Print :
			var apptsHtml = "";
			for (var i=0; i" + opts.events[i].desc + "";
				}
			}
			apptsHtml += "
";
			return apptsHtml;
		case $.fn.jqCal.modes.Tiny :
			var appts = 0;
			var toolTipText = "";
			var cssClass = baseClasses;
			for (var i=0; i 1) toolTipText += " | ";
					toolTipText += opts.events[i].desc.replace(/<[^>]*>/g, " ").replace(/'/g, "'");
					cssClass += " TinyAppointment";
				}
			}
			if (appts > 1) cssClass += this.CSS_MultiA;
			return " class='" + cssClass + "' title='" + toolTipText + "' ";
		case $.fn.jqCal.modes.Collective :
			var apptsHtml = "";
			for (var i=0; i" + opts.events[i].desc + "";
				}
			}
			apptsHtml += "
";
			return apptsHtml;
		}
	}


	function dateIsHoliday(date) {
		for (var i=0; i" +
			((o.monthYear) ? "" + 
			o.months[date.getMonth()] + ", " + date.getFullYear() + "" : "")];
		for (var i=0; i<7; i++) {
			calHtml[calHtml.length] = "" + 
				((o.mode !== $.fn.jqCal.modes.Tiny) ? o.days[i] : o.daysTiny[i]) + "";
		}
		calHtml[calHtml.length] = "";
		calHtml[calHtml.length] = "";
		var dayClass, numClass, isHoliday, hasAppts;
		$.fn.jqCal.calDays(date, function(iter) {
			dayClass = iter.during ? o.cssClasses.DayReg : o.cssClasses.DayEmpty;
			dayClass += ((o.mode === $.fn.jqCal.modes.Tiny) ? " " + o.cssClasses.Day + "Tiny" : "");
			dayClass += optDateEqual(currentDate, iter.optDate) ? " today" : "";
			numClass = iter.during ? o.cssClasses.DayRegNum : o.cssClasses.DayEmptyNum;
			hasAppts = dateHasAppts(iter.optDate);
			isHoliday = dateIsHoliday(iter.optDate);
			if (iter.newRow) {
				calHtml[calHtml.length] = "";
			}
			if (o.mode != $.fn.jqCal.modes.Tiny) {
				calHtml[calHtml.length] = 
					"" +
//					((isHoliday) ? "" +
//					"
" : "") +
					iter.dayNum +
//					((isHoliday) ? "
" + this.getHolidays(iter.curDate) + 
//					"
" : "") +
					"" + ((hasAppts) ? getDateAppts(iter.optDate) : " ") + 
					"";
			} else {
				calHtml[calHtml.length] = 
					"" + iter.dayNum + "";
			}
			if (iter.endRow === 6) {
				calHtml[calHtml.length] = "";
			}
		});
		calHtml[calHtml.length] = "";

		$this.html(calHtml.join(""));
	});
};

$.fn.jqCal.modes = {
Tiny : 0,
Big : 1,
Print : 2,
Collective : 3
};

//
// plugin defaults
//
$.fn.jqCal.defaults = {
mode : $.fn.jqCal.modes.Tiny,
events : [],
holidays : [],
monthYear : true,
months : ["January", "February", "March", "April", "May", "June", "July", "August",
	"September", "October", "November", "December"],
monthsTiny : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
	"Sep", "Oct", "Nov", "Dec"],
days : ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"],
daysTiny : ["S", "M", "T", "W", "T", "F", "S"],
cssClasses : {
	CalClass : "CalClass",
	MonthYear : "MonthYear",
	Weekday : "Weekday",
	Day : "CalDay",
	DayEmpty : "DayEmpty",
	DayEmptyNum : "DayEmptyNum",
	Appointment : "Appointment",
	ApptDay : "ApptDay",
	ApptDesc : "ApptDesc",
	MultiA : "MultiAppt",
	DayReg : "DayReg",
	DayRegNum : "DayRegNum",
	Holiday : "Holiday",
	ApptPrint : "ApptPrint",
	Hover : "CalDateHover"
		}
};

$.fn.jqCal.calDays = function(date, callback) {
	var calendarDate = new Date(date.valueOf());
	var calendarDateDay = calendarDate.getDay();
	var curDate = new Date(date.valueOf());
	curDate.setDate(curDate.getDate() - (curDate.getDay() + 1));

	var calendarMonth = calendarDate.getMonth();
	var calendarYear = calendarDate.getFullYear();

	var beforeMonth = curDate.getMonth() !== calendarMonth;
	var duringMonth = curDate.getMonth() === calendarMonth;
	var afterMonth = false;

	var idStrBase = "cal-" + calendarMonth + "-" + calendarYear + "-";
	
	var dayMs = 1000 * 60 * 60 * 24;

	var lastDayMs = new Date(calendarYear, calendarMonth + 1, 1).valueOf() - dayMs;
	var numDays = (new Date(lastDayMs)).getDate();

	var before=0, day=0, after=0;

	function dayNum() {
		if (duringMonth) {
			return day;
		} else {
			return curDate.getDate();
		}
	}

	function month() {
		var ret;
		if (duringMonth) {
			ret = calendarMonth;
		} else if (beforeMonth) {
			ret = calendarMonth - 1;
			if (ret === -1) {
				ret = 11;
			}
		} else {
			ret = calendarMonth + 1;
			if (ret === 12) {
				ret = 0;
			}
		}
		return ret;
	}

	function year() {
		if (duringMonth) {
			return calendarYear;
		} else {
			return curDate.getFullYear();
		}
	}

	function doDay() {
		var showDay = dayNum();
		var weekDay = curDate.getDay();
		callback({
			day : curDate, 
			during : duringMonth,
			before : beforeMonth, 
			after : afterMonth,
			dayNum : showDay,
			idStr : idStrBase + month() + "-" + showDay + "-" + year(),
			newRow : weekDay === 0,
			endRow : weekDay === 6,
			optDate : [year(), month(), showDay]
				});
	}

	while (true) {
		if (beforeMonth) {
			if (before < calendarDateDay) {
				curDate.setDate(curDate.getDate() + 1);
				before++;
				doDay();
				continue;
			} else {
				beforeMonth = false;
				duringMonth = true;
			}
		}
		if (duringMonth) {
			if (day < numDays) {
				curDate.setDate(curDate.getDate() + 1);
				day++;
				doDay();
				continue;
			}
			after = curDate.getDay();
			if (after === 6) {
				return;
			} else {
				duringMonth = false;
				afterMonth = true;
			}
		}
		if (afterMonth) {
			if (after < 6) {
				curDate.setDate(curDate.getDate() + 1);
				after++;
				doDay();
				continue;
			} else {
				return;
			}
		}
	}

};


})(jQuery);

Blueprint CSS Framework

I stumbled on the BluePrint CSS framework, seeing it used in another blog’s template. I had no idea there was such a thing as a CSS framework. So I’ve been playing around with it this morning.

I first wanted to try it out on a very simple page, and the public end of the bbpointofsale.com site is very simple but uses a lot of floats which are replaced in the framework with the concept of a “grid.” It makes things a lot simpler.

There was one tricky part, the content of bbpointofsale.com is centered ala most Google pages. In screen.css there is a container class that you use when you want to use grid layouts. The default width is 950px so it doesn’t center at all. I was only using 18 columns in my grid and the math for width of columns is columns * 40 – 10 = width so I changed it to this:

.container {width:710px;margin:0 auto;}

Viola! Centered content with the magic of grid layouts!

My initial impression is that the Blueprint CSS Framework is well worth learning and using for reduced development time and great simplicity and easier maintenance. Two thumbs up!

Caring Labs

In the process of incorporating Keyring Labs, my company. Also launching a new effort which I thought of during a conversation with someone who works at a non-profit (as I do). Caring Labs is going to be a sister corporation of Keyring Labs, offering pro-bono consulting, products and services to any non-profit needing any of that.

The idea is that the non-profit I work for has more resources than most non-profits, and a very good CTO who has gotten the organization ahead of the game technology-wise in most ways, but even for us some of things we do are a little backwards (only in dealing with technology). There are a lot of non-profits fighting for things of great value to this world that have even less resources than the organization I work for. If there was an organization that could come into a place, look at how they are using technology, and either point them in the right direction or do the actual work to get them operating far more efficiently, well, hopefully enough has been said and you all get the picture. Will update on the progress here often.

dronelasertag.com

I checked the stats for battlerc.com (relaunched as dronelasertag.com) this morning. Turns out I’ve actually created a google search:
battle rc
rc plane laser tag
laser tag rc airplanes
jet laser tag
laser tag plane
rc laser aircraft
battlerc
laser tag airplanes
laser remote control
1 real laser tag
1 laser tag remote control
battlerc island

Nobody was searching for these things before, they’re all directly related to battlerc.com being up. Looks like there is some buzz. It’s about time for a mailing list send!