ms-msrun 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -4,7 +4,7 @@ A library for working with LC/MS runs.
4
4
 
5
5
  == Examples
6
6
 
7
- The following example will work on *mzXML*, *mzData* ( and *mzML* files, shortly)!
7
+ The following example will work on *mzXML*, *mzData* ( and *mzML* files in near future)!
8
8
 
9
9
  require "ms/msrun"
10
10
 
@@ -55,8 +55,7 @@ The following example will work on *mzXML*, *mzData* ( and *mzML* files, shortly
55
55
  gem install ms-msrun
56
56
 
57
57
  The library currently relies on xmlparser (though LibXML is close to being
58
- supported). After installation of ms-msrun (which will automatically install
58
+ supported). After installation of ms-msrun (which should automatically install
59
59
  `axml`) issue this command to get instructions on installing xmlparser:
60
60
 
61
- ruby -rubygems -e 'require "axml" \
62
- puts AXML::Autoload.install_instructions(:xmlparser)'
61
+ ruby -rubygems -e 'require "axml"; puts AXML::Autoload.install_instructions(:xmlparser)'
data/Rakefile CHANGED
@@ -35,11 +35,26 @@ Rake::RDocTask.new do |rd|
35
35
  rd.options.push( *rdoc_options )
36
36
  end
37
37
 
38
- desc "create and upload docs to server"
39
- task :upload_docs => [:rdoc] do
40
- sh "scp -r #{rdoc_dir}/* jtprince@rubyforge.org:/var/www/gforge-projects/mspire/ms-msrun/"
38
+
39
+ desc "Publish RDoc to RubyForge"
40
+ task :publish_rdoc => [:rdoc] do
41
+ require 'yaml'
42
+
43
+ config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
44
+ host = "#{config["username"]}@rubyforge.org"
45
+
46
+ rsync_args = "-v -c -r"
47
+ remote_dir = "/var/www/gforge-projects/mspire/projects/#{NAME}"
48
+ local_dir = "rdoc"
49
+
50
+ sh %{rsync #{rsync_args} #{local_dir}/ #{host}:#{remote_dir}}
41
51
  end
42
52
 
53
+ #desc "create and upload docs to server"
54
+ #task :upload_docs => [:rdoc] do
55
+ # sh "scp -r #{rdoc_dir}/* jtprince@rubyforge.org:/var/www/gforge-projects/mspire/projects/ms-msrun/"
56
+ #end
57
+
43
58
  ###############################################
44
59
  # TESTS
45
60
  ###############################################
@@ -77,12 +92,12 @@ gemspec = Gem::Specification.new do |t|
77
92
  t.has_rdoc = true
78
93
  t.authors = ["John Prince"]
79
94
  t.files = dist_files
80
- t.add_dependency 'axml', '~> 0.0.5'
95
+ t.add_dependency 'ms-core'
96
+ t.add_dependency 'nokogiri'
81
97
  t.add_dependency 'runarray'
82
98
  t.rdoc_options = rdoc_options
83
99
  t.extra_rdoc_files = rdoc_extra_includes
84
100
  t.executables = FL["bin/*"].map {|file| File.basename(file) }
85
- t.requirements << 'xmlparser (preferrably) or libxml'
86
101
  t.test_files = FL["spec/**/*_spec.rb"]
87
102
  end
88
103
 
@@ -1,5 +1,6 @@
1
+ #!/usr/bin/ruby
1
2
 
2
- require 'ms/spectrum'
3
+ require 'ms/data/lazy_string'
3
4
 
4
5
  if ARGV.size == 0
5
6
  puts "usage: #{File.basename(__FILE__)} <base64_string>"
@@ -7,7 +8,5 @@ if ARGV.size == 0
7
8
  exit
8
9
  end
9
10
 
10
- precision = 32
11
- network_order = true
12
- ar = Ms::Spectrum.base64_to_array(ARGV.shift, precision, network_order)
11
+ ar = Ms::Data::LazyString.new(ARGV.shift).to_a
13
12
  puts "[ " + ar.join(", ") + " ]"
data/bin/ms_to_obiwarp.rb CHANGED
@@ -21,8 +21,8 @@ opts = OptionParser.new do |op|
21
21
  op.separator "(sums m/z values that round to the same bin)"
22
22
  op.separator ""
23
23
  op.on("--mz-inc N", Float, "m/z increment (def: 1.0)") {|n| opt[:mz_inc] = n.to_f}
24
- op.on("--mz-start N", Float, "m/z start (def: start of 1st full scan)") {|n| opt[:start_mz] = n.to_f}
25
- op.on("--mz-end N", Float, "m/z end (def: end of 1st full scan)") {|n| opt[:end_mz] = n.to_f}
24
+ op.on("--mz-start N", Float, "m/z start (otherwise auto)") {|n| opt[:start_mz] = n.to_f}
25
+ op.on("--mz-end N", Float, "m/z end (otherwise auto)") {|n| opt[:end_mz] = n.to_f}
26
26
  op.on("--baseline N", Float, "value for missing indices (def: #{opt[:baseline]})") {|n| opt[:baseline] = n.to_f}
27
27
  op.on("--ascii", "generates an lmata file instead") {opt[:ascii] = true}
28
28
  op.on("-v", "--verbose") {$VERBOSE = true}
@@ -36,21 +36,12 @@ end
36
36
  ARGV.each do |file|
37
37
  Ms::Msrun.open(file) do |msrun|
38
38
  mslevel = 1
39
- (start_mz, end_mz) = msrun.start_and_end_mz(mslevel)
40
- (times, spectra) = msrun.times_and_spectra(mslevel)
41
- args = {
42
- :start_mz => start_mz,
43
- :end_mz => end_mz,
44
-
45
- :start_tm => times.first,
46
- :end_tm => times.last,
47
- :inc_tm => nil,
48
- }
49
- args.merge!(opt)
50
- lmat = Ms::Msrun::Lmat.new.from_times_and_spectra(times, spectra, args)
39
+ lmat = Lmat.new
40
+ t = Time.now
41
+ lmat.from_msrun(msrun, opt)
51
42
  ext = File.extname(file)
52
43
  outfile = file.sub(/#{Regexp.escape(ext)}$/, opt[:newext])
53
- if args[:ascii]
44
+ if opt[:ascii]
54
45
  outfile << "a"
55
46
  lmat.print(outfile)
56
47
  else
data/bin/ms_to_search.rb CHANGED
@@ -1,37 +1,30 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
3
  require 'rubygems'
4
- require 'tap'
4
+ require 'optparse'
5
+ require 'ms/msrun/search'
5
6
 
7
+ opts = OptionParser.new do |op|
8
+ op.banner = "usage: #{File.basename(__FILE__)} <file>.mzXML ..."
9
+ op.separator "outputs: <file>.mgf"
10
+ op.separator "[by default outputs .mgf file]"
6
11
 
7
- module Ms ; end
8
- class Ms::Msrun ; end
9
-
10
- # Documentation here
11
- class Ms::Msrun::Search < Tap::Task
12
-
13
- #config :first_scan, 0, :short => 'F', &c.integer # first scan
14
- #config :last_scan, 1e12, :short => 'L', &c.integer # last scan
15
- ## if not determined to be +1, then create these charge states
16
- #config( :charge_states, [2,3], :short => 'c') {|v| v.split(',') }
17
- #config :bottom_mh, 0, :short => 'B', &c.float # bottom MH+
18
- #config :top_mh, -1.0, :short => 'T', &c.float # top MH+
19
- #config :min_peaks, 0, :short => 'P', &c.integer # minimum peak count
20
- #config :ms_levels, 2..-1, :short => 'M', &c.range # ms levels to export
12
+ end
21
13
 
14
+ if ARGV.size == 0
15
+ puts opts.to_s
16
+ exit
17
+ end
22
18
 
23
- def process(filename)
24
- Ms::Msrun.open(filename) do |ms|
25
- ms.to_mgf(ms.filename.chomp(File.extname(ms.filename)))
26
- end
19
+ ARGV.each do |file|
20
+ Ms::Msrun.open(file) do |ms|
21
+ outfile = ms.to_mgf(file.sub(/\.mzxml/i, '.mgf'))
22
+ ms.to_mgf(outfile)
27
23
  end
28
24
  end
29
25
 
30
- Ms::Msrun::Search.execute
31
-
32
26
  # extract_msn.exe -M0.2 -B85 -T4500 -S0 -G1 -I35 -C0 -P2 -D output smallraw.RAW
33
27
 
34
-
35
28
  #config :group_mass_tol, 1.4, :short => 'M', &c.float # prec. mass tolerance for grouping
36
29
  #config :bottom_mw, 0.0, :short => 'B', &c.float # bottom MW for data file creation
37
30
  #config :top_mw, 999999.0, :short => 'T', &c.float # top MW for data file creation
data/lib/lmat.rb CHANGED
@@ -64,49 +64,65 @@ class Lmat
64
64
  self
65
65
  end
66
66
 
67
- # converts raw times and spectrum to a labeled matrix
68
- # times is an array (or VecI object)
69
- # where each row = [mz,inten,mz,inten...]
67
+ # converts msrun object to a labeled matrix
70
68
  # takes hash with symbols as keys
71
- # if inc_tm is undefined, then times from the times array will be used
72
- def from_times_and_spectra(times, spectra, args)
69
+ def from_msrun(msrun, args)
73
70
  opt = {
74
- :start_mz => 400.0,
75
- :end_mz => 1500.0,
76
71
  :inc_mz => 1.0,
77
72
  :behave_mz => 'sum',
78
-
79
- :start_tm => 0.0,
80
- :end_tm => 3600.0,
81
- :inc_tm => nil,
82
-
83
73
  :baseline=> 0.0,
74
+
75
+ #:start_tm => 0.0,
76
+ #:end_tm => 3600.0,
77
+ #:inc_tm => nil,
78
+
79
+ #:start_mz => 400.0,
80
+ #:end_mz => 1500.0,
84
81
  }
85
82
  opt.merge!(args)
86
- unless opt[:start_tm] then opt[:start_tm] = times.first end
87
- unless opt[:end_tm] then opt[:end_tm] = times.last end
88
-
83
+
84
+ (st, en) = msrun.start_and_end_mz
85
+ unless st && en
86
+ msg = ["scanning spectrum for start and end m/z values"]
87
+ msg << "(use :start_mz and :end_mz options to avoid this)"
88
+ warn msg
89
+ (st, en) = msrun.start_and_end_mz_brute_force
90
+ end
91
+ opt[:start_mz] ||= st
92
+ opt[:end_mz] ||= en
93
+
94
+ #unless opt[:start_tm] then opt[:start_tm] = times.first end
95
+ #unless opt[:end_tm] then opt[:end_tm] = times.last end
96
+
89
97
  if opt[:inc_tm]
90
98
  raise NotImplementedError, "haven't implemented interpolation in ruby yet! (#{File.basename(__FILE__)}: #{__LINE__})"
91
99
  else ## No interpolation
92
- if times.first != opt[:start_tm] || times.last != opt[:end_tm]
93
- abort "haven't implemented yet! (#{File.basename(__FILE__)}: #{__LINE__})"
94
- else
95
- @mvec = NArray.new(times)
96
- give_vecs = true
97
- vecs = spectra.map do |spectrum|
98
- #(mz,inten) = spectrum_to_mz_and_inten(spectrum, VecD)
99
- # TODO: Figure out a shallow copy here:
100
- # perhaps we'll make spectra Vec objects by default in future and
101
- # then we'd be set...
102
- mzs = NArray.new(spectrum.mzs)
103
- intens = NArray.new(spectrum.intensities)
104
- (x,y) = mzs.inc_x(intens, opt[:start_mz], opt[:end_mz], opt[:inc_mz], opt[:baseline], opt[:behave_mz])
105
- @nvec = x # ends up being the last one, but that's OK
106
- y
100
+ times = []
101
+ @nvec = nil
102
+ vecs = []
103
+ num_scans = msrun.scan_count
104
+ printf "Reading #{num_scans} spectra [.=100]" if $VERBOSE
105
+ spectrum_cnt = 0
106
+ msrun.each do |scan|
107
+ spectrum = scan.spectrum
108
+ times << scan.time
109
+ #(mz,inten) = spectrum_to_mz_and_inten(spectrum, VecD)
110
+ # TODO: Figure out a shallow copy here:
111
+ # perhaps we'll make spectra Vec objects by default in future and
112
+ # then we'd be set...
113
+ mzs = NArray.new(spectrum.mzs)
114
+ intens = NArray.new(spectrum.intensities)
115
+ (x,y) = mzs.inc_x(intens, opt[:start_mz], opt[:end_mz], opt[:inc_mz], opt[:baseline], opt[:behave_mz])
116
+ spectrum_cnt += 1
117
+ if spectrum_cnt % 100 == 0
118
+ printf "." if $VERBOSE ; $stdout.flush
107
119
  end
108
- @mat = vecs
120
+ @nvec ||= x # just need the first one for the x values
121
+ vecs << y
109
122
  end
123
+ puts "DONE!" if $VERBOSE
124
+ @mvec = NArray.new(times)
125
+ @mat = vecs
110
126
  end
111
127
  self
112
128
  end
@@ -124,28 +140,6 @@ class Lmat
124
140
  other != nil && self.class == other.class && @nvec == other.nvec && @mvec == other.mvec && @mat == other.mat
125
141
  end
126
142
 
127
- # converts a single array of alternating m/z intensity values to two
128
- # separate arrays
129
- # (maybe implement in Ruby::Inline?)
130
- # the answer is given in terms of arrs_as (object of class "arrs_as" must
131
- # respond to "[]" and create a certain sized array with arrs_as.new(size))
132
- def spectrum_to_mz_and_inten(spectrum, arrs_as=Array)
133
- half_size = spectrum.size / 2
134
- mzs = arrs_as.new(half_size)
135
- intens = arrs_as.new(half_size)
136
- mz = true
137
- spectrum.each_index do |i|
138
- if mz
139
- mzs[i/2] = spectrum[i]
140
- mz = false
141
- else
142
- mz = true
143
- intens[(i-1)/2] = spectrum[i]
144
- end
145
- end
146
- [mzs, intens]
147
- end
148
-
149
143
  def write(file=nil)
150
144
  handle = $>
151
145
  if file; handle = File.open(file, "wb") end
data/lib/ms/msrun.rb CHANGED
@@ -3,9 +3,15 @@ require 'ms/scan'
3
3
  require 'ms/precursor'
4
4
  require 'ms/spectrum'
5
5
  require 'ms/msrun/search'
6
+ require 'ms/msrun/index'
6
7
 
7
8
  module Ms; end
8
9
  class Ms::Msrun
10
+ include Enumerable
11
+
12
+ #DEFAULT_PARSER = 'axml'
13
+ #DEFAULT_PARSER = 'regexp'
14
+ DEFAULT_PARSER = 'nokogiri'
9
15
 
10
16
  # the retention time in seconds of the first scan (regardless of any
11
17
  # meta-data written in the header)
@@ -13,8 +19,6 @@ class Ms::Msrun
13
19
  # the retention time in seconds of the last scan (regardless of any
14
20
  # meta-data written in the header)
15
21
  attr_accessor :end_time
16
- # an array of scans
17
- attr_accessor :scans
18
22
 
19
23
  # The filetype. Valid types (for parsing) are:
20
24
  # :mzxml
@@ -39,6 +43,15 @@ class Ms::Msrun
39
43
  # this will be nil.
40
44
  attr_accessor :parent_location
41
45
 
46
+ # an array of doublets, [start_byte, length] for each scan element
47
+ attr_accessor :index
48
+
49
+ # holds the class that parses the file
50
+ attr_accessor :parser
51
+
52
+ # an array holding the scan numbers found in the run
53
+ attr_accessor :scan_nums
54
+
42
55
  # Opens the filename
43
56
  def self.open(filename, &block)
44
57
  File.open(filename) {|io| block.call( self.new(io, filename) ) }
@@ -50,10 +63,13 @@ class Ms::Msrun
50
63
  def initialize(io, filename=nil)
51
64
  @scan_counts = nil
52
65
  @filename = filename
53
- @filetype, @version = Utils.filetype_and_version(io)
54
- parser = Utils.get_parser(@filetype, @version)
55
- parser.new.parse(self, io, @version)
56
- @scan_counts = nil # <- to keep warnings away
66
+ @filetype, @version = Ms::Msrun.filetype_and_version(io)
67
+ parser_klass = Ms::Msrun.get_parser(@filetype, @version)
68
+
69
+ @parser = parser_klass.new(self, io, @version)
70
+ @index = Ms::Msrun::Index.new(io)
71
+ @scan_nums = @index.scan_nums
72
+ @parser.parse_header(@index.header_length)
57
73
  end
58
74
 
59
75
  def parent_basename_noext
@@ -61,32 +77,59 @@ class Ms::Msrun
61
77
  end
62
78
 
63
79
  # returns each scan
64
- def each(&block)
65
- scans.each(&block)
80
+ # options:
81
+ # :spectrum => true | false (default is true)
82
+ # :precursor => true | false (default is true)
83
+ # :ms_level => Integer or Array return only scans of that level
84
+ # :reverse => true | false (default is false) goes backwards
85
+ def each_scan(parse_opts={}, &block)
86
+ ms_levels =
87
+ if msl = parse_opts[:ms_level]
88
+ if msl.is_a?(Integer) ; [msl]
89
+ else ; msl
90
+ end
91
+ end
92
+ snums = @index.scan_nums
93
+ snums = snums.reverse if parse_opts[:reverse]
94
+ snums.each do |scan_num|
95
+ if ms_levels
96
+ next unless ms_levels.include?(ms_level(scan_num))
97
+ end
98
+ block.call(scan(scan_num, parse_opts))
99
+ end
66
100
  end
101
+ alias_method :each, :each_scan
67
102
 
68
103
  # opens the file and yields each scan in the block
69
- def self.foreach(filename, &block)
104
+ # see each_scan for parsing options
105
+ def self.foreach(filename, parse_opts={}, &block)
70
106
  self.open(filename) do |obj|
71
- obj.each(&block)
107
+ obj.each_scan(parse_opts, &block)
72
108
  end
73
109
  end
74
110
 
75
- def scans_by_ms_level
76
- by_level = []
77
- scans.each do |scan|
78
- by_level[scan.ms_level] = scan
79
- end
80
- by_level
111
+ # a very fast method to only query the ms_level of a scan
112
+ def ms_level(num)
113
+ @parser.parse_ms_level(@index[num].first, @index[num].last)
81
114
  end
82
115
 
116
+ # returns a Ms::Scan object for the scan at that number
117
+ #
118
+ def scan(num, parse_opts={})
119
+ #@parser.parse_scan(*(@index[num]), parse_opts)
120
+ @parser.parse_scan(@index[num].first, @index[num].last, parse_opts)
121
+ end
122
+
123
+ #bracket_method = '[]'.to_sym
124
+ #alias_method bracket_method, :scan
125
+
83
126
  # 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,
84
127
  # ...
85
128
  def scan_counts
86
129
  return @scan_counts if @scan_counts
87
130
  ar = []
88
131
  ar[0] = 0
89
- scans.each do |sc|
132
+ each_scan do |sc|
90
133
  level = sc.ms_level
91
134
  unless ar[level]
92
135
  ar[level] = 0
@@ -104,113 +147,57 @@ class Ms::Msrun
104
147
  if mslevel == 0
105
148
  @scan_count
106
149
  else
107
- num = 0
108
- scans.each do |sc|
109
- if sc.ms_level == mslevel
110
- num += 1
111
- end
112
- end
113
- num
150
+ scan_counts[mslevel]
114
151
  end
115
152
  end
116
153
  end
117
154
 
118
- # for level 1, finds first scan and asks if it has start_mz/end_mz
119
- # attributes. for other levels, asks for start_mz/ end_mz and takes the
120
- # min/max. If start_mz and end_mz are not found, goes through every scan
121
- # finding the max/min first and last m/z. returns [start_mz (rounded down to
122
- # nearest int), end_mz (rounded up to nearest int)]
123
- def start_and_end_mz(mslevel=1)
124
- if mslevel == 1
125
- # special case for mslevel 1 (where we expect scans to be same length)
126
- scans.each do |sc|
127
- if sc.ms_level == mslevel
128
- if sc.start_mz && sc.end_mz
129
- return [sc.start_mz, sc.end_mz]
130
- end
131
- break
132
- end
133
- end
134
- end
135
- hi_mz = nil
136
- lo_mz = nil
137
- # see if we have start_mz and end_mz for the level we want
138
- # set the initial hi_mz and lo_mz in any case
139
- have_start_end_mz = false
140
- scans.each do |sc|
141
- if sc.ms_level == mslevel
142
- if sc.start_mz && sc.end_mz
143
- lo_mz = sc.start_mz
144
- hi_mz = sc.end_mz
145
- else
146
- mz_ar = sc.spectrum.mzs
147
- hi_mz = mz_ar.last
148
- lo_mz = mz_ar.first
149
- end
150
- break
151
- end
152
- end
153
- if have_start_end_mz
154
- scans.each do |sc|
155
- if sc.ms_level == mslevel
156
- if sc.start_mz < lo_mz
157
- lo_mz = sc.start_mz
158
- end
159
- if sc.end_mz > hi_mz
160
- hi_mz = sc.end_mz
161
- end
162
- end
155
+
156
+ # returns [start_mz, end_mz] or [nil,nil] if unknown
157
+ def start_and_end_mz
158
+ scan = first(:ms_level => 1, :spectrum => false, :precursor => false)
159
+ [scan.start_mz, scan.end_mz]
160
+ end
161
+
162
+ # goes through every scan and gets the first and last m/z, then returns the
163
+ # max.ceil and min.floor
164
+ def start_and_end_mz_brute_force
165
+ first_scan = first(:ms_level => 1, :precursor => false)
166
+ first_mzs = first_scan.spectrum.mzs
167
+
168
+ lo_mz = first_mzs[0]
169
+ hi_mz = first_mzs[-1]
170
+
171
+ each_scan(:ms_level => 1, :precursor => false) do |sc|
172
+ mz_ar = sc.spectrum.mzs
173
+ if mz_ar.last > hi_mz
174
+ hi_mz = mz_ar.last
163
175
  end
164
- else
165
- # didn't have the attributes (find by brute force)
166
- scans.each do |sc|
167
- if sc.ms_level == mslevel
168
- mz_ar = sc.spectrum.mzs
169
- if mz_ar.last > hi_mz
170
- hi_mz = mz_ar.last
171
- end
172
- if mz_ar.last < lo_mz
173
- lo_mz = mz_ar.last
174
- end
175
- end
176
+ if mz_ar.last < lo_mz
177
+ lo_mz = mz_ar.last
176
178
  end
177
179
  end
178
180
  [lo_mz.floor, hi_mz.ceil]
179
181
  end
180
182
 
181
- # returns an array of times and parallel array of spectra objects.
182
- # ms_level = 0 then all spectra and times
183
- # ms_level = 1 then all spectra of ms_level 1
184
- def times_and_spectra(ms_level=0)
185
- spectra = []
186
- if ms_level == 0
187
- times = @scans.map do |scan|
188
- spectra << scan.spectrum
189
- scan.time
190
- end
191
- [times, spectra]
192
- else # choose a particular ms_level
193
- times = []
194
- @scans.each do |scan|
195
- if ms_level == scan.ms_level
196
- spectra << scan.spectrum
197
- times << scan.time
198
- end
199
- end
200
- [times, spectra]
183
+ def first(opts={})
184
+ the_first = nil
185
+ each_scan(opts) do |scan|
186
+ the_first = scan
187
+ break
201
188
  end
189
+ the_first
202
190
  end
203
- end
204
-
205
191
 
206
- module Ms::Msrun::Axml ; end # so we can get our parser
207
-
208
- module Ms::Msrun::Utils
192
+ def last(opts={})
193
+ opts[:reverse] = true
194
+ first(opts)
195
+ end
209
196
 
210
197
  def self.get_parser(filetype, version)
211
- require "ms/msrun/axml/#{filetype}"
198
+ require "ms/msrun/#{DEFAULT_PARSER}/#{filetype}"
212
199
  parser_class = filetype.to_s.capitalize
213
- base_class = Ms::Msrun::Axml
200
+ base_class = Ms::Msrun.const_get( DEFAULT_PARSER.capitalize )
214
201
  if base_class.const_defined? parser_class
215
202
  base_class.const_get parser_class
216
203
  else
@@ -247,6 +234,7 @@ module Ms::Msrun::Utils
247
234
  end
248
235
  end
249
236
 
237
+
250
238
  Mzxml_regexp = /http:\/\/sashimi.sourceforge.net\/schema(_revision)?\/([\w\d_\.]+)/o
251
239
  # 'http://sashimi.sourceforge.net/schema/MsXML.xsd' # version 1
252
240
  # 'http://sashimi.sourceforge.net/schema_revision/mzXML_X.X' # others
@@ -258,6 +246,7 @@ module Ms::Msrun::Utils
258
246
  if file_or_io.is_a? IO
259
247
  io = file_or_io
260
248
  found = nil
249
+ io.rewind
261
250
  # Test for RAW file:
262
251
  header = io.read(18).unpack(Raw_header_unpack_code).join
263
252
  if header == 'Finnigan'
@@ -294,4 +283,5 @@ module Ms::Msrun::Utils
294
283
  end
295
284
  end
296
285
  end
286
+
297
287
  end