X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=code_ruby%2Flib%2Fmaasha%2Fseq.rb;h=7377da533d50ea34fb4dc12c3e2bfb7f05a7e944;hb=d71b5469d06eeaa8de7d83b501eb17a0dedd7999;hp=cf4b6e6802883d5447c7bbad7157ae65bc0c95b1;hpb=80706855913e8607239241fd2938f47d1fb76799;p=biopieces.git diff --git a/code_ruby/lib/maasha/seq.rb b/code_ruby/lib/maasha/seq.rb index cf4b6e6..7377da5 100644 --- a/code_ruby/lib/maasha/seq.rb +++ b/code_ruby/lib/maasha/seq.rb @@ -27,8 +27,12 @@ require 'maasha/seq/digest' require 'maasha/seq/trim' require 'narray' -autoload :BackTrack, 'maasha/seq/backtrack.rb' -autoload :Dynamic, 'maasha/seq/dynamic.rb' +autoload :BackTrack, 'maasha/seq/backtrack' +autoload :Dynamic, 'maasha/seq/dynamic' +autoload :Homopolymer, 'maasha/seq/homopolymer' +autoload :Hamming, 'maasha/seq/hamming' +autoload :Levenshtein, 'maasha/seq/levenshtein' +autoload :Ambiguity, 'maasha/seq/ambiguity' # Residue alphabets DNA = %w[a t c g] @@ -67,7 +71,6 @@ TRANS_TAB11 = { "GTG" => "V", "GCG" => "A", "GAG" => "E", "GGG" => "G" } - # Error class for all exceptions to do with Seq. class SeqError < StandardError; end @@ -87,10 +90,10 @@ class Seq def self.new_bp(record) seq_name = record[:SEQ_NAME] seq = record[:SEQ] - type = record[:SEQ_TYPE] + type = record[:SEQ_TYPE].to_sym if record[:SEQ_TYPE] qual = record[:SCORES] - self.new(seq_name, seq, type, qual) + self.new(seq_name: seq_name, seq: seq, type: type, qual: qual) end # Class method that generates all possible oligos of a specifed length and type. @@ -98,9 +101,9 @@ class Seq raise SeqError, "Cannot generate negative oligo length: #{length}" if length <= 0 case type.downcase - when /dna/ then alph = DNA - when /rna/ then alph = RNA - when /protein/ then alph = PROTEIN + when :dna then alph = DNA + when :rna then alph = RNA + when :protein then alph = PROTEIN else raise SeqError, "Unknown sequence type: #{type}" end @@ -122,16 +125,20 @@ class Seq oligos end - # Initialize a sequence object with the following arguments: - # - seq_name: Name of the sequence. - # - seq: The sequence. - # - type: The sequence type - DNA, RNA, or protein - # - qual: An Illumina type quality scores string. - def initialize(seq_name = nil, seq = nil, type = nil, qual = nil) - @seq_name = seq_name - @seq = seq - @type = type - @qual = qual + # Initialize a sequence object with the following options: + # - :seq_name Name of the sequence. + # - :seq The sequence. + # - :type The sequence type - DNA, RNA, or protein + # - :qual An Illumina type quality scores string. + def initialize(options = {}) + @seq_name = options[:seq_name] + @seq = options[:seq] + @type = options[:type] + @qual = options[:qual] + + if @seq and @qual and @seq.length != @qual.length + raise SeqError, "Sequence length and score length mismatch: #{@seq.length} != #{@qual.length}" + end end # Method that guesses and returns the sequence type @@ -140,9 +147,9 @@ class Seq raise SeqError, "Guess failed: sequence is nil" if self.seq.nil? case self.seq[0 ... 100].downcase - when /[flpqie]/ then return "protein" - when /[u]/ then return "rna" - else return "dna" + when /[flpqie]/ then return :protein + when /[u]/ then return :rna + else return :dna end end @@ -190,24 +197,24 @@ class Seq # Method that returns true is a given sequence type is DNA. def is_dna? - self.type == 'dna' + self.type == :dna end # Method that returns true is a given sequence type is RNA. def is_rna? - self.type == 'rna' + self.type == :rna end # Method that returns true is a given sequence type is protein. def is_protein? - self.type == 'protein' + self.type == :protein end # Method to transcribe DNA to RNA. def to_rna raise SeqError, "Cannot transcribe 0 length sequence" if self.length == 0 raise SeqError, "Cannot transcribe sequence type: #{self.type}" unless self.is_dna? - self.type = 'rna' + self.type = :rna self.seq.tr!('Tt','Uu') end @@ -215,14 +222,13 @@ class Seq def to_dna raise SeqError, "Cannot reverse-transcribe 0 length sequence" if self.length == 0 raise SeqError, "Cannot reverse-transcribe sequence type: #{self.type}" unless self.is_rna? - - self.type = 'dna' + self.type = :dna self.seq.tr!('Uu','Tt') end # Method to translate a DNA sequence to protein. def translate!(trans_tab = 11) - raise SeqError, "Sequence type must be 'dna' - not #{self.type}" unless self.type == 'dna' + raise SeqError, "Sequence type must be 'dna' - not #{self.type}" unless self.type == :dna raise SeqError, "Sequence length must be a multiplum of 3 - was: #{self.length}" unless (self.length % 3) == 0 case trans_tab @@ -257,7 +263,7 @@ class Seq self.seq = protein self.qual = nil - self.type = "protein" + self.type = :protein self end @@ -288,8 +294,8 @@ class Seq raise SeqError, "Missing seq_name" if self.seq_name.nil? or self.seq_name == '' raise SeqError, "Missing seq" if self.seq.nil? or self.seq.empty? - seq_name = self.seq_name.to_s - seq = self.seq.to_s + seq_name = self.seq_name + seq = self.seq.dup unless wrap.nil? seq.gsub!(/(.{#{wrap}})/) do |match| @@ -299,7 +305,7 @@ class Seq seq.chomp! end - ">" + seq_name + $/ + seq + $/ + ">" + seq_name.to_s + $/ + seq + $/ end # Method that given a Seq entry returns a FASTQ entry (a string). @@ -337,7 +343,14 @@ class Seq # Method to reverse the sequence. def reverse - Seq.new(self.seq_name, self.seq.reverse, self.type, self.qual ? self.qual.reverse : self.qual) + entry = Seq.new( + seq_name: self.seq_name, + seq: self.seq.reverse, + type: self.type, + qual: (self.qual ? self.qual.reverse : self.qual) + ) + + entry end # Method to reverse the sequence. @@ -351,10 +364,11 @@ class Seq def complement raise SeqError, "Cannot complement 0 length sequence" if self.length == 0 - entry = Seq.new - entry.seq_name = self.seq_name - entry.type = self.type - entry.qual = self.qual + entry = Seq.new( + seq_name: self.seq_name, + type: self.type, + qual: self.qual + ) if self.is_dna? entry.seq = self.seq.tr('AGCUTRYWSMKHDVBNagcutrywsmkhdvbn', 'TCGAAYRWSKMDHBVNtcgaayrwskmdhbvn') @@ -384,34 +398,46 @@ class Seq # Method to determine the Hamming Distance between # two Sequence objects (case insensitive). - def hamming_distance(seq) - self.seq.upcase.hamming_distance(seq.seq.upcase) + def hamming_distance(entry, options = nil) + if options and options[:ambiguity] + Hamming.distance(self.seq, entry.seq) + else + self.seq.upcase.hamming_distance(entry.seq.upcase) + end + end + + # Method to determine the Edit Distance between + # two Sequence objects (case insensitive). + def edit_distance(entry) + Levenshtein.distance(self.seq, entry.seq) end # Method that generates a random sequence of a given length and type. def generate(length, type) raise SeqError, "Cannot generate sequence length < 1: #{length}" if length <= 0 - case type.downcase - when "dna" - alph = DNA - when "rna" - alph = RNA - when "protein" - alph = PROTEIN + case type + when :dna then alph = DNA + when :rna then alph = RNA + when :protein then alph = PROTEIN else raise SeqError, "Unknown sequence type: #{type}" end seq_new = Array.new(length) { alph[rand(alph.size)] }.join("") self.seq = seq_new - self.type = type.downcase + self.type = type seq_new end # Method to return a new Seq object with shuffled sequence. def shuffle - Seq.new(self.seq_name, self.seq.split('').shuffle!.join, self.type, self.qual) + Seq.new( + seq_name: self.seq_name, + seq: self.seq.split('').shuffle!.join, + type: self.type, + qual: self.qual + ) end # Method to shuffle a sequence randomly inline. @@ -420,6 +446,15 @@ class Seq self end + # Method to add two Seq objects. + def +(entry) + new_entry = Seq.new() + new_entry.seq = self.seq + entry.seq + new_entry.type = self.type if self.type == entry.type + new_entry.qual = self.qual + entry.qual if self.qual and entry.qual + new_entry + end + # Method to concatenate sequence entries. def <<(entry) raise SeqError, "sequences of different types" unless self.type == entry.type @@ -431,35 +466,21 @@ class Seq self end - # Method that returns a subsequence of from a given start position - # and of a given length. - def subseq(start, length = self.length - start) - raise SeqError, "subsequence start: #{start} < 0" if start < 0 - raise SeqError, "subsequence length: #{length} < 0" if length < 0 - raise SeqError, "subsequence start + length > Seq.length: #{start} + #{length} > #{self.length}" if start + length > self.length - - if length == 0 - seq = "" - qual = "" unless self.qual.nil? - else - stop = start + length - 1 - - seq = self.seq[start .. stop] - qual = self.qual[start .. stop] unless self.qual.nil? - end + # Index method for Seq objects. + def [](*args) + entry = Seq.new + entry.seq_name = self.seq_name.dup if self.seq_name + entry.seq = self.seq[*args] + entry.type = self.type + entry.qual = self.qual[*args] unless self.qual.nil? - Seq.new(self.seq_name, seq, self.type, qual) # TODO changed self.seq_name.dup to self.seq_name -> consequence? + entry end - # Method that replaces a sequence with a subsequence from a given start position - # and of a given length. - def subseq!(start, length = self.length - start) - s = subseq(start, length) - - self.seq_name = s.seq_name - self.seq = s.seq - self.type = s.type - self.qual = s.qual + # Index assignment method for Seq objects. + def []=(*args, entry) + self.seq[*args] = entry.seq[*args] + self.qual[*args] = entry.qual[*args] unless self.qual.nil? self end @@ -473,7 +494,7 @@ class Seq start = rand(self.length - length + 1) end - self.subseq(start, length) + self[start .. start + length] end # Method that returns the residue compositions of a sequence in @@ -489,23 +510,6 @@ class Seq comp end - # Method that returns the length of the longest homopolymeric stretch - # found in a sequence. - def homopol_max(min = 1) - return 0 if self.seq.nil? or self.seq.empty? - - found = false - - self.seq.upcase.scan(/A{#{min},}|T{#{min},}|G{#{min},}|C{#{min},}|N{#{min},}/) do |match| - found = true - min = match.size > min ? match.size : min - end - - return 0 unless found - - min - end - # Method that returns the percentage of hard masked residues # or N's in a sequence. def hard_mask @@ -630,22 +634,26 @@ class Seq regex_start = Regexp.new(start_codons.join('|'), true) regex_stop = Regexp.new(stop_codons.join('|'), true) - while pos_beg and pos_beg < self.length - size_min - if pos_beg = self.seq.index(regex_start, pos_beg) - if pos_end = self.seq.index(regex_stop, pos_beg) - length = (pos_end - pos_beg) + 3 + while pos_beg = self.seq.index(regex_start, pos_beg) + pos_end = pos_beg + 3 + + while pos_end = self.seq.index(regex_stop, pos_end) + length = (pos_end - pos_beg) + 3 - if (length % 3) == 0 - if size_min <= length and length <= size_max - subseq = self.subseq(pos_beg, length) + if (length % 3) == 0 + if size_min <= length and length <= size_max + subseq = self[pos_beg ... pos_beg + length] - orfs << [subseq, pos_beg, pos_end + 3] - end + orfs << [subseq, pos_beg, pos_end + 3] end + + break end - pos_beg += 1 + pos_end += 1 end + + pos_beg += 1 end if pick_longest