bio-svgenes 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ {
2
+ "Page":{"width":800, "height":800, "intervals":15},
3
+ "Tracks": [
4
+ {
5
+ "glyph":"generic",
6
+ "name":"Genes",
7
+ "label":true,
8
+ "file":"gene.gff",
9
+ "file_type":"gff",
10
+ "fill_color":"red",
11
+ "stroke_width":1,
12
+ "x_round":4,
13
+ "y_round":4
14
+ },
15
+ {
16
+ "glyph":"transcript",
17
+ "name":"Transcripts",
18
+ "label":true,
19
+ "file":"transcripts.gff",
20
+ "file_type":"gff",
21
+ "exon_fill_color":"green_white_radial",
22
+ "utr_fill_color":"blue_white_radial",
23
+ "stroke_width":1,
24
+ "gap_marker":"angled"
25
+ },
26
+ {
27
+ "glyph":"histogram",
28
+ "name":"Data",
29
+ "label":true,
30
+ "file":"data.txt",
31
+ "file_type":"data",
32
+ "track_height":100,
33
+ "fill_color":"yellow_white_radial",
34
+ "stroke_width":1
35
+ },
36
+ {
37
+ "glyph":"histogram",
38
+ "name":"Data with maxed-out scale",
39
+ "label":true,
40
+ "file":"data.txt",
41
+ "file_type":"data",
42
+ "track_height":100,
43
+ "fill_color":"red_white_radial",
44
+ "stroke_width":1,
45
+ "max_y":1000
46
+ }
47
+ ]
48
+ }
49
+
data/examples/gene.gff ADDED
@@ -0,0 +1,4 @@
1
+ ##gff-version 3
2
+ ##sequence-region ctg123 1 1497228
3
+ ctg123 . gene 900 9900 . + . ID=gene00001;Name=EDEN
4
+ ctg123 . gene 10900 19900 . - . ID=gene00002;Name=EDEN2
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # get_coverage_in_windows.rb
4
+ #
5
+ # Created by Dan MacLean (TSL) on 2012-10-05.
6
+ # Copyright (c) . All rights reserved.
7
+ ###################################################
8
+ require 'optparse'
9
+ require 'pp'
10
+ options = {}
11
+ optparse = OptionParser.new do|opts|
12
+
13
+ opts.on( '-b', '--bam FILE', ' name of sorrted BAM file' ) do |b|
14
+ options[:bam] = b
15
+
16
+ end
17
+ opts.on( '-f', '--reference_file STRING', 'name of the reference sequence fasta file' ) do |f|
18
+ options[:fasta] = f
19
+
20
+ end
21
+ opts.on( '-c', '--chromosome STRING', 'name of the chromosome / contig in the fasta file' ) do |c|
22
+ options[:chrom] = c
23
+
24
+ end
25
+ opts.on( '-s', '--start INT', 'start position)' ) do |s|
26
+ options[:start] = s.to_i
27
+
28
+ end
29
+ opts.on( '-e', '--stop INT', 'stop position' ) do |e|
30
+ options[:stop] = e.to_i
31
+
32
+ end
33
+ opts.on( '-w', '--step INT', 'window size to use' ) do |w|
34
+ options[:step] = w.to_i
35
+
36
+ end
37
+ opts.on( '-o', '--bam_opts INT', 'bio-samtools BAM opts' ) do |o|
38
+ options[:bam_opts] = o ||= {:min_mapping_quality => 20, :min_base_quality => 30}
39
+
40
+ end
41
+ # This displays the help screen, all programs are
42
+ # assumed to have this option.
43
+ opts.on( '-h', '--help', 'Display this screen' ) do
44
+ puts opts
45
+ puts %{Example: ruby get_coverage_in_windows.rb --bam my_bam.bam --reference_file ref.fasta --chromosome contig1 --start 2000 --stop 3000 --step 100 --bam_opts {:min_mapping_quality => 20, :min_base_quality => 20}}
46
+ exit
47
+ end
48
+ end
49
+ optparse.parse!
50
+
51
+ options[:bam_opts] = {:min_mapping_quality => 20}
52
+
53
+
54
+ ###Actual script ---
55
+ require 'rubygems'
56
+ require 'bio-samtools'
57
+ require 'enumerator'
58
+ bam = Bio::DB::Sam.new({:bam=>options[:bam], :fasta=>options[:fasta]} )
59
+ bam.open
60
+
61
+ def passes_quality(aln, options)
62
+ if aln.mapq > options[:bam_opts][:min_mapping_quality] && aln.is_mapped
63
+ true
64
+ else
65
+ false
66
+ end
67
+ end
68
+
69
+ def actually_modify(cigar_arr, bases_to_ignore_at_start)
70
+ result = []
71
+ bases_ditched = 0
72
+ cigar_arr.each do |arr|
73
+ if arr[0] < bases_to_ignore_at_start - bases_ditched
74
+ bases_ditched += arr[0]
75
+ elsif arr[0] + bases_ditched >= bases_to_ignore_at_start && bases_ditched < bases_to_ignore_at_start
76
+ remainder = arr[0] - (bases_to_ignore_at_start - bases_ditched)
77
+ bases_ditched += (bases_to_ignore_at_start - bases_ditched)
78
+ result << [remainder, arr[1]] unless remainder == 0
79
+ elsif bases_ditched >= bases_to_ignore_at_start
80
+ result << [arr[0],arr[1]]
81
+ else
82
+ raise "Shouldn't ever happen. Did though... Panic. Alarums. Etc."
83
+ exit
84
+ end
85
+ end
86
+ result
87
+ end
88
+
89
+ def correct_cigar_for_overlaps(cigar_arr, bases_to_ignore_at_start, bases_to_ignore_at_end)
90
+ #takes the end off the cigar arr if the bases it describes are outside the window
91
+ return cigar_arr if bases_to_ignore_at_start == 0 && bases_to_ignore_at_end == 0
92
+ result = actually_modify(cigar_arr,bases_to_ignore_at_start)
93
+ actually_modify(result.reverse, bases_to_ignore_at_end).reverse
94
+ end
95
+
96
+ (options[:start]..options[:stop]).step(options[:step]) do |start|
97
+ positions = Array.new(options[:step], 0.0)
98
+
99
+ bam.fetch(options[:chrom], start, start + options[:step]).each do |aln|
100
+ next unless passes_quality(aln, options)
101
+
102
+ #update start and end for overhanging clipped bases
103
+ a = aln.cigar.split(/([A-Z])/)
104
+ cigar_arr = a.enum_for(:each_slice, 2).to_a #convert to array of arrays
105
+ cigar_arr.collect!{|a| [a.first.to_i, a.last] } #convert numbers to ints
106
+ if cigar_arr.first.last == "S" or cigar_arr.first.last == "H"
107
+ aln.pos = aln.pos - cigar_arr.first.first
108
+ aln.pos = 1 if aln.pos < 1 #ignore negative overhangs...
109
+ end
110
+
111
+ if cigar_arr.last.last == "S" or cigar_arr.last.last == "H"
112
+ aln.calend = aln.calend - cigar_arr.last.first
113
+ aln.calend = 1 if aln.calend < 1
114
+ end
115
+
116
+ ##remove any padding operations, we're only concerned with alignments relative to the reference, not other reads..
117
+ cigar_arr.delete_if {|b| b.last == "P"}
118
+ #need to know whether start outside window ...
119
+ bases_to_ignore_at_start = 0
120
+ bases_to_ignore_at_end = 0
121
+
122
+ if aln.pos < start
123
+ bases_to_ignore_at_start = start - aln.pos
124
+ end
125
+
126
+ if aln.calend >= start + options[:step] ## left hand border is included in window, right hand border excluded
127
+ bases_to_ignore_at_end = aln.calend - (start + options[:step]) + 1
128
+ end
129
+
130
+ cigar_arr = correct_cigar_for_overlaps(cigar_arr.dup, bases_to_ignore_at_start, bases_to_ignore_at_end)
131
+ #puts "start ignore = #{bases_to_ignore_at_start}"
132
+ #puts "end ignore = #{bases_to_ignore_at_end}"
133
+ #modify the read objects start and stop positions to keep only the bit in the window
134
+ #ignore soft clipping at the start and end
135
+ if bases_to_ignore_at_start >0
136
+ aln.pos = aln.pos + bases_to_ignore_at_start
137
+ end
138
+ if bases_to_ignore_at_end > 0
139
+ aln.calend = aln.calend - bases_to_ignore_at_end
140
+ end
141
+
142
+ begin
143
+ raise "aln start >= aln end" if aln.pos > aln.calend
144
+ rescue
145
+ next
146
+ else
147
+ read_start_corrected = aln.pos - start
148
+ read_end_corrected = aln.calend - start
149
+ range_start = read_start_corrected
150
+ range_stop = read_end_corrected
151
+ positions[range_start..range_stop] = positions.slice(range_start..range_stop).collect! {|a| a += 1}
152
+ ranges_to_decrement = []
153
+ dist_through_read = 0
154
+ cigar_arr.each_with_index do |cigar,i|
155
+ #use the corrected cigar array to correct the coverage post-hoc
156
+ if cigar.last == "D" or cigar.last == "N"
157
+ ranges_to_decrement << Range.new(read_start_corrected + dist_through_read, read_start_corrected + dist_through_read + cigar.first - 1)
158
+ end
159
+ dist_through_read += cigar.first
160
+ end #end cigar_arr each
161
+ #pp ranges_to_decrement unless ranges_to_decrement.empty?
162
+ ranges_to_decrement.each do |range|
163
+ begin
164
+ positions[range] = positions.slice(range).collect! {|b| b -= 1}
165
+ rescue
166
+ puts "Error: positions array likely out of range ... this error affects one read only ... so we'll skip it. Panic if you get lots of this"
167
+ end
168
+ end
169
+ end
170
+ end #end fetch
171
+ average = positions.inject(0) {|r,e| r + e} / positions.length.to_f
172
+ puts [start, start + options[:step], average].join("\t")
173
+
174
+ end
175
+
176
+ bam.close
@@ -0,0 +1,16 @@
1
+ ##gff-version 3
2
+ ##sequence-region ctg123 1 1497228
3
+ ctg123 . mRNA 900 9200 . + . ID=mRNA00001;Name=EDEN.1
4
+ ctg123 . utr 900 1000 . + . ID=utr00001;Parent=mRNA00001
5
+ ctg123 . exon 1050 1500 . + . ID=exon00002;Parent=mRNA00001
6
+ ctg123 . exon 3000 3902 . + . ID=exon00003;Parent=mRNA00001
7
+ ctg123 . exon 5000 5500 . + . ID=exon00004;Parent=mRNA00001
8
+ ctg123 . exon 7000 9000 . + . ID=exon00005;Parent=mRNA00001
9
+ ctg123 . utr 9100 9200 . + . ID=utr00002;Parent=mRNA00001
10
+ ctg123 . mRNA 10900 19200 . - . ID=mRNA00002;Name=EDEN2.1
11
+ ctg123 . utr 10900 11000 . - . ID=utr00003;Parent=mRNA00002
12
+ ctg123 . exon 11050 11500 . - . ID=exon00006;Parent=mRNA00002
13
+ ctg123 . exon 13000 13902 . - . ID=exon00007;Parent=mRNA00002
14
+ ctg123 . exon 15000 15500 . - . ID=exon00008;Parent=mRNA00002
15
+ ctg123 . exon 17000 19000 . - . ID=exon00009;Parent=mRNA00002
16
+ ctg123 . utr 19100 19200 . - . ID=utr00004;Parent=mRNA00002
data/lib/.DS_Store ADDED
Binary file
@@ -0,0 +1,17 @@
1
+ # Please require your code below, respecting the bioruby directory tree.
2
+ # For instance, perhaps the only uncommented line in this file might
3
+ # be something like this:
4
+ #
5
+ # require 'bio/sequence/awesome_sequence_plugin_thingy'
6
+ #
7
+ # and then create the ruby file 'lib/bio/sequence/awesome_sequence_thingy.rb'
8
+ # and put your plugin's code there. It is bad practice to write other code
9
+ # directly into this file, because doing so causes confusion if this biogem
10
+ # was ever to get merged into the main bioruby tree.
11
+
12
+ require 'bio/graphics/svgee'
13
+ require 'bio/graphics/glyph'
14
+ require 'bio/graphics/page'
15
+ require 'bio/graphics/track'
16
+ require 'bio/graphics/mini_feature'
17
+ require 'bio/graphics/primitive'
data/lib/bio/.DS_Store ADDED
Binary file
@@ -0,0 +1,241 @@
1
+ class Glyph
2
+ attr_reader :glyphs
3
+ #holds a load of definitions for glyphs .. a glyph is an array of primitives...
4
+ @glyphs = [:generic, :directed, :transcript, :scale, :label, :histogram]
5
+
6
+ def self.generic(args) #:x, :y, :width :fill, :stroke :stroke_width, :style, :height,
7
+ args = {
8
+ :height => 10,
9
+ :fill_color => 'red',
10
+ :stroke => "black",
11
+ :stroke_width => 1,
12
+ :x_round => 1,
13
+ :y_round => 1,
14
+ :style => "fill-opacity:0.4;"}.merge!(args)
15
+ [Primitive.new(:rectangle, args)]
16
+ end
17
+
18
+ def self.directed(args) #:x, :y, :width :fill, :stroke :stroke_width, :style, :height
19
+ args = {
20
+
21
+ :height => 10,
22
+ :fill_color => 'red',
23
+ :stroke => "black",
24
+ :stroke_width => 1,
25
+ :style => "fill-opacity:0.4;"}.merge!(args)
26
+
27
+ if args[:strand] == '-'
28
+ args[:points] = "#{args[:x]},#{args[:y]} #{args[:x] + args[:width]},#{args[:y]} #{args[:x] + args[:width]},#{args[:y] + args[:height] } #{args[:x]},#{args[:y] + (args[:height])} #{args[:x] - (args[:height] * 0.2)},#{args[:y] + (args[:height]/2)}"
29
+ else
30
+ args[:points] = "#{args[:x]},#{args[:y]} #{args[:x] + args[:width] - (args[:height] * 0.2)},#{args[:y]} #{args[:x] + args[:width]},#{args[:y] + (args[:height]/2) } #{args[:x] + args[:width] - (args[:height] * 0.2)},#{args[:y] + args[:height]} #{args[:x]},#{args[:y] + args[:height]}"
31
+ end
32
+ [Primitive.new(:polygon, args)]
33
+ end
34
+
35
+ def self.transcript(args)
36
+ args = {
37
+ :height => 10,
38
+ :utr_fill_color => 'black',
39
+ :utr_stroke => "black",
40
+ :utr_stroke_width => 1,
41
+ :exon_fill_color => 'red',
42
+ :exon_stroke => "black",
43
+ :exon_stroke_width => 1,
44
+ :line_color => 'black',
45
+ :line_width => 1,
46
+ :exon_style => "fill-opacity:0.4;",
47
+ :utr_style => "",
48
+ :line_style => "",
49
+ :block_gaps => "",
50
+ :gap_marker => ""
51
+ }.merge!(args)
52
+
53
+ composite = []
54
+
55
+ if not args[:utrs].empty? ##draw the utr as terminal element...##terminal utr is the one with the point on...
56
+ x,width = args[:strand] == '-' ? args[:utrs].shift : args[:utrs].pop
57
+ composite += Glyph.directed(:x => x,
58
+ :y => args[:y],
59
+ :width => width,
60
+ :strand => args[:strand],
61
+ :height => args[:height],
62
+ :fill_color => args[:utr_fill_color],
63
+ :stroke =>args[:utr_stroke],
64
+ :stroke_width => args[:utr_stroke_width],
65
+ :style => args[:utr_style] )
66
+ ##draw the other(s!)
67
+ args[:utrs].each do |utr|
68
+ composite << Primitive.new(:rectangle, {
69
+ :x => utr.first,
70
+ :width => utr.last,
71
+ :y => args[:y],
72
+ :height => args[:height],
73
+ :fill_color => args[:utr_fill_color],
74
+ :stroke =>args[:utr_stroke],
75
+ :stroke_width => args[:utr_stroke_width],
76
+ :style => args[:utr_style]} )
77
+ end
78
+ else ## draw the terminal exon as terminal element
79
+ points = nil
80
+ x,width = args[:strand] == '-' ? args[:exons].shift : args[:exons].pop
81
+ composite += Glyph.directed(:x => x,
82
+ :y => args[:y],
83
+ :width => width,
84
+ :strand => args[:strand],
85
+ :height => args[:height],
86
+ :fill_color => args[:exon_fill_color],
87
+ :stroke =>args[:exon_stroke],
88
+ :stroke_width => args[:exon_stroke_width],
89
+ :style => args[:exon_style] )
90
+ end
91
+ #draw any remaining exons
92
+ args[:exons].each do |exon|
93
+ composite << Primitive.new(:rectangle, {
94
+ :x => exon[0],
95
+ :width => exon[1],
96
+ :y => args[:y],
97
+ :height => args[:height],
98
+ :fill_color => args[:exon_fill_color],
99
+ :stroke =>args[:exon_stroke],
100
+ :stroke_width => args[:exon_stroke_width],
101
+ :style => args[:exon_style]} )
102
+ end
103
+ if args[:gap_marker] == "angled" and not args[:block_gaps].empty?
104
+ args[:block_gaps].each do |gap|
105
+ points = "#{gap.first},#{args[:y] + (args[:height]/2) } #{gap.first + (gap.last/2)},#{args[:y]} #{gap.first + gap.last},#{args[:y] + (args[:height]/2)}"
106
+ composite << Primitive.new(:polyline, {
107
+ :points => points,
108
+ :stroke => args[:line_color],
109
+ :stroke_width => args[:line_width],
110
+ :fill => "none",
111
+ :line_style => args[:line_style]})
112
+ end
113
+ else
114
+ #add line
115
+ composite << Primitive.new(:line, {
116
+ :x1 => args[:x],
117
+ :x2 => "#{args[:x] + args[:width]}",
118
+ :y1 => args[:y] + (args[:height]/2),
119
+ :y2 => args[:y] + (args[:height]/2),
120
+ :stroke => args[:line_color],
121
+ :stroke_width => args[:line_width],
122
+ :line_style => args[:line_style]})
123
+
124
+ end
125
+ composite
126
+ end
127
+
128
+ def self.scale(args)
129
+
130
+
131
+ first_mark = args[:start]
132
+ last_mark = args[:stop]
133
+ #(num.to_f / @nt_per_px_x.to_f)
134
+ full_dist = last_mark - first_mark
135
+ interval_width = full_dist / args[:number_of_intervals]
136
+
137
+
138
+ a = [Primitive.new(:line,
139
+ :stroke => 'black',
140
+ :stroke_width => 1,
141
+ :x1 => 1, :x2 => args[:page_width] * 1.1,
142
+ :y1 => "20", :y2 => "20" )]
143
+
144
+
145
+ marks = (first_mark..last_mark).step(interval_width).to_a
146
+ px_per_nt = full_dist / args[:page_width]
147
+ marks.each do |mark|
148
+ x = (mark.to_f - first_mark )/ px_per_nt
149
+ a << Primitive.new(:rectangle,
150
+ :x => x,
151
+ :y => 20,
152
+ :stroke => 'black',
153
+ :stroke_width => 1,
154
+ :width => 1,
155
+ :height => 5 )
156
+
157
+ a << Primitive.new(:text,
158
+ :x => x,
159
+ :y => 40, :fill => 'black',
160
+ :text => mark,
161
+ :style => "font-family:Arial;font-style:italic")
162
+ end
163
+ return a
164
+ end
165
+
166
+ def self.label(args)
167
+ [Primitive.new(:text,
168
+ :text => args[:text],
169
+ :x => args[:x],
170
+ :y => args[:y],
171
+ :fill => "black",
172
+ :style => "font-family:monospace;")]
173
+ end
174
+
175
+ def self.gradients #needs to know which of its gradients are predefined
176
+ [:red_white_h, :green_white_h, :blue_white_h, :yellow_white_h, :red_white_radial, :green_white_radial, :blue_white_radial, :yellow_white_radial ]
177
+ end
178
+
179
+ def self.gradient(gradient)
180
+ type, color = case gradient
181
+ when :red_white_h
182
+ [:linear, "red"]
183
+ when :green_white_h
184
+ [:linear, "green"]
185
+ when :blue_white_h
186
+ [:linear, "blue"]
187
+ when :yellow_white_h
188
+ [:linear, "yellow"]
189
+ when :red_white_radial
190
+ [:radial, "red"]
191
+ when :green_white_radial
192
+ [:radial, "green"]
193
+ when :blue_white_radial
194
+ [:radial, "blue"]
195
+ when :yellow_white_radial
196
+ [:radial, "yellow"]
197
+ end
198
+
199
+ case type
200
+ when :linear
201
+ {:type => :linear,
202
+ :id => gradient,
203
+ :x1 => 0, :y1 => 0,
204
+ :x2 => 0, :y2 => 100,
205
+ :stops => [
206
+ {
207
+ :color => color,
208
+ :offset => 0,
209
+ :opacity=> 1
210
+ },
211
+ {
212
+ :color => "white",
213
+ :offset => 100,
214
+ :opacity=> 1
215
+ }
216
+ ]
217
+ }
218
+ when :radial
219
+ {
220
+ :type => :radial,
221
+ :id => gradient,
222
+ :cx => 50, :cy => 50,
223
+ :r => 50, :fx => 50, :fy => 50,
224
+ :stops => [
225
+ {:offset => 0,
226
+ :color => "white",
227
+ :opacity => 0
228
+ },
229
+ {
230
+ :offset => 100,
231
+ :color => color,
232
+ :opacity => 1
233
+ }
234
+ ]
235
+ }
236
+ end
237
+
238
+ end
239
+
240
+
241
+ end