iroki 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/COPYING +674 -0
- data/Gemfile +4 -0
- data/Guardfile +70 -0
- data/README.md +43 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/iroki +86 -0
- data/iroki.gemspec +34 -0
- data/lib/iroki.rb +42 -0
- data/lib/iroki/color/color.rb +58 -0
- data/lib/iroki/const/const.rb +115 -0
- data/lib/iroki/core_ext/file/file.rb +64 -0
- data/lib/iroki/core_ext/hash/hash.rb +28 -0
- data/lib/iroki/core_ext/string/string.rb +34 -0
- data/lib/iroki/main/main.rb +232 -0
- data/lib/iroki/utils/utils.rb +103 -0
- data/lib/iroki/version.rb +32 -0
- metadata +207 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright 2016 Ryan Moore
|
2
|
+
# Contact: moorer@udel.edu
|
3
|
+
#
|
4
|
+
# This file is part of Iroki.
|
5
|
+
#
|
6
|
+
# Iroki is free software: you can redistribute it and/or modify it
|
7
|
+
# under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Iroki is distributed in the hope that it will be useful, but
|
12
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14
|
+
# General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Iroki. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
module Iroki
|
20
|
+
module CoreExt
|
21
|
+
module File
|
22
|
+
def check_file arg, which
|
23
|
+
help = " Try iroki --help for help."
|
24
|
+
|
25
|
+
abort_if arg.nil?,
|
26
|
+
"You must provide a #{which} file.#{help}"
|
27
|
+
|
28
|
+
abort_unless Object::File.exists?(arg),
|
29
|
+
"The file #{arg} doesn't exist.#{help}"
|
30
|
+
|
31
|
+
arg
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_name_map fname
|
35
|
+
check_file fname, :name_map
|
36
|
+
|
37
|
+
name_map = {}
|
38
|
+
Object::File.open(fname).each_line do |line|
|
39
|
+
oldname, newname = line.chomp.split "\t"
|
40
|
+
|
41
|
+
|
42
|
+
abort_if oldname.nil? || oldname.empty?,
|
43
|
+
"Column 1 missing for line: #{line.inspect}"
|
44
|
+
|
45
|
+
abort_if newname.nil? || newname.empty?,
|
46
|
+
"Column 2 missing for line: #{line.inspect}"
|
47
|
+
|
48
|
+
oldname = clean oldname
|
49
|
+
newname = clean newname
|
50
|
+
|
51
|
+
abort_if name_map.has_key?(oldname),
|
52
|
+
"#{oldname} is repeated in column 1"
|
53
|
+
|
54
|
+
name_map[oldname] = newname
|
55
|
+
end
|
56
|
+
|
57
|
+
abort_if duplicate_values?(name_map),
|
58
|
+
"Names in column 2 of name map file must be unique"
|
59
|
+
|
60
|
+
name_map
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Copyright 2016 Ryan Moore
|
2
|
+
# Contact: moorer@udel.edu
|
3
|
+
#
|
4
|
+
# This file is part of Iroki.
|
5
|
+
#
|
6
|
+
# Iroki is free software: you can redistribute it and/or modify it
|
7
|
+
# under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Iroki is distributed in the hope that it will be useful, but
|
12
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14
|
+
# General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Iroki. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
module Iroki
|
20
|
+
module CoreExt
|
21
|
+
module Hash
|
22
|
+
def duplicate_values? hash
|
23
|
+
values = hash.values
|
24
|
+
values.count != 1 && values.count != values.uniq.count
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Iroki
|
2
|
+
module CoreExt
|
3
|
+
module String
|
4
|
+
|
5
|
+
def hex? str
|
6
|
+
str.match(/^#[0-9A-Fa-f]{6}$/)
|
7
|
+
end
|
8
|
+
|
9
|
+
def clean str
|
10
|
+
str.gsub(/[^\p{Alnum}_]+/, "_").gsub(/_+/, "_")
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_color? name
|
14
|
+
name.match(/(.*)(\[&!color="#[0-9A-Fa-f]{6}"\])/)
|
15
|
+
end
|
16
|
+
alias already_checked? has_color?
|
17
|
+
|
18
|
+
def clean_name name
|
19
|
+
if name.nil?
|
20
|
+
nil
|
21
|
+
else
|
22
|
+
if (match = has_color? name)
|
23
|
+
name = match[1]
|
24
|
+
color = match[2]
|
25
|
+
|
26
|
+
clean(name) + color
|
27
|
+
else
|
28
|
+
clean(name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
# Copyright 2016 Ryan Moore
|
2
|
+
# Contact: moorer@udel.edu
|
3
|
+
#
|
4
|
+
# This file is part of Iroki.
|
5
|
+
#
|
6
|
+
# Iroki is free software: you can redistribute it and/or modify it
|
7
|
+
# under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Iroki is distributed in the hope that it will be useful, but
|
12
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14
|
+
# General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Iroki. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require "bio"
|
20
|
+
require "set"
|
21
|
+
|
22
|
+
module Iroki
|
23
|
+
module Main
|
24
|
+
def self.main(color_branches: nil,
|
25
|
+
color_taxa_names: nil,
|
26
|
+
exact: nil,
|
27
|
+
remove_bootstraps_below: nil,
|
28
|
+
color_map_f: nil,
|
29
|
+
name_map_f: nil,
|
30
|
+
auto_color: nil,
|
31
|
+
display_auto_color_options: nil,
|
32
|
+
newick_f: nil,
|
33
|
+
out_f: nil)
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
if display_auto_color_options
|
38
|
+
puts "\n Choices for --auto-color ..."
|
39
|
+
print " basic, basic_light, basic_dark, funky, funky_light, " +
|
40
|
+
"funky_dark\n\n"
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
auto_color_options =
|
45
|
+
["basic", "basic_light", "basic_dark",
|
46
|
+
"funky", "funky_light", "funky_dark",]
|
47
|
+
|
48
|
+
|
49
|
+
if(!auto_color.nil? &&
|
50
|
+
!auto_color_options.include?(auto_color))
|
51
|
+
puts "\n Choices for --auto-color ..."
|
52
|
+
print " basic, basic_light, basic_dark, funky, funky_light, " +
|
53
|
+
"funky_dark\n\n"
|
54
|
+
|
55
|
+
Trollop.die :auto_color, "#{auto_color} is not a valid option"
|
56
|
+
end
|
57
|
+
|
58
|
+
case auto_color
|
59
|
+
when nil
|
60
|
+
auto_colors = BASIC
|
61
|
+
when "basic"
|
62
|
+
auto_colors = BASIC
|
63
|
+
when "basic_light"
|
64
|
+
auto_colors = BASIC_LIGHT
|
65
|
+
when "basic_dark"
|
66
|
+
auto_colors = BASIC_DARK
|
67
|
+
when "funky"
|
68
|
+
auto_colors = FUNKY
|
69
|
+
when "funky_light"
|
70
|
+
auto_colors = FUNKY_LIGHT
|
71
|
+
when "funky_dark"
|
72
|
+
auto_colors = FUNKY_DARK
|
73
|
+
end
|
74
|
+
|
75
|
+
# color_branches = true
|
76
|
+
# color_taxa_names = true
|
77
|
+
# exact = false
|
78
|
+
# color_map_f = "test_files/500.patterns_with_name_map"
|
79
|
+
# name_map_f = "test_files/500.name_map"
|
80
|
+
# ARGV[0] = "test_files/500.zetas.tre"
|
81
|
+
newick = check_file newick_f, :newick
|
82
|
+
|
83
|
+
color_f = nil
|
84
|
+
if color_taxa_names || color_branches
|
85
|
+
color_f = check_file color_map_f, :color_map_f
|
86
|
+
end
|
87
|
+
|
88
|
+
check = color_map_f &&
|
89
|
+
!color_taxa_names &&
|
90
|
+
!color_branches
|
91
|
+
|
92
|
+
abort_if check,
|
93
|
+
"A pattern file was provided without specifying " +
|
94
|
+
"any coloring options"
|
95
|
+
|
96
|
+
|
97
|
+
# if passed color other than one defined, return black
|
98
|
+
black = "#000000"
|
99
|
+
red = "#FF1300"
|
100
|
+
yellow = "#FFD700"
|
101
|
+
blue = "#5311FF"
|
102
|
+
green = "#00FF2C"
|
103
|
+
color2hex = Hash.new "[&!color=\"#{black}\"]"
|
104
|
+
color2hex.merge!({
|
105
|
+
"black" => "[&!color=\"#{black}\"]",
|
106
|
+
"red" => "[&!color=\"#{red}\"]",
|
107
|
+
"blue" => "[&!color=\"#{blue}\"]",
|
108
|
+
"yellow" => "[&!color=\"#{yellow}\"]",
|
109
|
+
"green" => "[&!color=\"#{green}\"]"
|
110
|
+
})
|
111
|
+
|
112
|
+
# check if complementary colors requested
|
113
|
+
if color_f
|
114
|
+
colors = Set.new
|
115
|
+
File.open(color_f).each_line do |line|
|
116
|
+
_, color = line.chomp.split "\t"
|
117
|
+
|
118
|
+
colors << color
|
119
|
+
end
|
120
|
+
|
121
|
+
auto_color = colors.all? { |color| color.match /\A[0-4]\Z/ }
|
122
|
+
end
|
123
|
+
|
124
|
+
# get the color patterns
|
125
|
+
if color_f
|
126
|
+
patterns = {}
|
127
|
+
File.open(color_f).each_line do |line|
|
128
|
+
pattern, color = line.chomp.split "\t"
|
129
|
+
|
130
|
+
color = "black" if color.nil? || color.empty?
|
131
|
+
|
132
|
+
if name_map_f || color_taxa_names || color_branches
|
133
|
+
pattern = clean_name pattern
|
134
|
+
end
|
135
|
+
|
136
|
+
if !exact
|
137
|
+
pattern = Regexp.new pattern
|
138
|
+
end
|
139
|
+
|
140
|
+
if auto_color
|
141
|
+
patterns[pattern] = "[&!color=\"#{auto_colors[color]}\"]"
|
142
|
+
else
|
143
|
+
if hex? color
|
144
|
+
patterns[pattern] = "[&!color=\"#{color}\"]"
|
145
|
+
else
|
146
|
+
patterns[pattern] = color2hex[color]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
treeio = Bio::FlatFile.open(Bio::Newick, newick)
|
153
|
+
|
154
|
+
newick = treeio.next_entry
|
155
|
+
tree = newick.tree
|
156
|
+
|
157
|
+
# do this first cos everything after will use the "new" names
|
158
|
+
if name_map_f
|
159
|
+
name_map = parse_name_map name_map_f
|
160
|
+
|
161
|
+
tree.collect_node! do |node|
|
162
|
+
unless node.name.nil?
|
163
|
+
# every name is cleaned no matter what
|
164
|
+
node.name = clean node.name
|
165
|
+
|
166
|
+
if name_map.has_key?(node.name)
|
167
|
+
node.name = name_map[node.name]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
node
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
if color_taxa_names
|
176
|
+
leaves = tree.leaves.map do |n|
|
177
|
+
name = clean_name n.name
|
178
|
+
|
179
|
+
if (color = add_color_to_leaf_branch(patterns, name, exact))
|
180
|
+
name + color
|
181
|
+
else
|
182
|
+
name
|
183
|
+
end
|
184
|
+
end
|
185
|
+
else
|
186
|
+
leaves = tree.leaves.map { |n| clean_name n.name }
|
187
|
+
end
|
188
|
+
|
189
|
+
if color_branches
|
190
|
+
total = tree.nodes.count
|
191
|
+
n = 0
|
192
|
+
tree.collect_node! do |node|
|
193
|
+
n += 1
|
194
|
+
$stderr.printf "Node: %d of %d\r", n, total
|
195
|
+
|
196
|
+
color_nodes patterns, tree, node, exact
|
197
|
+
end
|
198
|
+
end
|
199
|
+
$stderr.puts
|
200
|
+
|
201
|
+
if remove_bootstraps_below
|
202
|
+
tree.collect_node! do |node|
|
203
|
+
if node.bootstrap && node.bootstrap < remove_bootstraps_below
|
204
|
+
node.bootstrap_string = ""
|
205
|
+
end
|
206
|
+
|
207
|
+
node
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
|
213
|
+
tre_str = tree.newick(indent: false).gsub(/'/, '')
|
214
|
+
|
215
|
+
nexus = "#NEXUS
|
216
|
+
begin taxa;
|
217
|
+
dimensions ntax=#{leaves.count};
|
218
|
+
taxlabels
|
219
|
+
#{leaves.join("\n")}
|
220
|
+
;
|
221
|
+
end;
|
222
|
+
|
223
|
+
begin trees;
|
224
|
+
tree tree_1 = [&R] #{tre_str}
|
225
|
+
end;
|
226
|
+
|
227
|
+
#{FIG}"
|
228
|
+
|
229
|
+
File.open(out_f, "w") { |f| f.puts nexus }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# Copyright 2016 Ryan Moore
|
2
|
+
# Contact: moorer@udel.edu
|
3
|
+
#
|
4
|
+
# This file is part of Iroki.
|
5
|
+
#
|
6
|
+
# Iroki is free software: you can redistribute it and/or modify it
|
7
|
+
# under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Iroki is distributed in the hope that it will be useful, but
|
12
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14
|
+
# General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Iroki. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
module Iroki
|
20
|
+
module Utils
|
21
|
+
include AbortIf
|
22
|
+
include Iroki::CoreExt::String
|
23
|
+
|
24
|
+
def leaf? tree, node
|
25
|
+
tree.children(node).empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_color_to_leaf_branch patterns, node, exact
|
29
|
+
num_matches = 0
|
30
|
+
color = nil
|
31
|
+
already_matched = false
|
32
|
+
|
33
|
+
if exact # treat patterns as string matching
|
34
|
+
node_s = node.to_s
|
35
|
+
if patterns.has_key? node_s
|
36
|
+
color = patterns[node_s]
|
37
|
+
|
38
|
+
return color
|
39
|
+
else
|
40
|
+
return nil
|
41
|
+
end
|
42
|
+
else
|
43
|
+
node_s = node.to_s
|
44
|
+
|
45
|
+
patterns.each do |pattern, this_color|
|
46
|
+
if node_s =~ pattern
|
47
|
+
abort_if already_matched,
|
48
|
+
"Non specific matching for #{node_s}"
|
49
|
+
|
50
|
+
color = this_color
|
51
|
+
already_matched = true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
return color
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_color node
|
60
|
+
begin
|
61
|
+
node.name.match(/\[&!color="#[0-9A-Fa-f]{6}"\]/)[0]
|
62
|
+
rescue NoMethodError => e
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def color_nodes patterns, tree, node, exact
|
68
|
+
# # check if it needs color, if so set the color
|
69
|
+
# color = add_color_to_leaf_branch patterns, node, exact
|
70
|
+
|
71
|
+
# clean the name no matter what
|
72
|
+
node.name = clean_name node.name
|
73
|
+
|
74
|
+
# if its a leaf that hasnt been checked & needs color
|
75
|
+
if leaf?(tree, node) && !already_checked?(node.name) # && color
|
76
|
+
# check if it needs color, if so set the color
|
77
|
+
|
78
|
+
# NOTE: this was originally before cleaning the node name a
|
79
|
+
# couple lines up, does it matter that it is after?
|
80
|
+
color = add_color_to_leaf_branch patterns, node, exact
|
81
|
+
|
82
|
+
# add color to the name
|
83
|
+
node.name = node.name + color if color
|
84
|
+
elsif !leaf?(tree, node)
|
85
|
+
children = tree.children(node) # get the children
|
86
|
+
children_colors = []
|
87
|
+
children.each do |child|
|
88
|
+
# recurse to color the child if needed
|
89
|
+
color_nodes patterns, tree, child, exact
|
90
|
+
children_colors << get_color(child) # add color of the child
|
91
|
+
end
|
92
|
+
|
93
|
+
# if all the children have the same color
|
94
|
+
if children_colors.uniq.count == 1
|
95
|
+
# set the branch node to only the color name
|
96
|
+
node.name = children_colors[0]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
return node
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|