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.
@@ -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