Skip to content

Instantly share code, notes, and snippets.

@radaniba
Created March 22, 2013 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save radaniba/5221583 to your computer and use it in GitHub Desktop.
Save radaniba/5221583 to your computer and use it in GitHub Desktop.
For when you have to label the tips of a phylogeny in a systematic way. Rather than "point and click" within Dendroscope, this script takes a .den/dendro file and colors the tips according to a "color description" file. This is a simple csv file with taxa labels and a corresponding color. The color may either be an RGB triplet or a scalar value …
#!/usr/bin/env ruby
# Color the node labels in a Dendroscope tree.
### IMPORTS
require 'test/unit/assertions'
require 'optparse'
require 'pp'
require 'csv'
require 'ostruct'
require 'date'
require 'time'
include Test::Unit::Assertions
### CONSTANTS & DEFINES
### IMPLEMENTATION
def interpolate(str, sub_hash)
return str.gsub(/\{([^}]+)\}/) { |m|
sub_hash[$1]
}
end
### MAIN
class ColorMapper
attr_accessor(:min, :max, :range)
def initialize(vals)
@max, @min = vals.max.to_f(), vals.min.to_f()
@range = @max - @min
end
def map(val)
norm_val = normalise_val(val)
val255 = (norm_val * 255).round()
clrs = [val255, 255 - val255, val255].each { |v|
v.to_i.to_s
}
return clrs.join(' ')
end
def normalise_val(val)
# get a val from 0 to 1
return (val - @min) / @range
end
end
# Return a mapping between label names and colors
#
def parse_clr_file(clr_file, map_to_clr)
clr_map = {}
# read file
CSV::Reader.parse(File.open(clr_file, 'rb')) { |row|
#assert_equal(2, row.length, "row length should be 2")
label, color = row[0].strip(), row[1].strip()
if map_to_clr == false
assert_equal(3, color.split(' ').length(),
"color '#{color}' looks malformed")
end
clr_map[label] = color
}
# map to colors if need be
if (map_to_clr)
clr_map.each_pair { |k,v|
clr_map[k] = v.to_f()
}
mapper = ColorMapper.new(clr_map.values)
clr_map.each_pair { |k,v|
clr_map[k] = mapper.map(v)
}
end
return clr_map
end
# Parse commandline arguments.
#
def parse_clargs(arg_arr)
clopts = {
:save => "{root}-colored.{ext}",
:overwrite => false,
:def_clr => "0 0 0",
:map_to_clr => false,
}
OptionParser.new { |opts|
opts.program_name = __FILE__
opts.banner = "Color the nodes in a dendroscope file"
opts.separator("")
opts.separator("The input is a CSV file of coloring instructions and one")
opts.separator("or more Dendroscope tree files. The coloring instructions")
opts.separator("consist of one row for each node to be labelled,")
opts.separator("consisting of:")
opts.separator("")
opts.separator(" <label>, <fgcolor>")
opts.separator("")
opts.separator("where the color is a RGB 256 triplet (e.g. '0 128 255').")
opts.separator("")
opts.separator("Usage: #{opts.program_name} [options] CLRFILE TREEFILE1 ...]")
opts.on('-h', '--help', 'Display this screen') {
puts opts
exit
}
opts.on('-m', '--default-color STR',
"The default color nodes will be given") { |v|
clopts[:def_clr] = v
}
opts.on('-m', '--map-to-colors',
"The coloring instructions give a float value which will be mapped to a color") {
clopts[:map_to_clr] = true
}
opts.on('', '--save STR',
"Name output files according this template") { |v|
clopts[:save] = v
}
opts.on('-o', '--overwrite',
"Overwrite pre-existing files") {
clopts[:overwrite] = true
}
begin
opts.parse!(arg_arr)
rescue OptionParser::InvalidOption => e
puts e
puts opts
exit 1
end
}
pargs = arg_arr
assert(2 <= pargs.length, "need files to work on")
return clopts, pargs[0], pargs[1, pargs.length]
end
# Main script functionality.
#
def main()
clopts, clr_file, tree_files = parse_clargs(ARGV)
clr_table = parse_clr_file(clr_file, clopts[:map_to_clr])
tree_files.each { |f|
# slurp ...
puts "== Reading '#{f}' ..."
converted_lines = []
tree_data = File.open(f, 'rb').read()
# give each node a color, whether it wants it or not
tree_data.gsub!(Regexp.new('^(\d+: .*) lb=(.+)$')) { |m|
match_str = m.to_s()
if (match_str.include?(" lc="))
# color already defined
match_str
else
"#{$1} lc=#{clopts[:def_clr]} lb=#{$2}"
end
}
# add color labels
clr_table.each_pair { |label, color|
pp color
tree_data.gsub!(
Regexp.new("lc=(.*) (lb='#{Regexp.escape(label)}')"),
"lc=#{color} " + '\2'
)
}
# write output
# make filename
ext = File.extname(f)
subs = {
"ext" => ext[1, ext.length],
"base" => File.basename(f),
"root" => File.basename(f, ext),
"date" => Date.today.to_s(),
"time" => Time.now.strftime(fmt='%T'),
"datetime" => DateTime.now.strftime(fmt='%F T%T'),
}
out_name = interpolate(clopts[:save], subs)
puts "Saving drawing to '#{out_name}' ..."
# do the writing
if File.exists?(out_name)
assert(clopts[:overwrite], "Can't overwrite existing file '#{out_name}'")
end
File.open(out_name, 'w').write(tree_data)
#drwr.save(out_name)
puts "Saved."
}
puts "== Finished."
end
if $0 == __FILE__
main()
end
### END
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment