brainmap-ImageData 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ImageData.gemspec +32 -0
- data/LICENSE +3 -0
- data/Manifest +15 -0
- data/README.rdoc +43 -0
- data/Rakefile +18 -0
- data/lib/CLUs/import_study.rb +161 -0
- data/lib/CLUs/import_visit.rb +73 -0
- data/lib/mysql_tools.rb +33 -0
- data/lib/raw_image_dataset.rb +131 -0
- data/lib/raw_image_file.rb +411 -0
- data/lib/series_description.rb +81 -0
- data/lib/visit_raw_data_directory.rb +356 -0
- data/test/raw_image_dataset_test.rb +46 -0
- data/test/raw_image_file_test.rb +135 -0
- data/test/visit_duplication_test.rb +24 -0
- data/test/visit_test.rb +77 -0
- metadata +85 -0
@@ -0,0 +1,411 @@
|
|
1
|
+
|
2
|
+
require 'rubygems';
|
3
|
+
require 'yaml';
|
4
|
+
require 'sqlite3';
|
5
|
+
|
6
|
+
=begin rdoc
|
7
|
+
Implements a collection of metadata associated with a raw image file. In
|
8
|
+
this case, by image we mean one single file. For the case of Pfiles one file
|
9
|
+
corresponds to a complete 4D data set. For dicoms one file corresponds to a single
|
10
|
+
2D slice, many of which are assembled later during reconstruction to create a
|
11
|
+
4D data set. The motivation for this class is to provide access to the metadata
|
12
|
+
stored in image file headers so that they can be later reconstructed into nifti
|
13
|
+
data sets.
|
14
|
+
=end
|
15
|
+
class RawImageFile
|
16
|
+
#:stopdoc:
|
17
|
+
MIN_HDR_LENGTH = 400
|
18
|
+
DICOM_HDR = "dicom_hdr"
|
19
|
+
RDGEHDR = "rdgehdr"
|
20
|
+
MONTHS = {
|
21
|
+
:jan => "01", :feb => "02", :mar => "03", :apr => "04", :may => "05",
|
22
|
+
:jun => "06", :jul => "07", :aug => "08", :sep => "09", :oct => "10",
|
23
|
+
:nov => "11", :dec => "12"
|
24
|
+
}
|
25
|
+
#:startdoc:
|
26
|
+
|
27
|
+
# The file name that the instance represents.
|
28
|
+
attr_reader :filename
|
29
|
+
# Which header reading utility reads this file, currently 'rdgehdr' or 'dicom_hdr'.
|
30
|
+
attr_reader :hdr_reader
|
31
|
+
# File types are either 'dicom' or 'pfile'.
|
32
|
+
attr_reader :file_type
|
33
|
+
# The date on which this scan was acquired, this is a ruby DateTime object.
|
34
|
+
attr_reader :timestamp
|
35
|
+
# The scanner used to perform this scan, e.g. 'Andys3T'.
|
36
|
+
attr_reader :source
|
37
|
+
# An identifier unique to a 'visit', these are assigned by the scanner techs at scan time.
|
38
|
+
attr_reader :rmr_number
|
39
|
+
# A short string describing the acquisition sequence. These come from the scanner.
|
40
|
+
# code and are used to initialise SeriesDescription objects to find related attributes.
|
41
|
+
attr_reader :series_description
|
42
|
+
# M or F.
|
43
|
+
attr_reader :gender
|
44
|
+
# Number of slices in the data set that includes this file, used by AFNI for reconstruction.
|
45
|
+
attr_reader :num_slices
|
46
|
+
# Given in millimeters.
|
47
|
+
attr_reader :slice_thickness
|
48
|
+
# Gap between slices in millimeters.
|
49
|
+
attr_reader :slice_spacing
|
50
|
+
# AKA Field of View, in millimeters.
|
51
|
+
attr_reader :reconstruction_diameter
|
52
|
+
# Voxels in x axis.
|
53
|
+
attr_reader :acquisition_matrix_x
|
54
|
+
# Voxels in y axis.
|
55
|
+
attr_reader :acquisition_matrix_y
|
56
|
+
# Time for each bold repetition, relevent for functional scans.
|
57
|
+
attr_reader :rep_time
|
58
|
+
# Number of bold reps in the complete functional task run.
|
59
|
+
attr_reader :bold_reps
|
60
|
+
|
61
|
+
=begin rdoc
|
62
|
+
Creates a new instance of the class given a path to a valid image file.
|
63
|
+
|
64
|
+
Throws IOError if the file given is not found or if the available header reading
|
65
|
+
utilities cannot read the image header. Also raises IOError if any of the
|
66
|
+
attributes cannot be found in the header. Be aware that the filename used to
|
67
|
+
initialize your instance is used to set the "file" attribute. If you need to
|
68
|
+
unzip a file to a temporary location, be sure to keep the same filename for the
|
69
|
+
temporary file.
|
70
|
+
=end
|
71
|
+
def initialize(pathtofile)
|
72
|
+
|
73
|
+
# raise an error if the file doesn't exist
|
74
|
+
absfilepath = File.expand_path(pathtofile)
|
75
|
+
raise(IOError, "File not found.") if not File.exists?(absfilepath)
|
76
|
+
@filename = File.basename(absfilepath)
|
77
|
+
|
78
|
+
# try to read the header, raise an ioerror if unsuccessful
|
79
|
+
@hdr_data, @hdr_reader = read_header(absfilepath)
|
80
|
+
raise(IOError, "Header not readable.") if @hdr_reader.nil?
|
81
|
+
|
82
|
+
# file type is based on file name but only if the header was read successfully
|
83
|
+
@file_type = determine_file_type
|
84
|
+
|
85
|
+
# try to import attributes from the header, raise an ioerror if any attributes
|
86
|
+
# are not found
|
87
|
+
begin
|
88
|
+
import_hdr
|
89
|
+
rescue
|
90
|
+
raise(IOError, "Header import failed.")
|
91
|
+
end
|
92
|
+
|
93
|
+
# deallocate the header data to save memory space.
|
94
|
+
@hdr_data = nil
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
=begin rdoc
|
99
|
+
Predicate method that tells whether or not the file is actually an image. This
|
100
|
+
judgement is based on whether one of the available header reading utilities can
|
101
|
+
actually read the header information.
|
102
|
+
=end
|
103
|
+
def image?
|
104
|
+
return ( @hdr_reader == RDGEHDR or @hdr_reader == DICOM_HDR )
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
=begin rdoc
|
109
|
+
Predicate simply returns true if "pfile" is stored in the @img_type instance variable.
|
110
|
+
=end
|
111
|
+
def pfile?
|
112
|
+
return @file_type == "pfile"
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
=begin rdoc
|
117
|
+
Predicate simply returns true if "dicom" is stored in the img_type instance variable.
|
118
|
+
=end
|
119
|
+
def dicom?
|
120
|
+
return @file_type == "dicom"
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
=begin rdoc
|
125
|
+
Returns a yaml string based on a subset of the attributes. Specifically,
|
126
|
+
the @hdr_data is not included. This is used to generate .yaml files that are
|
127
|
+
placed in image directories for later scanning by YamlScanner.
|
128
|
+
=end
|
129
|
+
def to_yaml
|
130
|
+
yamlhash = {}
|
131
|
+
instance_variables.each do |var|
|
132
|
+
yamlhash[var[1..-1]] = instance_variable_get(var) if (var != "@hdr_data")
|
133
|
+
end
|
134
|
+
return yamlhash.to_yaml
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
=begin rdoc
|
139
|
+
Returns the internal, parsed data fields in an array. This is used when scanning
|
140
|
+
dicom slices, to compare each dicom slice in a folder and make sure they all hold the
|
141
|
+
same data.
|
142
|
+
=end
|
143
|
+
def to_array
|
144
|
+
return [@filename,
|
145
|
+
@timestamp,
|
146
|
+
@source,
|
147
|
+
@rmr_number,
|
148
|
+
@series_description,
|
149
|
+
@gender,
|
150
|
+
@slice_thickness,
|
151
|
+
@slice_spacing,
|
152
|
+
@reconstruction_diameter,
|
153
|
+
@acquisition_matrix_x,
|
154
|
+
@acquisition_matrix_y]
|
155
|
+
end
|
156
|
+
|
157
|
+
=begin rdoc
|
158
|
+
Returns an SQL statement to insert this image into the raw_images table of a
|
159
|
+
compatible database (sqlite3). This is intended for inserting into the rails
|
160
|
+
backend database.
|
161
|
+
=end
|
162
|
+
def db_insert(image_dataset_id)
|
163
|
+
"INSERT INTO raw_image_files
|
164
|
+
(filename, header_reader, file_type, timestamp, source, rmr_number, series_description,
|
165
|
+
gender, num_slices, slice_thickness, slice_spacing, reconstruction_diameter,
|
166
|
+
acquisition_matrix_x, acquisition_matrix_y, rep_time, bold_reps, created_at, updated_at, image_dataset_id)
|
167
|
+
VALUES ('#{@filename}', '#{@hdr_reader}', '#{@file_type}', '#{@timestamp.to_s}', '#{@source}', '#{@rmr_number}',
|
168
|
+
'#{@series_description}', '#{@gender}', #{@num_slices}, #{@slice_thickness}, #{@slice_spacing},
|
169
|
+
#{@reconstruction_diameter}, #{@acquisition_matrix_x}, #{@acquisition_matrix_y}, #{@rep_time},
|
170
|
+
#{@bold_reps}, '#{DateTime.now}', '#{DateTime.now}', #{image_dataset_id})"
|
171
|
+
end
|
172
|
+
|
173
|
+
=begin rdoc
|
174
|
+
Returns an SQL statement to select this image file row from the raw_image_files table
|
175
|
+
of a compatible database.
|
176
|
+
=end
|
177
|
+
def db_fetch
|
178
|
+
"SELECT *" + from_table_where + sql_match_conditions
|
179
|
+
end
|
180
|
+
|
181
|
+
=begin rdoc
|
182
|
+
Returns and SQL statement to remove this image file from the raw_image_files table
|
183
|
+
of a compatible database.
|
184
|
+
=end
|
185
|
+
def db_remove
|
186
|
+
"DELETE" + from_table_where + sql_match_conditions
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
=begin rdoc
|
191
|
+
Uses the db_insert method to actually perform the database insert using the
|
192
|
+
specified database file.
|
193
|
+
=end
|
194
|
+
def db_insert!( db_file )
|
195
|
+
db = SQLite3::Database.new( db_file )
|
196
|
+
db.transaction do |database|
|
197
|
+
if not database.execute( db_fetch ).empty?
|
198
|
+
raise(IndexError, "Entry exists for #{filename}, #{@rmr_number}, #{@timestamp.to_s}... Skipping.")
|
199
|
+
end
|
200
|
+
database.execute( db_insert )
|
201
|
+
end
|
202
|
+
db.close
|
203
|
+
end
|
204
|
+
|
205
|
+
=begin rdoc
|
206
|
+
Removes this instance from the raw_image_files table of the specified database.
|
207
|
+
=end
|
208
|
+
def db_remove!( db_file )
|
209
|
+
db = SQLite3::Database.new( db_file )
|
210
|
+
db.execute( db_remove )
|
211
|
+
db.close
|
212
|
+
end
|
213
|
+
|
214
|
+
=begin rdoc
|
215
|
+
Finds the row in the raw_image_files table of the given db file that matches this object.
|
216
|
+
ORM is based on combination of rmr_number, timestamp, and filename. The row is returned
|
217
|
+
as an array of values (see 'sqlite3' gem docs).
|
218
|
+
=end
|
219
|
+
def db_fetch!( db_file )
|
220
|
+
db = SQLite3::Database.new( db_file )
|
221
|
+
db_row = db.execute( db_fetch )
|
222
|
+
db.close
|
223
|
+
return db_row
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
|
232
|
+
|
233
|
+
def from_table_where
|
234
|
+
" FROM raw_image_files WHERE "
|
235
|
+
end
|
236
|
+
|
237
|
+
def sql_match_conditions
|
238
|
+
"rmr_number = '#{@rmr_number}' AND timestamp = '#{@timestamp.to_s}' AND filename = '#{@filename}'"
|
239
|
+
end
|
240
|
+
|
241
|
+
=begin rdoc
|
242
|
+
Reads the file header using one of the available header reading utilities.
|
243
|
+
Returns both the header data as a one big string, and the name of the utility
|
244
|
+
used to read it.
|
245
|
+
=end
|
246
|
+
def read_header(absfilepath)
|
247
|
+
header = `#{DICOM_HDR} #{absfilepath} 2> /dev/null`
|
248
|
+
if ( header.index("ERROR") == nil and
|
249
|
+
header.chomp != "" and
|
250
|
+
header.length > MIN_HDR_LENGTH )
|
251
|
+
return [ header, DICOM_HDR ]
|
252
|
+
end
|
253
|
+
header = `#{RDGEHDR} #{absfilepath} 2> /dev/null`
|
254
|
+
if ( header.chomp != "" and
|
255
|
+
header.length > MIN_HDR_LENGTH )
|
256
|
+
return [ header, RDGEHDR ]
|
257
|
+
end
|
258
|
+
return [ nil, nil ]
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
=begin rdoc
|
263
|
+
Returns a string that indicates the file type. This is difficult because dicom
|
264
|
+
files have no consistent naming conventions/suffixes. Here we chose to call a
|
265
|
+
file a "pfile" if it is an image and the file name is of the form P*.7
|
266
|
+
All other images are called "dicom".
|
267
|
+
=end
|
268
|
+
def determine_file_type
|
269
|
+
return "pfile" if image? and (@filename =~ /^P.....\.7/) != nil
|
270
|
+
return "dicom" if image? and (@filename =~ /^P.....\.7/) == nil
|
271
|
+
return nil
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
=begin rdoc
|
276
|
+
Parses the header data and extracts a collection of instance variables. If
|
277
|
+
@hdr_data and @hdr_reader are not already availables, this function does nothing.
|
278
|
+
=end
|
279
|
+
def import_hdr
|
280
|
+
return if @hdr_data == nil
|
281
|
+
dicom_hdr_import if (@hdr_reader == "dicom_hdr")
|
282
|
+
rdgehdr_import if (@hdr_reader == "rdgehdr")
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
=begin rdoc
|
287
|
+
Extracts a collection of metadata from @hdr_data retrieved using the dicom_hdr
|
288
|
+
utility.
|
289
|
+
=end
|
290
|
+
def dicom_hdr_import
|
291
|
+
date_pat = /ID STUDY DATE\/\/(.*)\n/i
|
292
|
+
time_pat = /ID Series Time\/\/(.*)\n/i
|
293
|
+
source_pat = /ID INSTITUTION NAME\/\/(.*)\n/i
|
294
|
+
rmr_number_pat = /[ID Accession Number|ID Study Description]\/\/(RMR.*)\n/i
|
295
|
+
series_description_pat = /ID SERIES DESCRIPTION\/\/(.*)\n/i
|
296
|
+
gender_pat = /PAT PATIENT SEX\/\/(.)/i
|
297
|
+
slice_thickness_pat = /ACQ SLICE THICKNESS\/\/(.*)\n/i
|
298
|
+
slice_spacing_pat = /ACQ SPACING BETWEEN SLICES\/\/(.*)\n/i
|
299
|
+
recon_diam_pat = /ACQ RECONSTRUCTION DIAMETER\/\/([0-9]+)/i
|
300
|
+
#acquisition_matrix_pat = /ACQ ACQUISITION MATRIX\/\/ ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)/i
|
301
|
+
acq_mat_x_pat = /IMG Rows\/\/ ([0-9]+)/i
|
302
|
+
acq_mat_y_pat = /IMG Columns\/\/ ([0-9]+)/i
|
303
|
+
num_slices_pat = /REL Images in Acquisition\/\/([0-9]+)/i
|
304
|
+
bold_reps_pat = /REL Number of Temporal Positions\/\/([0-9]+)/i
|
305
|
+
rep_time_pat = /ACQ Repetition Time\/\/(.*)\n/i
|
306
|
+
|
307
|
+
rmr_number_pat =~ @hdr_data
|
308
|
+
@rmr_number = ($1).strip.chomp
|
309
|
+
|
310
|
+
source_pat =~ @hdr_data
|
311
|
+
@source = ($1).strip.chomp
|
312
|
+
|
313
|
+
num_slices_pat =~ @hdr_data
|
314
|
+
@num_slices = ($1).to_i
|
315
|
+
|
316
|
+
slice_thickness_pat =~ @hdr_data
|
317
|
+
@slice_thickness = ($1).strip.chomp.to_f
|
318
|
+
|
319
|
+
slice_spacing_pat =~ @hdr_data
|
320
|
+
@slice_spacing = ($1).strip.chomp.to_f
|
321
|
+
|
322
|
+
date_pat =~ @hdr_data
|
323
|
+
date = $1
|
324
|
+
time_pat =~ @hdr_data
|
325
|
+
time = $1
|
326
|
+
@timestamp = DateTime.parse(date + time)
|
327
|
+
|
328
|
+
gender_pat =~ @hdr_data
|
329
|
+
@gender = $1
|
330
|
+
|
331
|
+
#acquisition_matrix_pat =~ @hdr_data
|
332
|
+
#matrix = [($1).to_i, ($2).to_i, ($3).to_i, ($4).to_i]
|
333
|
+
#matrix = matrix.delete_if { |x| x == 0 }
|
334
|
+
acq_mat_x_pat =~ @hdr_data
|
335
|
+
@acquisition_matrix_x = ($1).to_i
|
336
|
+
acq_mat_y_pat =~ @hdr_data
|
337
|
+
@acquisition_matrix_y = ($1).to_i
|
338
|
+
|
339
|
+
series_description_pat =~ @hdr_data
|
340
|
+
@series_description = ($1).strip.chomp
|
341
|
+
|
342
|
+
recon_diam_pat =~ @hdr_data
|
343
|
+
@reconstruction_diameter = ($1).to_i
|
344
|
+
|
345
|
+
bold_reps_pat =~ @hdr_data
|
346
|
+
@bold_reps = ($1).to_i
|
347
|
+
|
348
|
+
rep_time_pat =~ @hdr_data
|
349
|
+
@rep_time = ($1).strip.chomp.to_f
|
350
|
+
end
|
351
|
+
|
352
|
+
|
353
|
+
=begin rdoc
|
354
|
+
Extracts a collection of metadata from @hdr_data retrieved using the rdgehdr
|
355
|
+
utility.
|
356
|
+
=end
|
357
|
+
def rdgehdr_import
|
358
|
+
source_pat = /hospital [Nn]ame: ([[:graph:]\t ]+)/i
|
359
|
+
num_slices_pat = /Number of slices in this scan group: ([0-9]+)/i
|
360
|
+
slice_thickness_pat = /slice thickness \(mm\): ([[:graph:]]+)/i
|
361
|
+
slice_spacing_pat = /spacing between scans \(mm\??\): ([[:graph:]]+)/i
|
362
|
+
date_pat = /actual image date\/time stamp: (.*)\n/i
|
363
|
+
gender_pat = /Patient Sex: (1|2)/i
|
364
|
+
acquisition_matrix_x_pat = /Image matrix size \- X: ([0-9]+)/i
|
365
|
+
acquisition_matrix_y_pat = /Image matrix size \- Y: ([0-9]+)/i
|
366
|
+
series_description_pat = /Series Description: ([[:graph:] \t]+)/i
|
367
|
+
recon_diam_pat = /Display field of view \- X \(mm\): ([0-9]+)/i
|
368
|
+
rmr_number_pat = /Patient ID for this exam: ([[:graph:]]+)/i
|
369
|
+
bold_reps_pat = /Number of excitations: ([0-9]+)/i
|
370
|
+
rep_time_pat = /Pulse repetition time \(usec\): ([0-9]+)/i
|
371
|
+
|
372
|
+
rmr_number_pat =~ @hdr_data
|
373
|
+
@rmr_number = ($1).nil? ? "rmr not found" : ($1).strip.chomp
|
374
|
+
|
375
|
+
source_pat =~ @hdr_data
|
376
|
+
@source = ($1).nil? ? "source not found" : ($1).strip.chomp
|
377
|
+
|
378
|
+
num_slices_pat =~ @hdr_data
|
379
|
+
@num_slices = ($1).to_i
|
380
|
+
|
381
|
+
slice_thickness_pat =~ @hdr_data
|
382
|
+
@slice_thickness = ($1).to_f
|
383
|
+
|
384
|
+
slice_spacing_pat =~ @hdr_data
|
385
|
+
@slice_spacing = ($1).to_f
|
386
|
+
|
387
|
+
date_pat =~ @hdr_data
|
388
|
+
@timestamp = DateTime.parse($1)
|
389
|
+
|
390
|
+
gender_pat =~ @hdr_data
|
391
|
+
@gender = $1 == 1 ? "M" : "F"
|
392
|
+
|
393
|
+
acquisition_matrix_x_pat =~ @hdr_data
|
394
|
+
@acquisition_matrix_x = ($1).to_i
|
395
|
+
acquisition_matrix_y_pat =~ @hdr_data
|
396
|
+
@acquisition_matrix_y = ($1).to_i
|
397
|
+
|
398
|
+
series_description_pat =~ @hdr_data
|
399
|
+
@series_description = ($1).strip.chomp
|
400
|
+
|
401
|
+
recon_diam_pat =~ @hdr_data
|
402
|
+
@reconstruction_diameter = ($1).to_i
|
403
|
+
|
404
|
+
bold_reps_pat =~ @hdr_data
|
405
|
+
@bold_reps = ($1).to_i
|
406
|
+
|
407
|
+
rep_time_pat =~ @hdr_data
|
408
|
+
@rep_time = ($1).to_f / 1000000
|
409
|
+
end
|
410
|
+
|
411
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Provides a mapping between series descriptions that are extracted from raw image
|
3
|
+
headers and some associated attributes.
|
4
|
+
=end
|
5
|
+
class SeriesDescription
|
6
|
+
#:stopdoc:
|
7
|
+
SERIES_DESCRIPTIONS = {
|
8
|
+
"3-P,Localizer" => [ "3PlaneLoc", "anat", nil ],
|
9
|
+
"gre field map rhrcctrl = 15" => [ "Fieldmap", "epan", nil ],
|
10
|
+
"SAG EPI Test 1 2 3" => [ "EPITest", "epan", "sag" ],
|
11
|
+
"3D IR AX T1 - NEW" => [ "T1_EFGRE3D", "anat", "ax" ],
|
12
|
+
"AX T2 W FR FSE 1.7 skip 0.3" => [ "T2_FSE", "fse", "ax" ],
|
13
|
+
"High Order Shim 28cm" => [ "Shim", "anat", nil ],
|
14
|
+
"SAG gre field map rhrcctr =15" => [ "Fieldmap", "epan", "sag" ],
|
15
|
+
"dti w/ card gate" => [ "DTI", "epan", nil ],
|
16
|
+
"HOS Head coil" => [ "3PlaneLoc", "anat", nil ],
|
17
|
+
"Localizer" => [ "3PlaneLoc", "anat", nil ],
|
18
|
+
"F Map; rhrcctrl 15; te7, 10" => [ "Fieldmap", "epan", nil ],
|
19
|
+
"SAG EPI TEST" => [ "EPITest", "epan", "sag" ],
|
20
|
+
"ASL CBF" => [ "AlsopsASL", "anat", nil ],
|
21
|
+
"DTI - prev 39 slices" => [ "DTI", "epan", nil ],
|
22
|
+
"3D IR COR T1 - NEW" => [ "T1_EFGRE3D", "anat", "cor" ],
|
23
|
+
"SAG T2 W FSE 1.7 skip 0.3" => [ "T2_FSE", "fse", "sag" ],
|
24
|
+
"COR T2 W FSE 1.7 skip 0.3" => [ "T2_FSE", "fse", "cor" ],
|
25
|
+
"3plane - hirez" => [ "3PlaneLoc", "anat", nil ],
|
26
|
+
"SAG gre field map rhrcctr =1?" => [ "Fieldmap", "epan", "sag" ],
|
27
|
+
"SAG EPI Test 1 2 3" => [ "EPITest", "epan", "sag" ],
|
28
|
+
"dti w/o card gate" => [ "DTI", "epan", nil ],
|
29
|
+
"Ax Flair irFSE" => [ "T1_Flair", "fse", "ax" ],
|
30
|
+
"DTI" => [ "DTI", "epan", nil ],
|
31
|
+
"AX T2 Flair" => [ "T2_Flair", "fse", "ax" ],
|
32
|
+
"AX T2 FLAIR" => [ "T2_Flair", "fse", "ax" ],
|
33
|
+
"SAG EPI Snod" => [ "fMRI_snod", "epan", "sag" ],
|
34
|
+
"SAG EPI Resting" => [ "fMRI_rest", "epan", "sag" ],
|
35
|
+
"SAG EPI Snod (141 x 2)" => [ "fMRI_snod", "epan", "sag" ],
|
36
|
+
"DTI - 10 Dir 1.8mm" => [ "DTI", "epan", nil ],
|
37
|
+
"F Map; rhrcctrl 15; te6, 9" => [ "Fieldmap", "epan", nil ],
|
38
|
+
"ASSET CAL" => [ "ASSET_Calibration", "epan", nil ],
|
39
|
+
"SAG EPI Resting (180)" => [ "fMRI_rest", "epan", "sag" ],
|
40
|
+
"Sag SMAPS" => [ "smaps", "anat", "sag" ]
|
41
|
+
}
|
42
|
+
#:startdoc:
|
43
|
+
|
44
|
+
# A string used to build nice file names for reconstructed data sets
|
45
|
+
attr_reader :scan_type
|
46
|
+
|
47
|
+
# Used as an argument to to3d, the AFNI command used to reconstruct a collection
|
48
|
+
# of dicom files into a single nifti data set
|
49
|
+
attr_reader :anat_type
|
50
|
+
|
51
|
+
# The scan acquisition plane: axial, coronal, or sagittal
|
52
|
+
attr_reader :acq_plane
|
53
|
+
|
54
|
+
=begin rdoc
|
55
|
+
Creates a new object based on a series description string.
|
56
|
+
The series description for an image is conveniently available as an attribute
|
57
|
+
of the RawImageFile class.
|
58
|
+
|
59
|
+
<i>Note that the series descriptions inside image headers sometimes have trailing</i>
|
60
|
+
<i>white space, the constructor here strips and chomps it. Be advised of this behavior.</i>
|
61
|
+
|
62
|
+
<tt>sd = SeriesDescription.new('3D IR AX T1 - NEW')</tt>
|
63
|
+
|
64
|
+
<tt>sd.scan_type</tt>
|
65
|
+
|
66
|
+
<tt>=> "T1_EFGRE3D"</tt>
|
67
|
+
|
68
|
+
<tt>sd.anat_type</tt>
|
69
|
+
|
70
|
+
<tt>=> "anat"</tt>
|
71
|
+
|
72
|
+
<tt>sd.acq_plane</tt>
|
73
|
+
|
74
|
+
<tt>=> "axial"</tt>
|
75
|
+
=end
|
76
|
+
def initialize(series_description)
|
77
|
+
@series_description = series_description.strip.chomp
|
78
|
+
raise IndexError if not SERIES_DESCRIPTIONS.has_key?(@series_description)
|
79
|
+
@scan_type, @anat_type, @acq_plane = SERIES_DESCRIPTIONS[@series_description]
|
80
|
+
end
|
81
|
+
end
|