Categories
Art Art Computing

Emacs For Art Writing

The Emacs text editor has been in active development since the 1970s. You can install it using your GNU/Linux distro’s package manager or via Apple’s Mac OS X software site. Real writers use plain text, and Emacs excels at editing plain text.

Emacs doesn’t use the key commands that have become standard since it was written, rather it uses a more expressive set of keypresses to manipulate text. To find or create a file, you press Control and f then Control and f (C-x C-f). To save a file you press Control and X then Control and W (C-x C-w). Then to quit press C-x C-c .

Emacs contains its own tutorial (which you can access by pressing C-h then just pressing t), and there’s a handy reference sheet included when you install Emacs or downloadable on the net (for example at http://www.dsm.fordham.edu/~agw/emacs-refcard.pdf ).

Emacs is also programmable, using a version of the Lisp programming language, and you can use this to extend and customise Emacs to better fit how you work.

You can turn Emacs into a full-screen text editor – http://www.emacswiki.org/emacs/FullScreen

You can add on-the-fly spell checking – http://www.emacswiki.org/emacs/FlySpell

You can even turn Emacs into a powerful idea capturing, note-taking, task list managing, publishing platform – http://orgmode.org/

Specifically for art writing, I’ve written an Emacs “minor mode” that highlights mistakes such as use of the passive voice, weasel words, art writing cliches (“artbollocks”), and lexical illusions (duplicate words) – https://gitorious.org/robmyers/scripts/blobs/master/artbollocks-mode.el

Learning how to use Emacs is an investment that pays off massively,
turning your computer into a remarkably direct instrument for working
with text.

Categories
Aesthetics Satire

The Destiny Of Virus-Infected Drones

http://churchofcyberpunk.tumblr.com/post/11147180129

Once drones are infected by botnets the criminal gangs who make money by leasing out time on infected machines will have the effect of bringing military drone sorties into the market. As we all know, state monopolies are bad because they prevent the market efficiently allocating resources within society, and armies are the state monopoly on violence. Once drones are subjected to the logic of the market, their correct price will be assigned and their true value revealed.

Expat-funded terrorist groups will be able to bid higher than Guardianista “anti-war-crimes” drives for other people’s money, but in the end they will lose out to spammers. Drones, those avatars of thanatos, will become heralds of eros – flying pornbots broadcasting advertisements for viagra and cialis…

(Via netbehaviour, and with thanks to Dave Miller.)

Categories
Art Computing Art History Art Open Data Projects

Exploring Art Data 21

Now that we have a file of statistical information about the folder of images that we are examining, we can plot this using the images themselves.

First we need to install and load the library we will use to load the images to plot. You may need to install ImageMagick’s libmagick for EBImage to install, it doesn’t seem to like GraphicsMagick.

In Fedora run:

sudo yum install libmagick-devel

In Ubuntu run:

sudo aptitude install libmagick-dev

We can then install and load EBImage as follows:

## source("http://bioconductor.org/biocLite.R")
## biocLite("EBImage")
library("EBImage") 

Next we declare constants to control various aspects of the plot. This includes the size of the image, the graphical properties of the elements that we are plotting, and which elements to plot.
 

## The plot
#inches
plotWidth<-8
plotHeight<-6
plotBorder<-1
innerWidth<-plotWidth - (plotBorder * 2)
innerHeight<-plotHeight - (plotBorder * 2)
plotBackgroundCol<-rgb(0.4, 0.4, 0.4, 1.0)
## Thumbnail images
thumbnailWidth<-0.3
## Lines
lineWidth<-1
lineCol<-rgb(0.8, 0.8, 0.8, 1.0)
## Points
## This is the point scale factor (cex)
pointSize<-2
pointStyle<-19
pointCol<-rgb(0.8, 0.8, 0.8, 1.0)
## Labels
## The label scale factor (cex)
labelSize<-0.25
labelCol<-rgb(1.0, 1.0, 1.0, 1.0)
## Axes
axisLabelX<-""
axisLabelY<-""
axisCol<-rgb(1.0, 1.0, 1.0, 1.0)
## Number of significant digits to round fractional part of each tick value to
axisRoundDigits<-3
## What to draw
shouldDrawImages<-TRUE
shouldDrawPoints<-TRUE
shouldDrawLines<-TRUE
shouldDrawLabels<-TRUE
shouldDrawAxes<-TRUE 

Then we declare variables and functions that will be used to process the data in order to fit its values into the plot in a visually appealing way.

minXValue<-NULL
maxXValue<-NULL
minYValue<-NULL
maxYValue<-NULL
scaleX<-NULL
scaleY<-NULL
## Update the scaling factor for positioning images
updateXYScale<-function(){
rangeX<<-maxXValue - minXValue
scaleX<<-innerWidth / rangeX
rangeY<<-maxYValue - minYValue
scaleY<<-innerHeight / rangeY
}
scaleXValue<-function(x){
plotBorder + ((x - minXValue) * scaleX)
}
scaleYValue<-function(y){
plotBorder + ((y - minYValue) * scaleY)
}
## Set the range of the X and Y axes for positioning images
setMinMaxXYValues<-function(xMin, yMin, xMax, yMax){
minXValue<<-xMin
maxXValue<<-xMax
minYValue<<-yMin
maxYValue<<-yMax
updateXYScale()
}
## Calculate the range of the X and Y axes for positioning images
discoverMinMaxXYValues<-function(xValues, yValues){
xRange<-range(xValues)
yRange<-range(yValues)
## Handle 0..1 or a..b
if(xRange[2] - xRange[1] > 1){
xRange<-c(floor(xRange[1]), ceiling(xRange[2]))
} else {
xRange<-c(floor(xRange[1] * 1000) / 1000, ceiling(xRange[2] * 1000) / 1000)
}
if(yRange[2] - yRange[1] > 1){
yRange<-c(floor(yRange[1]), ceiling(yRange[2]))
} else {
yRange<-c(floor(yRange[1] * 1000) / 1000, ceiling(yRange[2] * 1000) / 1000)
}
## Floor and ceiling the values to round them to the nearest integers
## and make the values on the plot nicer
setMinMaxXYValues(xRange[1], yRange[1], xRange[2], yRange[2])
}
## Left X value for image
## image parameter accepted to give these calls a regular signature
imageXLeft<-function(image, valueX){
valueX
}
## Right X value for image
## image parameter accepted to give these calls a regular signature
imageXRight<-function(image, valueX){
valueX + thumbnailWidth
}
## Get the height of the image scaled to the new width
imageHeightScaled<-function(image, scaledWidth){
scale<-dim(image)[1] / scaledWidth
dim(image)[2] / scale
}
## Bottom Y value for image
imageYBottom<-function(image, valueY){
valueY - imageHeightScaled(image, thumbnailWidth)
}
## Top Y value for image
imageYTop<-function(image, valueY){
valueY
} 

The labels for
each image, the points marking the image’s position, the lines connecting each image, and the top left of each image are positioned on the x, y co-ordinates for the image’s properties being plotted.

Centering the image on the x, y co-ordinates might be more natural but it would obscure the position of the point and the connecting lines if they were also drawn.

plotLabels<-function(labelValues, xValues, yValues){
## Position the labels underneath the images
text(xValues, yValues, labelValues, col=labelCol, cex=labelSize, pos=3)
}

plotImages<-function(imageFilePaths, xValues, yValues){
for(i in 1:length(imageFilePaths)){
image<-readImage(imageFilePaths[i])
x<-xValues[i]
y<-yValues[i]
## Does the image really have to be rotated???
rasterImage(rotate(image), imageXLeft(image, x), imageYTop(image, y),
imageXRight(image, x), imageYBottom(image, y))
}
} 

When we plot the axes their tick values are auto-generated from the value ranges, so they may look weird.
 

plotAxes<-function(){
xat<-round(seq(minXValue, maxXValue,
(maxXValue - minXValue) / plotWidth),
axisRoundDigits)
axis(1, 0:plotWidth, xat, col=axisCol, col.ticks=axisCol, col.axis=axisCol)
yat<-round(seq(minYValue, maxYValue,
(maxYValue - minYValue) / plotHeight),
axisRoundDigits)
axis(2, 0:plotHeight, yat, col=axisCol, col.ticks=axisCol, col.axis=axisCol)
} 

Having written functions to plot each element, we declare an all-in-one function to plot everything that is enabled in the configuration constants above.
 

plotElements<-function(imageFilePaths, xValues, yValues, labelValues){
if(shouldDrawLines){
lines(xValues, yValues, col=lineCol, lwd=lineWidth)
}
if(shouldDrawPoints){
points(xValues, yValues, pch=pointStyle, col=pointCol)
}
if(shouldDrawImages){
plotImages(imageFilePaths, xValues, yValues)
}
if(shouldDrawLabels){
plotLabels(labelValues, xValues, yValues)
}
if(shouldDrawAxes){
plotAxes()
}
}


Then we declare a function to get the values from the data frame and call the plot-everything function.
 

setValuesAndPlot<-function(data, imageFilepaths, xColumn, yColumn,
labelColumn="filename", discoverRange=TRUE){
## Get the lists for the data columns, get the doubles from them,
## and scale to the plot
xValues<-data[xColumn][,1]
yValues<-data[yColumn][,1]
if(discoverRange){
discoverMinMaxXYValues(xValues, yValues)
}
scaledXValues<-sapply(xValues, scaleXValue)
scaledYValues<-sapply(yValues, scaleYValue)
axisLabelX<<-xColumn
axisLabelY<<-yColumn
plotElements(imageFilepaths, scaledXValues, scaledYValues, data[,labelColumn])
title(xlab=xColumn, ylab=yColumn, col.lab=axisCol)
} 

You’ll notice each function is combining and building on earlier functions. Functions should be short, readable, organizing units. The next one that we declare reads the data file and the image files, and then plots the values.


readAndPlot<-function(dataFile, imageFolder, xColumn, yColumn, labelColumn="filename", discoverRange=TRUE){ data<-read.delim(dataFile, stringsAsFactors=FALSE) imageFilepaths<-sapply(data["filename"], function(filename) file.path(imageFolder, filename)) setValuesAndPlot(data, imageFilepaths, xColumn, yColumn, labelColumn, discoverRange) }


The next function makes a new R plot with the proper graphics parameters
Notably this sets the bounds and background colour.
 

newPlot<-function(dataFile, imageFolder, xColumn, yColumn,
labelColumn="filename", discoverRange=TRUE){
## Call before plot.new()
par(bg=plotBackgroundCol)
plot.new()
## Use co-ordinates relative to the bounds
par(usr=c(0, plotWidth, 0, plotHeight))
par(bty="n")
readAndPlot(dataFile, imageFolder, xColumn, yColumn, labelColumn,
discoverRange)
}


Finally we can declare functions to plot to various different kinds of R devices. X11 for screen display and testing, PNG for embedding in web pages and documents, and PDF for high-quality output. Note that the PDF will include all the images plotted, and so it will become very large very quickly. A high-resolution PNG will be more practical for very large imagesets.
 

## Make a new X11 plot
X11Plot<-function(dataFile, imageFolder, xColumn, yColumn,
labelColumn="filename", discoverRange=TRUE){
X11(width=plotWidth, height=plotHeight)
newPlot(dataFile, imageFolder, xColumn, yColumn, labelColumn, discoverRange)
}
## Make a new PNG plot
pngPlot<-function(outFile, dataFile, imageFolder, xColumn, yColumn,
labelColumn="filename", discoverRange=TRUE, dpi=600){
png(filename=outFile, width=plotWidth, height=plotHeight, units="in",
res=dpi)
newPlot(dataFile, imageFolder, xColumn, yColumn, labelColumn, discoverRange)
dev.off()
}
## Make a new PDF plot
pdfPlot<-function(outFile, dataFile, imageFolder, xColumn, yColumn,
labelColumn="filename", discoverRange=TRUE){
pdf(file=outFile, width=plotWidth, height=plotHeight)
newPlot(dataFile, imageFolder, xColumn, yColumn, labelColumn, discoverRange)
dev.off()
} 

Calling these image generating commands from the REPL in Emacs or on the command line means that we can see the output and modify the constants we declared at the start and the parameters that we pass to the image plotting functions in order to modify and improve the results interactively.

Running:
 

X11Plot("images.txt", "images", "brightness_median", "saturation_stdev") 

Gives us:

Mondrian VisualizationNext we can wrap the functions we have written in command-line and GUI interfaces and explore the strengths and weaknesses of each.

Categories
Art Free Culture Projects

Balloon Dog

My 3D printing art project “Balloon Dog” is now available as part of Collaboration and Freedom – The World of Free and Open Source Art. Furtherfield commissioned it as a sequel to Urinal.

balloon_dog2.pngThe model was created by Bassam Kurdali, and it’s available under a Creative Commons Attrubution-ShareAlike 3.0 unported licence (as with Urinal’s modeller Chris Webber, Bassam holds the copyright on the model). 

I submitted it to Boing Boing and Thingiverse, where there have been some interesting comments, and is already being used for purposes other than 3D printing, which is great to see.
Categories
Aesthetics Art Free Software Projects Satire

artbollocks-mode.el

I turned the scripts I use for avoiding various cardinal sins of art
writing into an Emacs minor mode. This means that you can run it in your Emacs session as you write.

What do you mean you don’t use Emacs? Don’t be silly. 😉

https://gitorious.org/robmyers/scripts/blobs/master/artbollocks-mode.el

Categories
Art Computing Art Open Data Projects

Exploring Art Data 20

[Exploring Art Data 18 and 19 concern parsing and charting the Graves Art Sales data covering Constable. They will be published later.]

Let’s reproduce the functionality of ImagePlot in R . We’ll do this in several stages. In this post we’ll write code to produce statistical information about collections of image files. In the next post we’ll write code to visualize that information. Then we’ll write command line and graphical user interface code for the visualization. Finally we’ll use the command line code to look at how to perform image analysis distributed over the network.

For the statistical analysis code (image-properties.r), first we’ll need to install and load some libraries:

## source("http://bioconductor.org/biocLite.R")
## biocLite("EBImage")
## You may need to install libmagick for EBImage
library("EBImage")
##install.packages("colorspace")
library("colorspace")

Then we will need to write code to convert from the computer-friendly RGB colourspace to the human-friendly HSB colourspace.


## Get the r,g,b colour values for all the pixels in the image as a list
imageRgbs<-function(bitmap){
## Get flat lists of red, green and blue pixel values
red<-imageData(channel(bitmap, "red"))
dim(red)<-NULL
green<-imageData(channel(bitmap, "green"))
dim(green)<-NULL
blue<-imageData(channel(bitmap, "blue"))
dim(blue)<-NULL
## Combine these lists into a table of pixel r,g,b values
data.frame(red=red, green=green,blue=blue)
}
## Convert the RGB data.frame to an RGB objects collection
rgbToHsv<-function(rgbs){
as(RGB(rgbs$red, rgbs$green, rgbs$blue), "HSV")
}

Next we write the code to produce the statistics for each image. R makes this very easy.


## Calculate the median values for the HSV coordinates
## The colour returned is not a colour in the image,
## it just contains the median values
medianHsv<-function(hsvcoords){
HSV(median(hsvcoords[,"H"]), median(hsvcoords[,"S"]), median(hsvcoords[,"V"]))
}
## Calculate the minimum and maximum values for the HSV coordinates
## Returns a vector of colours, the first containing low values,
## the second containing high values
## These are not colours that appear in the image, they just contain the values
rangeHsv<-function(hsvcoords){
hrange<-range(hsvcoords[,"H"])
srange<-range(hsvcoords[,"S"])
vrange<-range(hsvcoords[,"V"])
c(min=HSV(hrange[1], srange[1], vrange[1]),
max=HSV(hrange[2], srange[2], vrange[2]))
}
## Calculate the standard deviation for the HSV coordinates
## The colour returned is not a colour in the image,
## it just contains the sd for each value
sdHsv<-function(hsvcoords){
hsd<-sd(hsvcoords[,"H"])
ssd<-sd(hsvcoords[,"S"])
vsd<-sd(hsvcoords[,"V"])
HSV(hsd[1], ssd[1], vsd[1])
}
## A good way of getting the min, max, median and other useful values
summaryHsv<-function(hsvcoords){
list(H=summary(hsvcoords[,"H"]),
S=summary(hsvcoords[,"S"]),
V=summary(hsvcoords[,"V"]))
}

Now we write the code to output those statistics. This is slightly more complex than the simplest possible way of structuring the code would be in order to make the code more robust if we need to change it later.


## HSV == HSB
## We use brightness for compatibility
## Some columns and column names are also for compatibility
## Load the file and return a vector of named interesting statistics
fileRow<-function(filename){
cat(filename, sep="\n")
img<-readImage(filename)
rgbs<-imageRgbs(img)
hsvs<-rgbToHsv(rgbs)
hsvcoords<-coords(hsvs)
summaryhsv<-summaryHsv(hsvcoords)
sdhsv<-sdHsv(hsvcoords)
sdhsvcoords<-coords(sdhsv)
## NaN for year for now
c("year"=NaN,
## Get the values manually so that we don't rely on position in case
## that ever changes
"hue_min"=summaryhsv$H[["Min."]],
"hue_1st_qu"=summaryhsv$H[["1st Qu."]],
"hue_median"=summaryhsv$H[["Median"]],
"hue_mean"=summaryhsv$H[["Mean"]],
"hue_3rd_qu"=summaryhsv$H[["3rd Qu."]],
"hue_max"=summaryhsv$H[["Max."]],
"hue_stdev"=sdhsvcoords[,"H"][[1]], ## There must be a better way than this
"saturation_min"=summaryhsv$S[["Min."]],
"saturation_1st_qu"=summaryhsv$S[["1st Qu."]],
"saturation_median"=summaryhsv$S[["Median"]],
"saturation_mean"=summaryhsv$S[["Mean"]],
"saturation_3rd_qu"=summaryhsv$S[["3rd Qu."]],
"saturation_max"=summaryhsv$S[["Max."]],
"saturation_stdev"=sdhsvcoords[,"S"][[1]],
"brightness_min"=summaryhsv$V[["Min."]],
"brightness_1st_qu"=summaryhsv$V[["1st Qu."]],
"brightness_median"=summaryhsv$V[["Median"]],
"brightness_mean"=summaryhsv$V[["Mean"]],
"brightness_3rd_qu"=summaryhsv$V[["3rd Qu."]],
"brightness_max"=summaryhsv$V[["Max."]],
"brightness_stdev"=sdhsvcoords[,"V"][[1]])
}
## Create a frame containing interesting information about the images
filesSummaries<-function(filenames, folder){
cat("Processing: ")
cat(filenames, sep=", ")
filepaths<-sapply(filenames,
function(filename) file.path(folder, filename))
## data.frame columns can be different types, so we add the filenames here
## We don't have the strings as factors as if we paste() them as factors
## they are pasted as numbers (levels)
data.frame(filename=filenames,
imageID=1:length(filenames),
t(sapply(filepaths, fileRow)), stringsAsFactors=FALSE)
}
## Print the fileDetails frame to a tab-separated-values file
## This can easily be loaded back into R
printFilesSummaries<-function(fileDetails, outfile=""){
## Build an array of values for the file images,
## and make it a frame with the filenames as a column
cat(paste(names(fileDetails), collapse="\t"), file=outfile)
cat("\n", file=outfile)
for(row in 1:dim(fileDetails)[1]){
cat(paste(fileDetails[row,], collapse="\t"), file=outfile)
cat("\n", file=outfile)
}
cat("Done.", sep="\n")
}

This code can be run from an interactive R session. For scripting and distribution it can be convenient to have a command-line interface to the code. So in another file (imgstats) we write a simple command-line interface to the code.
First of all we load the image statistics code


source("image-properties.r")

Then we parse the command line arguments and make sure that the user has provided reasonable values to the script, quitting with an advisory message if they have not.


################################################################################
## Parse the command line
################################################################################
args<-commandArgs(TRUE)
if(length(args) != 1){
stop(paste("usage: imgstats [foldername]"))
}
folder<-args[1]
if(folder == "."){
stop("Please pass the name of the folder to process, not '.' .")
}

Next we call the code from image-properties.r to process each of the files in the folder the user named as an argument to the script.


################################################################################
## Process the files
################################################################################
## Add more formats to taste. We need to only load image files though
files<-list.files(folder, pattern="*.(jpg|jpeg|tif|tiff|png|gif|bmp)")
stats<-filesSummaries(files, folder)

Finally we call the code from image-properties.r to save the data to a file (named after the folder that has been processed by the script).


################################################################################
## Write the data
################################################################################
outfile<-file(paste(folder, ".txt", sep=""), "w")
printFilesSummaries(stats, outfile)
close(outfile)

To run the code from the command line we have to set its execute file permission:


chmod +x imgstat

And now we can generate statistical data about a folder of images ready to visualize.

Here’s an example of the output from the script:

filename	imageID	year	hue_min	hue_1st_qu	hue_median	hue_mean	hue_3rd_qu	hue_max	hue_stdev	saturation_min	saturation_1st_qu	saturation_median	saturation_mean	saturation_3rd_qu	saturation_max	saturation_stdev	brightness_min	brightness_1st_qu	brightness_median	brightness_mean	brightness_3rd_qu	brightness_max	brightness_stdev
1905.5_a_mondrian.jpg	1	NaN	0	51.43	55.29	60.23	65.71	330	16.5393230900438	0	0.232	0.2925	0.3066	0.3537	1	0.123994722897117	0.01176	0.2863	0.4549	0.4818	0.702	1	0.246702823821300
1905.5_b_mondrian.jpg	2	NaN	0	36	40.47	94.86	192	360	90.020405312462	0	0.1333	0.2155	0.1979	0.2581	0.7143	0.0935518755834357	0.04314	0.2	0.6118	0.5523	0.8549	0.9725	0.312092338505255
1905.5_c_mondrian.jpg	3	NaN	0	41.54	264	200.3	330	360	139.634180648185	0	0.141	0.2	0.2078	0.2644	1	0.0978851449446736	0.03137	0.08235	0.1608	0.2296	0.2667	1	0.216606346291597
1905.5_d_mondrian.jpg	4	NaN	0	32	42	76.1	67.5	360	90.4207404627883	0	0.2308	0.306	0.3256	0.4	1	0.139508165859475	0.01569	0.1176	0.2627	0.3912	0.698	1	0.296420604122761
1905.5_e_mondrian.jpg	5	NaN	0	72	124.3	135.3	217.2	345	65.5574296333988	0	0.1646	0.25	0.2706	0.3925	1	0.144880395319209	0.01569	0.2	0.3176	0.4501	0.8314	1	0.304302318390967
1905.5_f_mondrian.jpg	6	NaN	0	48	56.84	85.65	70	360	74.8812889958008	0	0.05	0.1452	0.1937	0.3438	0.8333	0.153920183168041	0.01569	0.2863	0.4353	0.4846	0.7059	0.9725	0.242587145239624
1905.5_g_mondrian.jpg	7	NaN	0	38.46	43.08	42.42	46.32	100	5.77930853730472	0	0.324	0.4706	0.4646	0.5972	1	0.164745255193285	0.01176	0.4196	0.5922	0.6044	0.8588	1	0.243507248302950
1905.5_h_mondrian.jpg	8	NaN	0	40	47.14	54.34	60	360	25.19346977218	0	0.07547	0.1946	0.2287	0.3524	1	0.174754958215903	0.003922	0.2902	0.5882	0.5644	0.8157	0.9961	0.300666257269201