brainmap-ImageData 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/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
|