fcsparse 0.1.0
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.
- data/lib/fcsparse/fcsconst.rb +93 -0
- data/lib/fcsparse/fcsevent.rb +215 -0
- data/lib/fcsparse.rb +363 -0
- metadata +68 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
#--
|
2
|
+
# /* ***** BEGIN LICENSE BLOCK *****
|
3
|
+
# *
|
4
|
+
# * Copyright (c) 2012 Colin J. Fuller
|
5
|
+
# *
|
6
|
+
# * Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# * of this software and associated documentation files (the Software), to deal
|
8
|
+
# * in the Software without restriction, including without limitation the rights
|
9
|
+
# * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# * copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# * furnished to do so, subject to the following conditions:
|
12
|
+
# *
|
13
|
+
# * The above copyright notice and this permission notice shall be included in
|
14
|
+
# * all copies or substantial portions of the Software.
|
15
|
+
# *
|
16
|
+
# * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
# * SOFTWARE.
|
23
|
+
# *
|
24
|
+
# * ***** END LICENSE BLOCK ***** */
|
25
|
+
#++
|
26
|
+
|
27
|
+
##
|
28
|
+
# Contains all classes and constants for parsing FCS v3.x formatted files.
|
29
|
+
#
|
30
|
+
module FCSParse
|
31
|
+
|
32
|
+
#
|
33
|
+
# Contains constants associated with the fcs file format, such as byte offsets
|
34
|
+
# and parameter names.
|
35
|
+
#
|
36
|
+
# The first character of each constant name specifies the section of the fcs file
|
37
|
+
# to which it is relevant. (H = header, T = text, etc.)
|
38
|
+
#
|
39
|
+
# @author Colin J. Fuller
|
40
|
+
#
|
41
|
+
|
42
|
+
#byte offsets to the start and end of the version string
|
43
|
+
H_VersionStart = 0
|
44
|
+
H_VersionEnd = 5
|
45
|
+
|
46
|
+
#length of the block specifying the offsets of text, data, analysis sections
|
47
|
+
H_OffsetBlockLength = 8
|
48
|
+
|
49
|
+
#offsets to the start and end of the text section offset
|
50
|
+
H_TextBlockOffsetStart = 10
|
51
|
+
H_TextBlockOffsetEnd = 18
|
52
|
+
|
53
|
+
#offsets to the start and end of the data section offset
|
54
|
+
H_DataBlockOffsetStart = 26
|
55
|
+
H_DataBlockOffsetEnd = 34
|
56
|
+
|
57
|
+
#offsets to the start and end of the analysis section offset
|
58
|
+
H_AnalysisBlockOffsetStart = 42
|
59
|
+
H_AnalysisBlockOffsetEnd = 50
|
60
|
+
|
61
|
+
#keyword specifying offset to supplementary text section
|
62
|
+
T_SupplTextStartKeyword = :$BEGINSTEXT
|
63
|
+
T_SupplTextEndKeyword = :$ENDSTEXT
|
64
|
+
|
65
|
+
#keyword specifying offset to analysis section
|
66
|
+
T_AnalysisStartKeyword = :$BEGINANALYSIS
|
67
|
+
T_AnalysisEndKeyword = :$BEGINANALYSIS
|
68
|
+
|
69
|
+
#keyword specifying offset to data section
|
70
|
+
T_DataStartKeyword = :$BEGINDATA
|
71
|
+
T_DataEndKeyword = :$ENDDATA
|
72
|
+
|
73
|
+
#keyword specifying the data mode
|
74
|
+
T_ModeKeyword = :$MODE
|
75
|
+
|
76
|
+
#keyword specifying the data type (e.g. integer, float, etc)
|
77
|
+
T_DatatypeKeyword = :$DATATYPE
|
78
|
+
|
79
|
+
#keyword specifying byte order and value when little endian
|
80
|
+
T_ByteorderKeyword = :$BYTEORD
|
81
|
+
T_LittleEndianByteorder = "1,2,3,4"
|
82
|
+
|
83
|
+
#keyword specifying the number of parameters measured per event
|
84
|
+
T_ParameterCountKeyword = :$PAR
|
85
|
+
|
86
|
+
#keyword specifying total number of events
|
87
|
+
T_EventCountKeyword = :$TOT
|
88
|
+
|
89
|
+
#regular expressions matching names and ranges of all parameters
|
90
|
+
T_ParameterNameKeywordRegex = /P(\d+)N/
|
91
|
+
T_ParameterRangeKeywordRegex = /P(\d+)R/
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
#--
|
2
|
+
# /* ***** BEGIN LICENSE BLOCK *****
|
3
|
+
# *
|
4
|
+
# * Copyright (c) 2012 Colin J. Fuller
|
5
|
+
# *
|
6
|
+
# * Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# * of this software and associated documentation files (the Software), to deal
|
8
|
+
# * in the Software without restriction, including without limitation the rights
|
9
|
+
# * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# * copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# * furnished to do so, subject to the following conditions:
|
12
|
+
# *
|
13
|
+
# * The above copyright notice and this permission notice shall be included in
|
14
|
+
# * all copies or substantial portions of the Software.
|
15
|
+
# *
|
16
|
+
# * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
# * SOFTWARE.
|
23
|
+
# *
|
24
|
+
# * ***** END LICENSE BLOCK ***** */
|
25
|
+
#++
|
26
|
+
|
27
|
+
require 'fcsparse/fcsconst'
|
28
|
+
|
29
|
+
##
|
30
|
+
# Contains all classes and constants for parsing FCS v3.x formatted files.
|
31
|
+
#
|
32
|
+
module FCSParse
|
33
|
+
|
34
|
+
##
|
35
|
+
# Class representing a single parameter and its value in a single event.
|
36
|
+
#
|
37
|
+
# @author Colin J. Fuller
|
38
|
+
#
|
39
|
+
class FCSParam
|
40
|
+
|
41
|
+
##
|
42
|
+
# Create a new parameter with specified information
|
43
|
+
#
|
44
|
+
# @param name the name of the parameter
|
45
|
+
# @param description a longer description of the parameter
|
46
|
+
# @param value the value of the parameter
|
47
|
+
# @param limit the maximum value that the parameter can take
|
48
|
+
#
|
49
|
+
def initialize(name, description, value, limit)
|
50
|
+
|
51
|
+
@name = name
|
52
|
+
@description = description
|
53
|
+
@value = value
|
54
|
+
@limit = limit
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_accessor :name, :description, :value, :limit
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
##
|
64
|
+
# Class representing a single FCS-encoded event.
|
65
|
+
#
|
66
|
+
# @author Colin J. Fuller
|
67
|
+
#
|
68
|
+
class FCSEvent
|
69
|
+
|
70
|
+
#default delimiter for printing output
|
71
|
+
DefaultDelimiter = ","
|
72
|
+
|
73
|
+
private_class_method :new
|
74
|
+
|
75
|
+
def initialize
|
76
|
+
|
77
|
+
@values = Hash.new
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
##
|
83
|
+
# Creates a new FCSEvent from the specified information.
|
84
|
+
#
|
85
|
+
# @param [String] event_data_string the raw data corresponding to the
|
86
|
+
# entire event from the fcs file
|
87
|
+
# @param [String] event_format_string a string suitable for use with String#unpack
|
88
|
+
# that can decode the raw data.
|
89
|
+
# @param [Hash] parameter_info_hash a hash containing at minimum the parameters
|
90
|
+
# from the text section specifying the names and ranges of the parameters
|
91
|
+
# keys should be the parameter names from the fcs file format converted
|
92
|
+
# to symbols ($ included), and values should be a string corresponding
|
93
|
+
# to the value of the parameter from the fcs file.
|
94
|
+
# @return [FCSEvent] an FCSEvent that has been created by parsing the raw data.
|
95
|
+
#
|
96
|
+
def self.new_with_data_and_format(event_data_string, event_format_string, parameter_info_hash)
|
97
|
+
|
98
|
+
data_points = event_data_string.unpack(event_format_string)
|
99
|
+
|
100
|
+
parameter_names = Hash.new
|
101
|
+
parameter_limits = Hash.new
|
102
|
+
|
103
|
+
parameter_info_hash.each_key do |k|
|
104
|
+
|
105
|
+
matchobj = k.to_s.match(T_ParameterNameKeywordRegex)
|
106
|
+
|
107
|
+
|
108
|
+
if matchobj then
|
109
|
+
|
110
|
+
parameter_names[matchobj[1].to_i] = parameter_info_hash[k]
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
matchobj = k.to_s.match(T_ParameterRangeKeywordRegex)
|
116
|
+
|
117
|
+
if matchobj then
|
118
|
+
|
119
|
+
parameter_limits[matchobj[1].to_i] = parameter_info_hash[k]
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
ordered_indices = parameter_names.keys.sort
|
126
|
+
|
127
|
+
event = new
|
128
|
+
|
129
|
+
data_points.each_with_index do |e, i|
|
130
|
+
|
131
|
+
param = FCSParam.new(parameter_names[ordered_indices[i]], nil, e, parameter_limits[ordered_indices[i]])
|
132
|
+
|
133
|
+
event[param.name] = param
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
event
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# Gets a named parameter associated with the event.
|
143
|
+
#
|
144
|
+
# @param [String] parameter_name the name of the parameter to retrieve; this should be
|
145
|
+
# exactly the name specified for the parameter in the text
|
146
|
+
# section of the fcs file
|
147
|
+
# @return [FCSParam] an FCSParam object that holds the information about the named parameter.
|
148
|
+
#
|
149
|
+
def [](parameter_name)
|
150
|
+
|
151
|
+
@values[parameter_name]
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Sets a named parameter associated with the event.
|
157
|
+
# @param [String] parameter_name the name of the parameter to retrieve; this should be
|
158
|
+
# exactly the name specified for the parameter in the text
|
159
|
+
# section of the fcs file
|
160
|
+
# @param [FCSParam] value an FCSParam object that holds the information about the named parameter.
|
161
|
+
#
|
162
|
+
def []=(parameter_name, value)
|
163
|
+
|
164
|
+
@values[parameter_name]= value
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# Gets the names of the parameters associated with this event in alphabetical
|
170
|
+
# order as a string, delimited by the supplied delimiter.
|
171
|
+
#
|
172
|
+
# @param [String] delimiter a String containing the desired delimiter.
|
173
|
+
# @return [String] a String containing delimited alphabetized parameter names.
|
174
|
+
#
|
175
|
+
def names_to_s_delim(delimiter)
|
176
|
+
|
177
|
+
all_param_names = @values.keys.sort
|
178
|
+
|
179
|
+
all_param_names.join(delimiter)
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
##
|
184
|
+
# Gets the values of the parameters associated with this event ordered
|
185
|
+
# alphabetically by the parameter names (i.e. in the same order as when calling
|
186
|
+
# {#names_to_s_delim}), delimited by the supplied delimiter.
|
187
|
+
#
|
188
|
+
# @param [String]delimiter a String containing the desired delimiter.
|
189
|
+
# @return [String] a String containing delimited ordered parameter values.
|
190
|
+
#
|
191
|
+
def to_s_delim(delimiter)
|
192
|
+
|
193
|
+
all_param_names = @values.keys.sort
|
194
|
+
|
195
|
+
all_param_values = all_param_names.map{|e| @values[e].value}
|
196
|
+
|
197
|
+
all_param_values.join(delimiter)
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
##
|
202
|
+
# Converts the event to a string representation. This is the same as calling
|
203
|
+
# {#to_s_delim} with the delimiter set to {FCSEvent::DefaultDelimiter}.
|
204
|
+
#
|
205
|
+
# @return [String] a String containing delimited ordered parameter values.
|
206
|
+
#
|
207
|
+
def to_s
|
208
|
+
|
209
|
+
to_s_delim(DefaultDelimiter)
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
data/lib/fcsparse.rb
ADDED
@@ -0,0 +1,363 @@
|
|
1
|
+
#--
|
2
|
+
# /* ***** BEGIN LICENSE BLOCK *****
|
3
|
+
# *
|
4
|
+
# * Copyright (c) 2012 Colin J. Fuller
|
5
|
+
# *
|
6
|
+
# * Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# * of this software and associated documentation files (the Software), to deal
|
8
|
+
# * in the Software without restriction, including without limitation the rights
|
9
|
+
# * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# * copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# * furnished to do so, subject to the following conditions:
|
12
|
+
# *
|
13
|
+
# * The above copyright notice and this permission notice shall be included in
|
14
|
+
# * all copies or substantial portions of the Software.
|
15
|
+
# *
|
16
|
+
# * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
# * SOFTWARE.
|
23
|
+
# *
|
24
|
+
# * ***** END LICENSE BLOCK ***** */
|
25
|
+
#++
|
26
|
+
|
27
|
+
require 'fcsparse/fcsconst'
|
28
|
+
require 'fcsparse/fcsevent'
|
29
|
+
|
30
|
+
##
|
31
|
+
# Contains all classes and constants for parsing FCS v3.x formatted files.
|
32
|
+
#
|
33
|
+
module FCSParse
|
34
|
+
|
35
|
+
##
|
36
|
+
# A class representing an FCS-encoded file. Has methods to parse the data,
|
37
|
+
# manage it, and convert it to human-readable format.
|
38
|
+
#
|
39
|
+
# Currently reads FCS v3.x files. Files must have data encoded in list mode,
|
40
|
+
# and data points must be in float or double format (this will change eventually to support
|
41
|
+
# all formats in the specification).
|
42
|
+
#
|
43
|
+
# Analysis sections of the file are ignored.
|
44
|
+
#
|
45
|
+
# Data can be written to delimited text files, and metadata to plain text files,
|
46
|
+
# or the objects containing the data can be used by other code to process
|
47
|
+
# the data further.
|
48
|
+
#
|
49
|
+
# @author Colin J. Fuller
|
50
|
+
#
|
51
|
+
class FCSFile
|
52
|
+
|
53
|
+
MetadataExtension = ".meta.txt"
|
54
|
+
DataExtension = ".data.csv"
|
55
|
+
DataDelimiter = FCSEvent::DefaultDelimiter
|
56
|
+
SupportedVersions = ["FCS3.0", "FCS3.1"]
|
57
|
+
|
58
|
+
attr_accessor :filename, :events
|
59
|
+
|
60
|
+
|
61
|
+
##
|
62
|
+
# Generates a new FCSFile object from the specified file.
|
63
|
+
#
|
64
|
+
# Calling this will read the entire file into memory but will not parse it.
|
65
|
+
#
|
66
|
+
# @param [String] filename the filename of the FCS-encoded file (with path as required to locate it)
|
67
|
+
# @return [FCSFile] a new FCSFile object initialized with the contents of the file.
|
68
|
+
#
|
69
|
+
def self.new_from_file(filename)
|
70
|
+
|
71
|
+
fcsfile = nil
|
72
|
+
|
73
|
+
File.open(filename) do |f|
|
74
|
+
fcsfile = self.new(f.read)
|
75
|
+
end
|
76
|
+
|
77
|
+
fcsfile.filename = filename
|
78
|
+
|
79
|
+
fcsfile
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Generates a new FCSFile from the specified FCS-encoded data string.
|
85
|
+
#
|
86
|
+
# @param [String] file_string a String containing an FCS-encoded dataset.
|
87
|
+
def initialize(file_string)
|
88
|
+
@data = file_string
|
89
|
+
@keywords = Hash.new
|
90
|
+
end
|
91
|
+
|
92
|
+
def read_header
|
93
|
+
|
94
|
+
version_string= @data[H_VersionStart..H_VersionEnd]
|
95
|
+
|
96
|
+
unless SupportedVersions.include?(version_string) then
|
97
|
+
raise "Unable to read this FCS format: " + version_string
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
@version = version_string[3..5].to_f
|
102
|
+
|
103
|
+
@text_offsets = Array.new
|
104
|
+
@data_offsets = Array.new
|
105
|
+
@analysis_offsets = Array.new
|
106
|
+
|
107
|
+
@text_offsets << @data[H_TextBlockOffsetStart, H_OffsetBlockLength].to_i
|
108
|
+
@text_offsets << @data[H_TextBlockOffsetEnd, H_OffsetBlockLength].to_i
|
109
|
+
|
110
|
+
@data_offsets << @data[H_DataBlockOffsetStart, H_OffsetBlockLength].to_i
|
111
|
+
@data_offsets << @data[H_DataBlockOffsetEnd, H_OffsetBlockLength].to_i
|
112
|
+
|
113
|
+
@analysis_offsets << @data[H_AnalysisBlockOffsetStart, H_OffsetBlockLength].to_i
|
114
|
+
@analysis_offsets << @data[H_AnalysisBlockOffsetEnd, H_OffsetBlockLength].to_i
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
def parse_text_segment_with_bounds(lower_bound, upper_bound)
|
119
|
+
|
120
|
+
token_queue = Array.new
|
121
|
+
|
122
|
+
delimiter = @data[lower_bound]
|
123
|
+
|
124
|
+
token_start = lower_bound + 1
|
125
|
+
|
126
|
+
(lower_bound+1).upto(upper_bound) do |i|
|
127
|
+
|
128
|
+
if @data[i] == delimiter and not @data[i-1] == delimiter and not @data[i+1] == delimiter then
|
129
|
+
|
130
|
+
token_queue << @data[token_start...i]
|
131
|
+
token_start = i+1
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
token_queue.each_slice(2) do |kv|
|
138
|
+
|
139
|
+
@keywords[kv[0].upcase.to_sym]= kv[1]
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
def parse_text_segment
|
148
|
+
|
149
|
+
parse_text_segment_with_bounds(@text_offsets[0].to_i, @text_offsets[1].to_i)
|
150
|
+
|
151
|
+
suppl_text_start = @keywords[T_SupplTextStartKeyword].to_i
|
152
|
+
suppl_text_end = @keywords[T_SupplTextEndKeyword].to_i
|
153
|
+
|
154
|
+
unless suppl_text_start == 0 and suppl_text_end == 0 then
|
155
|
+
parse_text_segment_with_bounds(suppl_text_start, suppl_text_end)
|
156
|
+
end
|
157
|
+
|
158
|
+
@data_offsets[0] = @keywords[T_DataStartKeyword].to_i
|
159
|
+
@data_offsets[1] = @keywords[T_DataEndKeyword].to_i
|
160
|
+
|
161
|
+
@analysis_offsets[0] = @keywords[T_AnalysisStartKeyword].to_i
|
162
|
+
@analysis_offsets[1] = @keywords[T_AnalysisEndKeyword].to_i
|
163
|
+
|
164
|
+
unless @keywords[T_ModeKeyword] == "L" then
|
165
|
+
raise "Only list mode is supported for the data block."
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
def parse_data_segment
|
171
|
+
|
172
|
+
event_format = construct_event_format_string
|
173
|
+
|
174
|
+
event_count = @keywords[T_EventCountKeyword].to_i
|
175
|
+
|
176
|
+
bytes_per_event = ((@data_offsets[1] - @data_offsets[0] + 1)*1.0/event_count).to_i
|
177
|
+
|
178
|
+
@events = Array.new
|
179
|
+
|
180
|
+
0.upto(event_count -1) do |e|
|
181
|
+
|
182
|
+
offset = @data_offsets[0] + bytes_per_event*e
|
183
|
+
|
184
|
+
event_string = @data[offset, bytes_per_event]
|
185
|
+
|
186
|
+
event = FCSEvent.new_with_data_and_format(event_string, event_format, @keywords)
|
187
|
+
|
188
|
+
@events << event
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
def construct_event_format_string
|
195
|
+
|
196
|
+
datatype = @keywords[T_DatatypeKeyword]
|
197
|
+
|
198
|
+
is_little_endian = (@keywords[T_ByteorderKeyword] == T_LittleEndianByteorder)
|
199
|
+
|
200
|
+
formatchar = case datatype
|
201
|
+
|
202
|
+
when 'I'
|
203
|
+
|
204
|
+
nil
|
205
|
+
|
206
|
+
when 'F'
|
207
|
+
|
208
|
+
if is_little_endian then
|
209
|
+
|
210
|
+
'e'
|
211
|
+
|
212
|
+
else
|
213
|
+
|
214
|
+
'g'
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
when 'D'
|
219
|
+
|
220
|
+
if is_little_endian then
|
221
|
+
|
222
|
+
'E'
|
223
|
+
|
224
|
+
else
|
225
|
+
|
226
|
+
'G'
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
when 'A'
|
231
|
+
|
232
|
+
nil
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
unless formatchar then
|
237
|
+
raise "Integer and string data not yet supported."
|
238
|
+
end
|
239
|
+
|
240
|
+
parameter_count = @keywords[T_ParameterCountKeyword].to_i
|
241
|
+
|
242
|
+
formatchar*parameter_count
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
##
|
247
|
+
# Gets the metadata (that is all the information in the text block of the
|
248
|
+
# fcs file) as a string, where there is one key, value pair per line, separated
|
249
|
+
# by the characters " => "
|
250
|
+
#
|
251
|
+
# @return [String] a String containing all the metadata
|
252
|
+
def get_metadata_string
|
253
|
+
str = ""
|
254
|
+
@keywords.keys.map{|e| e.to_s}.sort.each do |k|
|
255
|
+
str << k.to_s + " => " + @keywords[k.to_sym].to_s + "\n"
|
256
|
+
end
|
257
|
+
str
|
258
|
+
end
|
259
|
+
|
260
|
+
##
|
261
|
+
# Prints the metadata string returned by {#get_metadata_string}.
|
262
|
+
#
|
263
|
+
def print_metadata
|
264
|
+
|
265
|
+
puts get_metadata_string
|
266
|
+
|
267
|
+
nil
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
##
|
272
|
+
# Writes the metadata and data from the FCS-formatted file to disk in human
|
273
|
+
# readable format.
|
274
|
+
#
|
275
|
+
# The metadata is written as a text file (formatted by
|
276
|
+
# {#get_metadata_string}), in the same location as the input FCS file and
|
277
|
+
# with the same name except for an extra extension specified in
|
278
|
+
# {FCSFile::MetadataExtension}.
|
279
|
+
#
|
280
|
+
# The data, delimited by the data delimiter specified
|
281
|
+
# as {FCSFile::DataDelimiter}, one row per event, is written to a text file
|
282
|
+
# in the same location as the input FCS file and with the same name except
|
283
|
+
# for an extra extension specified in {FCSFile::DataExtension}.
|
284
|
+
#
|
285
|
+
# @param [Boolean] write_header_row an optional parameter specifying whether
|
286
|
+
# the data file should have a header row with the name of each column's
|
287
|
+
# parameter. Defaults to true.
|
288
|
+
#
|
289
|
+
def write_metadata_and_data(write_header_row = true)
|
290
|
+
|
291
|
+
meta_filename = @filename + MetadataExtension
|
292
|
+
|
293
|
+
data_filename = @filename + DataExtension
|
294
|
+
|
295
|
+
File.open(meta_filename, "w") do |f|
|
296
|
+
|
297
|
+
f.write(get_metadata_string)
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
File.open(data_filename, "w") do |f|
|
302
|
+
|
303
|
+
if write_header_row then
|
304
|
+
f.puts(@events[0].names_to_s_delim(DataDelimiter))
|
305
|
+
end
|
306
|
+
|
307
|
+
@events.each do |e|
|
308
|
+
|
309
|
+
f.puts(e.to_s_delim(DataDelimiter))
|
310
|
+
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
314
|
+
|
315
|
+
nil
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
##
|
320
|
+
# Parses the raw data loaded in the FCSFile to metadata, events, and parameters.
|
321
|
+
#
|
322
|
+
# Erases the raw data from the FCSFile object after parsing is complete.
|
323
|
+
#
|
324
|
+
def parse
|
325
|
+
read_header
|
326
|
+
parse_text_segment
|
327
|
+
parse_data_segment
|
328
|
+
@data = nil
|
329
|
+
end
|
330
|
+
|
331
|
+
private :read_header, :parse_text_segment_with_bounds, :parse_text_segment, :parse_data_segment, :construct_event_format_string
|
332
|
+
|
333
|
+
end
|
334
|
+
|
335
|
+
|
336
|
+
##
|
337
|
+
# Processes a specified FCS-formatted file, and writes human-readable output
|
338
|
+
# to disk in the format specified by {FCSFile#write_metadata_and_data}.
|
339
|
+
#
|
340
|
+
# @param [String] filename the filename of the FCS-encoded file (with path as required to locate it)
|
341
|
+
# @param [Boolean] data_header_row an optional parameter specifying whether
|
342
|
+
# the data file should have a header row with the name of each column's
|
343
|
+
# parameter. Defaults to true.
|
344
|
+
#
|
345
|
+
def self.process_file(filename, data_header_row = true)
|
346
|
+
|
347
|
+
fcsfile = FCSFile.new_from_file(filename)
|
348
|
+
fcsfile.parse
|
349
|
+
fcsfile.write_metadata_and_data(data_header_row)
|
350
|
+
|
351
|
+
nil
|
352
|
+
|
353
|
+
end
|
354
|
+
|
355
|
+
|
356
|
+
|
357
|
+
end
|
358
|
+
|
359
|
+
if __FILE__ == $0 then
|
360
|
+
|
361
|
+
FCSParse.process_file(ARGV[0], false)
|
362
|
+
|
363
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fcsparse
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 1322991910812937300
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Colin J. Fuller
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-06-18 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Parses flow cytometry FCS v3.x files and allows output to plain (delimited) text.
|
22
|
+
email: cjfuller@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- lib/fcsparse.rb
|
31
|
+
- lib/fcsparse/fcsconst.rb
|
32
|
+
- lib/fcsparse/fcsevent.rb
|
33
|
+
homepage: http://code.google.com/p/fcsparse/
|
34
|
+
licenses:
|
35
|
+
- MIT
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 2002549777813010636
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
version: "0"
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 2002549777813010636
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.8.12
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Parser for FCS v3.x file format
|
66
|
+
test_files: []
|
67
|
+
|
68
|
+
has_rdoc:
|