metamri 0.2.9 → 0.2.10
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/bin/import_study.rb +7 -0
- data/lib/metamri.rb +1 -0
- data/lib/metamri/raw_image_dataset.rb +20 -7
- data/lib/metamri/raw_image_file.rb +25 -8
- data/lib/metamri/version.rb +1 -1
- data/lib/metamri/visit_raw_data_directory.rb +39 -26
- data/spec/unit/raw_image_dataset_spec.rb +1 -1
- metadata +4 -4
data/bin/import_study.rb
CHANGED
@@ -75,6 +75,11 @@ STUDIES = {
|
|
75
75
|
:filter => /^pd..._/,
|
76
76
|
:codename => 'gallagher.pd.visit1'
|
77
77
|
},
|
78
|
+
:pc_4000 => { :dir => '/Data/vtrak1/raw/pc_4000',
|
79
|
+
:logfile => 'pc_4000.scan.log',
|
80
|
+
:filter => /^pc001/,
|
81
|
+
:codename => 'johnson.pc4000.visit1'
|
82
|
+
},
|
78
83
|
:pib_pilot => { :dir => '/Data/vtrak1/raw/pib_pilot_mri',
|
79
84
|
:logfile => 'pib.mri.pilot.scan.log',
|
80
85
|
:filter => /^cpr0/,
|
@@ -148,6 +153,8 @@ def import_study(study, dbfile)
|
|
148
153
|
puts "Exception message: #{e.message}"
|
149
154
|
log.error "There was a problem scanning a dataset in #{visitdir}... skipping."
|
150
155
|
log.error "Exception message: #{e.message}"
|
156
|
+
log.error e.backtrace
|
157
|
+
raise e
|
151
158
|
ensure
|
152
159
|
v = nil
|
153
160
|
end
|
data/lib/metamri.rb
CHANGED
@@ -21,7 +21,7 @@ class RawImageDataset
|
|
21
21
|
# From the first raw image file in the dataset
|
22
22
|
attr_reader :timestamp
|
23
23
|
# From the first raw image file in the dataset
|
24
|
-
attr_reader :
|
24
|
+
attr_reader :exam_number
|
25
25
|
# A key string unique to a dataset composed of the rmr number and the timestamp.
|
26
26
|
attr_reader :dataset_key
|
27
27
|
# the file scanned
|
@@ -86,8 +86,8 @@ class RawImageDataset
|
|
86
86
|
@scanner_source = @raw_image_files.first.source
|
87
87
|
raise(IndexError, "No scanner source found") if @scanner_source.nil?
|
88
88
|
|
89
|
-
@
|
90
|
-
|
89
|
+
@exam_number = @raw_image_files.first.exam_number.nil? ? nil : @raw_image_files.first.exam_number
|
90
|
+
validates_metainfo_for :exam_number, :msg => "No study id / exam number found", :optional => true
|
91
91
|
|
92
92
|
@study_description = @raw_image_files.first.study_description
|
93
93
|
validates_metainfo_for :study_description, :msg => "No study description found" if dicom?
|
@@ -109,7 +109,10 @@ class RawImageDataset
|
|
109
109
|
|
110
110
|
@dicom_taghash = @raw_image_files.first.dicom_taghash
|
111
111
|
validates_metainfo_for :dicom_taghash if dicom?
|
112
|
-
|
112
|
+
|
113
|
+
@image_uid = @raw_image_files.first.image_uid
|
114
|
+
validates_metainfo_for :image_uid if pfile?
|
115
|
+
|
113
116
|
$LOG ||= Logger.new(STDOUT)
|
114
117
|
end
|
115
118
|
|
@@ -132,10 +135,10 @@ class RawImageDataset
|
|
132
135
|
def db_insert(visit_id)
|
133
136
|
"INSERT INTO image_datasets
|
134
137
|
(rmr, series_description, path, timestamp, created_at, updated_at, visit_id,
|
135
|
-
glob, rep_time, bold_reps, slices_per_volume, scanned_file)
|
138
|
+
glob, rep_time, bold_reps, slices_per_volume, scanned_file, 'dicom_study_uid')
|
136
139
|
VALUES ('#{@rmr_number}', '#{@series_description}', '#{@directory}', '#{@timestamp.to_s}', '#{DateTime.now}',
|
137
140
|
'#{DateTime.now}', '#{visit_id}', '#{self.glob}', '#{@raw_image_files.first.rep_time}',
|
138
|
-
'#{@raw_image_files.first.bold_reps}', '#{@raw_image_files.first.num_slices}', '#{@scanned_file}')"
|
141
|
+
'#{@raw_image_files.first.bold_reps}', '#{@raw_image_files.first.num_slices}', '#{@scanned_file}' )"
|
139
142
|
end
|
140
143
|
|
141
144
|
def db_update(dataset_id)
|
@@ -186,7 +189,8 @@ class RawImageDataset
|
|
186
189
|
:slices_per_volume => @raw_image_files.first.num_slices,
|
187
190
|
:scanned_file => @scanned_file,
|
188
191
|
:dicom_series_uid => @dicom_series_uid,
|
189
|
-
:dicom_taghash => @dicom_taghash
|
192
|
+
:dicom_taghash => @dicom_taghash,
|
193
|
+
:image_uid => @image_uid
|
190
194
|
}.merge attrs
|
191
195
|
end
|
192
196
|
|
@@ -361,6 +365,15 @@ private
|
|
361
365
|
def directory_basename
|
362
366
|
File.basename(@directory)
|
363
367
|
end
|
368
|
+
|
369
|
+
# Metamri will return both dicom_series_uid and image_uid for ActiveRecord
|
370
|
+
# Correct UID choosing will happen in the Panda.
|
371
|
+
# But, here's a shortcut to a pfile?/dicom? ternary for correct uid for dataset
|
372
|
+
def dataset_uid
|
373
|
+
@raw_image_files.first.dicom_series_uid ?
|
374
|
+
@raw_image_files.first.dicom_series_uid : @raw_image_files.first.image_uid
|
375
|
+
end
|
376
|
+
|
364
377
|
|
365
378
|
# Ensure that metadata is present in instance variables.
|
366
379
|
#
|
@@ -41,7 +41,7 @@ class RawImageFile
|
|
41
41
|
# An identifier unique to a 'visit', these are assigned by the scanner techs at scan time.
|
42
42
|
attr_reader :rmr_number
|
43
43
|
# An identifier unique to a Study Session - AKA Exam Number
|
44
|
-
attr_reader :
|
44
|
+
attr_reader :exam_number
|
45
45
|
# A short string describing the acquisition sequence. These come from the scanner.
|
46
46
|
# code and are used to initialise SeriesDescription objects to find related attributes.
|
47
47
|
attr_reader :series_description
|
@@ -73,6 +73,8 @@ class RawImageFile
|
|
73
73
|
attr_reader :dicom_header
|
74
74
|
# Hash of all DICOM Tags including their Names and Values (See #dicom_taghash for more information on the structure)
|
75
75
|
attr_reader :dicom_taghash
|
76
|
+
# DICOM SOP Instance UID (from the scanned file)
|
77
|
+
attr_reader :dicom_image_uid
|
76
78
|
# DICOM Series UID
|
77
79
|
attr_reader :dicom_series_uid
|
78
80
|
# DICOM Study UID
|
@@ -235,8 +237,16 @@ class RawImageFile
|
|
235
237
|
return db_row
|
236
238
|
end
|
237
239
|
|
238
|
-
|
239
|
-
|
240
|
+
# The series ID (dicom_series_uid [dicom] or series_uid [pfile/ifile])
|
241
|
+
# This is unique for DICOM datasets, but not for PFiles
|
242
|
+
def series_uid
|
243
|
+
@dicom_series_uid || @series_uid
|
244
|
+
end
|
245
|
+
|
246
|
+
# The UID unique to the raw image file scanned
|
247
|
+
def image_uid
|
248
|
+
@dicom_image_uid || @image_uid
|
249
|
+
end
|
240
250
|
|
241
251
|
private
|
242
252
|
|
@@ -325,6 +335,7 @@ private
|
|
325
335
|
# 0008,0030 Study Time TM 6 101538
|
326
336
|
# 0008,0080 Institution Name LO 4 Institution
|
327
337
|
# 0008,1010 Station Name SH 8 Station
|
338
|
+
# 0008,0018 SOP Instance UID 12 1.2.840.113619.2.155.3596.6906438.17031.1121881958.942
|
328
339
|
# 0008,1030 Study Description LO 12 PILOT Study
|
329
340
|
# 0008,103E Series Description LO 12 3pl loc FGRE
|
330
341
|
# 0008,1070 Operators' Name PN 2 SP
|
@@ -386,9 +397,10 @@ private
|
|
386
397
|
:software_version => "0018,1020",
|
387
398
|
:protocol_name => "0018,1030",
|
388
399
|
:bold_reps => "0020,0105",
|
389
|
-
:
|
400
|
+
:dicom_image_uid => "0008,0018", # Each DICOM Image (i.e. raw image file) has a unique SOP Instance UID
|
401
|
+
:dicom_series_uid => "0020,000E", # Series UID (shared by all dicoms in the same series)
|
390
402
|
:dicom_study_uid => "0020,000D",
|
391
|
-
:
|
403
|
+
:exam_number => "0020,0010",
|
392
404
|
:num_slices => "0020,1002",
|
393
405
|
:acquisition_matrix_x => "0028,0010",
|
394
406
|
:acquisition_matrix_y => "0028,0011"
|
@@ -461,7 +473,7 @@ private
|
|
461
473
|
:pat => /[ID Accession Number|ID Study Description]\/\/(RMR.*)\n/i,
|
462
474
|
:required => true
|
463
475
|
}
|
464
|
-
dicom_tag_templates[:
|
476
|
+
dicom_tag_templates[:exam_number] = {
|
465
477
|
:type => :string,
|
466
478
|
:pat => /STUDY ID\/\/([0-9]+)/i,
|
467
479
|
:required => true
|
@@ -580,6 +592,7 @@ private
|
|
580
592
|
rep_time_pat = /Pulse repetition time \(usec\): ([0-9]+)/i
|
581
593
|
study_uid_pat = /Study entity unique ID: ([[:graph:]]+)/i
|
582
594
|
series_uid_pat = /Series entity unique ID: ([[:graph:]]+)/i
|
595
|
+
image_uid_pat = /Image unique ID: ([[:graph:]]+)/i
|
583
596
|
|
584
597
|
rmr_number_pat =~ @hdr_data
|
585
598
|
@rmr_number = ($1).nil? ? "rmr not found" : ($1).strip.chomp
|
@@ -620,10 +633,14 @@ private
|
|
620
633
|
@rep_time = ($1).to_f / 1000000
|
621
634
|
|
622
635
|
study_uid_pat =~ @hdr_data
|
623
|
-
@
|
636
|
+
@study_uid = ($1).strip.chomp unless $1.nil?
|
624
637
|
|
625
638
|
series_uid_pat =~ @hdr_data
|
626
|
-
@
|
639
|
+
@series_uid = ($1).strip.chomp unless $1.nil?
|
640
|
+
|
641
|
+
image_uid_pat =~ @hdr_data
|
642
|
+
@image_uid = ($1).strip.chomp unless $1.nil?
|
643
|
+
|
627
644
|
end
|
628
645
|
|
629
646
|
end
|
data/lib/metamri/version.rb
CHANGED
@@ -4,7 +4,7 @@ require 'tempfile'
|
|
4
4
|
require 'yaml'
|
5
5
|
require 'tmpdir'
|
6
6
|
require 'fileutils'
|
7
|
-
|
7
|
+
require 'sqlite3'
|
8
8
|
require 'logger'
|
9
9
|
require 'pp'
|
10
10
|
require 'metamri/raw_image_file'
|
@@ -49,7 +49,7 @@ class VisitRawDataDirectory
|
|
49
49
|
# scanner source
|
50
50
|
attr_accessor :scanner_source
|
51
51
|
# scanner-defined study id / exam number
|
52
|
-
attr_accessor :
|
52
|
+
attr_accessor :exam_number
|
53
53
|
#
|
54
54
|
attr_accessor :db
|
55
55
|
# Scan ID is the short name for the scan (tbiva018, tbiva018b)
|
@@ -67,7 +67,7 @@ class VisitRawDataDirectory
|
|
67
67
|
# A new Visit instance needs to know the path to its raw data and scan_procedure name. The scan_procedure
|
68
68
|
# name must match a name in the database, if not a new scan_procedure entry will be inserted.
|
69
69
|
def initialize(directory, scan_procedure_name=nil)
|
70
|
-
raise(IOError, "Visit directory not found: #{directory}") unless File.
|
70
|
+
raise(IOError, "Visit directory not found: #{directory}") unless File.directory? File.expand_path(directory)
|
71
71
|
@visit_directory = File.expand_path(directory)
|
72
72
|
@working_directory = Dir.tmpdir
|
73
73
|
@datasets = Array.new
|
@@ -75,7 +75,7 @@ class VisitRawDataDirectory
|
|
75
75
|
@rmr_number = nil
|
76
76
|
@scan_procedure_name = scan_procedure_name.nil? ? get_scan_procedure_based_on_raw_directory : scan_procedure_name
|
77
77
|
@db = nil
|
78
|
-
@
|
78
|
+
@exam_number = nil
|
79
79
|
initialize_log
|
80
80
|
end
|
81
81
|
|
@@ -84,8 +84,10 @@ class VisitRawDataDirectory
|
|
84
84
|
# @datasets will hold an array of ImageDataset instances. Setting the rmr here can raise an
|
85
85
|
# exception if no valid rmr is found in the datasets, be prepared to catch it.
|
86
86
|
#
|
87
|
-
#
|
88
|
-
#
|
87
|
+
# === Options
|
88
|
+
#
|
89
|
+
# * <tt>:ignore_patterns</tt> -- Array of Regex'es. An array of Regular Expressions that will be used to skip heavy directories.
|
90
|
+
#
|
89
91
|
def scan(options = {})
|
90
92
|
flash "Scanning visit raw data directory #{@visit_directory}" if $LOG.level <= Logger::INFO
|
91
93
|
default_options = {:ignore_patterns => []}
|
@@ -110,8 +112,8 @@ class VisitRawDataDirectory
|
|
110
112
|
@timestamp = get_visit_timestamp
|
111
113
|
@rmr_number = get_rmr_number
|
112
114
|
@scanner_source = get_scanner_source
|
113
|
-
@
|
114
|
-
@
|
115
|
+
@exam_number = get_exam_number
|
116
|
+
@study_uid = get_study_uid unless dicom_datasets.empty?
|
115
117
|
flash "Completed scanning #{@visit_directory}" if $LOG.level <= Logger::DEBUG
|
116
118
|
else
|
117
119
|
raise(IndexError, "No datasets could be scanned for directory #{@visit_directory}")
|
@@ -125,8 +127,8 @@ class VisitRawDataDirectory
|
|
125
127
|
:rmr => @rmr_number,
|
126
128
|
:path => @visit_directory,
|
127
129
|
:scanner_source => @scanner_source ||= get_scanner_source,
|
128
|
-
:scan_number => @
|
129
|
-
:dicom_study_uid => @
|
130
|
+
:scan_number => @exam_number,
|
131
|
+
:dicom_study_uid => @study_uid
|
130
132
|
}
|
131
133
|
end
|
132
134
|
|
@@ -161,6 +163,7 @@ class VisitRawDataDirectory
|
|
161
163
|
end
|
162
164
|
rescue Exception => e
|
163
165
|
puts e.message
|
166
|
+
puts e.backtrace
|
164
167
|
ensure
|
165
168
|
@db.close
|
166
169
|
@db = nil
|
@@ -203,7 +206,7 @@ Returns an array of the created nifti files.
|
|
203
206
|
def to_s
|
204
207
|
puts; @visit_directory.length.times { print "-" }; puts
|
205
208
|
puts "#{@visit_directory}"
|
206
|
-
puts "#{@rmr_number} - #{@timestamp.strftime('%F')} - #{@scanner_source} - #{@
|
209
|
+
puts "#{@rmr_number} - #{@timestamp.strftime('%F')} - #{@scanner_source} - #{@exam_number unless @exam_number.nil?}"
|
207
210
|
puts
|
208
211
|
puts RawImageDataset.to_table(@datasets)
|
209
212
|
return
|
@@ -260,18 +263,24 @@ Returns an array of the created nifti files.
|
|
260
263
|
end
|
261
264
|
|
262
265
|
def insert_new_visit(p_id)
|
263
|
-
puts sql_insert_visit
|
264
|
-
@db.execute(sql_insert_visit
|
265
|
-
|
266
|
+
puts sql_insert_visit
|
267
|
+
@db.execute(sql_insert_visit)
|
268
|
+
visit_id = @db.last_insert_row_id
|
269
|
+
puts sql_insert_scan_procedures_visits(p_id, visit_id)
|
270
|
+
@db.execute(sql_insert_scan_procedures_visits(p_id, visit_id))
|
271
|
+
return visit_id
|
266
272
|
end
|
267
273
|
|
268
274
|
def get_existing_visit_id
|
269
275
|
return @db.execute(sql_fetch_visit_matches).first['id']
|
270
276
|
end
|
271
277
|
|
278
|
+
# ScanProcedure now in a separate table
|
279
|
+
# Ignore it for now. BAD!
|
280
|
+
# Note: wtf
|
272
281
|
def update_existing_visit(v_id, p_id)
|
273
|
-
puts sql_update_visit(v_id
|
274
|
-
@db.execute(sql_update_visit(v_id
|
282
|
+
puts sql_update_visit(v_id)
|
283
|
+
@db.execute(sql_update_visit(v_id))
|
275
284
|
end
|
276
285
|
|
277
286
|
def fetch_or_insert_scan_procedure
|
@@ -284,12 +293,11 @@ Returns an array of the created nifti files.
|
|
284
293
|
return scan_procedure_matches.empty? ? new_scan_procedure_id : scan_procedure_matches.first['id']
|
285
294
|
end
|
286
295
|
|
287
|
-
def sql_update_visit(v_id
|
296
|
+
def sql_update_visit(v_id)
|
288
297
|
"UPDATE visits SET
|
289
298
|
date = '#{@timestamp.to_s}',
|
290
299
|
rmr = '#{@rmr_number}',
|
291
300
|
path = '#{@visit_directory}',
|
292
|
-
scan_procedure_id = '#{p_id.to_s}',
|
293
301
|
scanner_source = '#{@scanner_source}'
|
294
302
|
WHERE id = '#{v_id}'"
|
295
303
|
end
|
@@ -298,6 +306,10 @@ Returns an array of the created nifti files.
|
|
298
306
|
"INSERT INTO scan_procedures (codename) VALUES ('#{@scan_procedure_name}')"
|
299
307
|
end
|
300
308
|
|
309
|
+
def sql_insert_scan_procedures_visits(scan_procedure_id, visit_id)
|
310
|
+
"INSERT INTO scan_procedures_visits (scan_procedure_id, visit_id) VALUES('#{scan_procedure_id}', '#{visit_id}')"
|
311
|
+
end
|
312
|
+
|
301
313
|
def sql_insert_series_description(sd)
|
302
314
|
"INSERT INTO series_descriptions (long_description) VALUES ('#{sd}')"
|
303
315
|
end
|
@@ -307,7 +319,7 @@ Returns an array of the created nifti files.
|
|
307
319
|
end
|
308
320
|
|
309
321
|
def sql_fetch_scan_procedure_name
|
310
|
-
"SELECT * FROM scan_procedures WHERE codename = '#{@scan_procedure_name}'"
|
322
|
+
"SELECT * FROM scan_procedures WHERE codename = '#{@scan_procedure_name}' LIMIT 1"
|
311
323
|
end
|
312
324
|
|
313
325
|
def sql_fetch_series_description(sd)
|
@@ -320,13 +332,13 @@ Returns an array of the created nifti files.
|
|
320
332
|
|
321
333
|
|
322
334
|
# generates an sql insert statement to insert this visit with a given participant id
|
323
|
-
def sql_insert_visit
|
335
|
+
def sql_insert_visit
|
324
336
|
"INSERT INTO visits
|
325
|
-
(date,
|
337
|
+
(date, scan_number, initials, rmr, radiology_outcome, notes, transfer_mri, transfer_pet,
|
326
338
|
conference, compile_folder, dicom_dvd, user_id, path, scanner_source, created_at, updated_at)
|
327
339
|
VALUES
|
328
|
-
('#{@timestamp.to_s}', '
|
329
|
-
'no', 'no', '
|
340
|
+
('#{@timestamp.to_s}', '', '', '#{@rmr_number}', 'no', '', 'yes', 'no',
|
341
|
+
'no', 'no', '', NULL, '#{@visit_directory}', '#{@scanner_source}', '#{DateTime.now}', '#{DateTime.now}')"
|
330
342
|
end
|
331
343
|
|
332
344
|
# Build a new RawImageDataset from a path to the rawfile and parent directory.
|
@@ -386,6 +398,7 @@ Returns an array of the created nifti files.
|
|
386
398
|
|
387
399
|
# retrieves a scanner source from the collection of datasets, raises Exception of none is found
|
388
400
|
def get_scanner_source
|
401
|
+
raise IOError, "No datasets available, can't look for a scanner source" if @datasets.empty?
|
389
402
|
@datasets.each do |ds|
|
390
403
|
return ds.scanner_source unless ds.scanner_source.nil?
|
391
404
|
end
|
@@ -393,15 +406,15 @@ Returns an array of the created nifti files.
|
|
393
406
|
end
|
394
407
|
|
395
408
|
# retrieves exam number / scan id from first #RawImageDataset
|
396
|
-
def
|
409
|
+
def get_exam_number
|
397
410
|
@datasets.each do |ds|
|
398
|
-
return ds.
|
411
|
+
return ds.exam_number unless ds.exam_number.nil?
|
399
412
|
end
|
400
413
|
# raise(IOError, "No valid study id / exam number found.")
|
401
414
|
end
|
402
415
|
|
403
416
|
# retrieves exam number / scan id from first #RawImageDataset
|
404
|
-
def
|
417
|
+
def get_study_uid
|
405
418
|
@datasets.each do |ds|
|
406
419
|
return ds.dicom_study_uid unless ds.dicom_study_uid.nil?
|
407
420
|
end
|
@@ -22,7 +22,7 @@ describe RawImageDataset, "for a single valid DICOM file" do
|
|
22
22
|
ds.scanned_file.should == @valid_dicom_basename
|
23
23
|
ds.scanner_source.should == "Institution"
|
24
24
|
ds.series_description.should == "Ax FSPGR BRAVO T1"
|
25
|
-
ds.
|
25
|
+
ds.exam_number.should == "1405"
|
26
26
|
ds.timestamp.should == DateTime.parse("Wed, 10 Nov 2010 00:00:00 +0000")
|
27
27
|
ds.study_description.should == "RMRMABRAVOTEST"
|
28
28
|
ds.dicom?.should be true
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: metamri
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 3
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 10
|
10
|
+
version: 0.2.10
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Kristopher J. Kosmatka
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2011-08-
|
19
|
+
date: 2011-08-26 00:00:00 -05:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|