require 'ms/scan' require 'ms/parser' require 'ms/msrun_index' require 'ms/converter/mzxml' #require 'ms/parser/mzxml' #require 'ms/parser/mzdata' module MS; end class MS::MSRun attr_accessor :start_time, :end_time attr_accessor :scans # (just for reference) the type of file this is (as symbol) attr_accessor :filetype # (just for reference) the version string of this type of file attr_accessor :version # the total number of scans attr_writer :scan_count # should be able to read basic information from a variety of files # this will be written in regexp's because REXML is way too slow, xmlparser # is not guaranteed to be on every system, xmlib is not on win32. # spectra is false, then spectra are not parsed out and included # OPTIONS: # :spectra => *true|false # whether to parse out spectra # [note: precursor intensities not guaranteed to exist unless :spectra == true] def initialize(file=nil, opts={}) myopts = opts.dup ; myopts[:msrun] = self if file filetype_and_version = MS::Parser.filetype_and_version(file) MS::Parser.new(filetype_and_version, :msrun).parse(file, myopts) (@filetype, @version) = filetype_and_version end end # returns an array, whose indices provide the number of scans in each index level the ms_levels, [0] = all the scans, [1] = mslevel 1, [2] = mslevel 2, # ... def scan_counts ar = [] ar[0] = 0 scans.each do |sc| level = sc.ms_level unless ar[level] ar[level] = 0 end ar[level] += 1 ar[0] += 1 end ar end def scan_count(mslevel=0) if mslevel == 0 @scan_count else num = 0 scans.each do |sc| if sc.ms_level == mslevel num += 1 end end num end end # for level 1, finds first scan and asks if it has start_mz/end_mz # attributes. for other levels, asks for start_mz/ end_mz and takes the # min/max. If start_mz and end_mz are not found, goes through every scan # finding the max/min first and last m/z. returns [start_mz (rounded down to # nearest int), end_mz (rounded up to nearest int)] def start_and_end_mz(mslevel=1) if mslevel == 1 # special case for mslevel 1 (where we expect scans to be same length) scans.each do |sc| if sc.ms_level == mslevel if sc.start_mz && sc.end_mz return [sc.start_mz, sc.end_mz] end break end end end hi_mz = nil lo_mz = nil # see if we have start_mz and end_mz for the level we want # set the initial hi_mz and lo_mz in any case have_start_end_mz = false scans.each do |sc| if sc.ms_level == mslevel if sc.start_mz && sc.end_mz lo_mz = sc.start_mz hi_mz = sc.end_mz else mz = sc.spectrum.mz hi_mz = mz.last lo_mz = mz.first end break end end if have_start_end_mz scans.each do |sc| if sc.ms_level == mslevel if sc.start_mz < lo_mz lo_mz = sc.start_mz end if sc.end_mz > hi_mz hi_mz = sc.end_mz end end end else # didn't have the attributes (find by brute force) scans.each do |sc| if sc.ms_level == mslevel mz = sc.spectrum.mz if mz.last > hi_mz hi_mz = mz.last end if mz.last < lo_mz lo_mz = mz.last end end end end [lo_mz.floor, hi_mz.ceil] end # returns an array of precursor mz by scan number # returns only the m/z of the FIRST precursor if multiple def precursor_mz_by_scan_num ar = Array.new(@scans.size + 1) @scans.each do |scan| if prec = scan.precursors.first ar[scan.num] = prec.mz else ar[scan.num] = nil end end ar end # returns an array of times and parallel array of spectra objects. # ms_level = 0 then all spectra and times # ms_level = 1 then all spectra of ms_level 1 def times_and_spectra(ms_level=0) spectra = [] if ms_level == 0 times = @scans.map do |scan| spectra << scan.spectrum scan.time end [times, spectra] else # choose a particular ms_level times = [] @scans.each do |scan| if ms_level == scan.ms_level spectra << scan.spectrum times << scan.time end end [times, spectra] end end # same as the instance method (creates an object without spectrum and calls # instance method of the same name) def self.precursor_mz_by_scan_num(file) self.new(file, :spectra => false).precursor_mz_by_scan_num end # only adds the parent if one is not already present! def self.add_parent_scan(scans, add_intensities=false) #start = Time.now prev_scan = nil parent_stack = [nil] ## we want to set the level to be the first mslevel we come to prev_level = scans.first.ms_level scans.each do |scan| #next unless scan ## the first one is nil, (others?) level = scan.ms_level if prev_level < level parent_stack.unshift prev_scan end if prev_level > level (prev_level - level).times do parent_stack.shift end end if scan.ms_level > 1 scan.precursors.each do |precursor| #precursor.parent = parent_stack.first # that's the next line's precursor[2] = parent_stack.first unless precursor[2] #precursor.intensity if add_intensities precursor[1] = precursor[2].spectrum.intensity_at_mz(precursor[0]) end end end prev_level = level prev_scan = scan end #puts "TOOK #{Time.now - start} secs" end end