metamri 0.2.9 → 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -9,6 +9,7 @@ begin
9
9
  require 'rspec'
10
10
  require 'dicom'
11
11
  require 'rmagick'
12
+ require 'sqlite'
12
13
  rescue LoadError
13
14
  end
14
15
 
@@ -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 :study_id
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
- @study_id = @raw_image_files.first.study_id.nil? ? nil : @raw_image_files.first.study_id
90
- # raise(IndexError, "No study id / exam number found") if @study_id.nil?
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 :study_id
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
- :dicom_series_uid => "0020,000E",
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
- :study_id => "0020,0010",
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[:study_id] = {
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
- @dicom_study_uid = ($1).strip.chomp unless $1.nil?
636
+ @study_uid = ($1).strip.chomp unless $1.nil?
624
637
 
625
638
  series_uid_pat =~ @hdr_data
626
- @dicom_series_uid = ($1).strip.chomp
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
@@ -1,3 +1,3 @@
1
1
  module Metamri
2
- VERSION = "0.2.9"
2
+ VERSION = "0.2.10"
3
3
  end
@@ -4,7 +4,7 @@ require 'tempfile'
4
4
  require 'yaml'
5
5
  require 'tmpdir'
6
6
  require 'fileutils'
7
- # require 'sqlite3'
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 :study_id
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.exist?(File.expand_path(directory))
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
- @study_id = nil
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
- # Run a scan with the following options:
88
- # - :ignore_patterns - An array of Regular Expressions that will be used to skip heavy directories.
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
- @study_id = get_study_id
114
- @dicom_study_uid = get_dicom_study_uid unless dicom_datasets.empty?
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 => @study_id,
129
- :dicom_study_uid => @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} - #{@study_id unless @study_id.nil?}"
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(p_id)
264
- @db.execute(sql_insert_visit(p_id))
265
- return @db.last_insert_row_id
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, p_id)
274
- @db.execute(sql_update_visit(v_id, p_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, p_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(scan_procedure_id=0)
335
+ def sql_insert_visit
324
336
  "INSERT INTO visits
325
- (date, scan_procedure_id, scan_number, initials, rmr, radiology_outcome, notes, transfer_mri, transfer_pet,
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}', '#{scan_procedure_id.to_s}', '', '', '#{@rmr_number}', 'no', '', 'no', 'no',
329
- 'no', 'no', 'no', NULL, '#{@visit_directory}', '#{@scanner_source}', '#{DateTime.now}', '#{DateTime.now}')"
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 get_study_id
409
+ def get_exam_number
397
410
  @datasets.each do |ds|
398
- return ds.study_id unless ds.study_id.nil?
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 get_dicom_study_uid
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.study_id.should == "1405"
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: 5
4
+ hash: 3
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 9
10
- version: 0.2.9
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-18 00:00:00 -05:00
19
+ date: 2011-08-26 00:00:00 -05:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency