metamri 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.
@@ -0,0 +1,3 @@
1
+ *.log
2
+ *.gem
3
+ ._*
@@ -0,0 +1,16 @@
1
+ bin/import_study.rb
2
+ bin/import_visit.rb
3
+ ImageData.gemspec
4
+ lib/metamri.rb
5
+ lib/mysql_tools.rb
6
+ lib/raw_image_dataset.rb
7
+ lib/raw_image_file.rb
8
+ lib/series_description.rb
9
+ lib/visit_raw_data_directory.rb
10
+ Manifest
11
+ Rakefile
12
+ README.rdoc
13
+ test/raw_image_dataset_test.rb
14
+ test/raw_image_file_test.rb
15
+ test/visit_duplication_test.rb
16
+ test/visit_test.rb
@@ -0,0 +1,43 @@
1
+ == ImageData
2
+
3
+ A small library that can be used to extract metadata from large collections of research MR imaging data sets. Support is also provided to insert the metadata into a Wisconsin ADRC Imaging Core compatible database. Several
4
+ command line utilities are provided as well as a minimal API that is useful for building ruby on rails rake tasks.
5
+
6
+ You will most likely be interested in either:
7
+
8
+ = import_visit.rb CLU
9
+
10
+ == Synopsis
11
+ A simple utility for importing imaging data collected during one visit into the WADRC Data Tools web
12
+ application. Data from a visit is contained in one big directory that may have many subdirectories.
13
+ Each individual imaging scan may be composed of an entire directory of dicom files or one single p-file.
14
+ This utility scans through all of the image data sets and retrieved meta-data about the scans from their
15
+ header information.
16
+
17
+ == Examples
18
+ import_visit.rb /path/to/raw/mri/data study.codename /path/to/db/db.sqlite3
19
+
20
+ == Usage
21
+ import_visit.rb <raw_data_directory> <scan_procedure_codename> <database_file>
22
+
23
+ For help use: import_visit.rb -h
24
+
25
+ == Options
26
+ -h, --help Displays help message
27
+ -v, --visit Visit raw data directory, absolute path
28
+ -p, --scan_procedure scan_procedure codename, e.g. johnson.alz.visit1
29
+ -d, --database Database file into which information will imported
30
+
31
+ == Author
32
+ K.J. Kosmatka, kk4@medicine.wisc.edu
33
+
34
+ == Copyright
35
+ Copyright (c) 2009 WADRC Imaging Core.
36
+
37
+
38
+ or:
39
+
40
+
41
+ = VisitRawDirectory class
42
+
43
+ see the doc directory
@@ -0,0 +1,34 @@
1
+ #
2
+ # To change this template, choose Tools | Templates
3
+ # and open the template in the editor.
4
+
5
+ require 'rubygems'
6
+ require 'rake'
7
+ # require 'echoe'
8
+ #
9
+ # Echoe.new('metamri', '0.1.0') do |p|
10
+ # p.description = "Extraction of MRI metadata and insertion into compatible sqlite3 databases."
11
+ # p.url = "http://github.com/brainmap/metamri"
12
+ # p.author = "Kristopher J. Kosmatka"
13
+ # p.email = "kk4@medicine.wisc.edu"
14
+ # p.ignore_pattern = ["nbproject/*"]
15
+ # p.development_dependencies = []
16
+ # end
17
+ #
18
+ # Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
19
+
20
+
21
+ begin
22
+ require 'jeweler'
23
+ Jeweler::Tasks.new do |gemspec|
24
+ gemspec.name = "metamri"
25
+ gemspec.summary = "MRI metadata"
26
+ gemspec.description = "Extraction of MRI metadata and insertion into compatible sqlite3 databases."
27
+ gemspec.email = "kk4@medicine.wisc.edu"
28
+ gemspec.homepage = "http://github.com/brainmap/metamri"
29
+ gemspec.authors = ["Kristopher J. Kosmatka"]
30
+ end
31
+ Jeweler::GemcutterTasks.new
32
+ rescue LoadError
33
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
34
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # == Synopsis
4
+ # A simple utility for importing imaging data for an entire study into the WADRC Data Tools web
5
+ # application. Scans each visit within a particular protocol and inserts all the appropriat meta-data
6
+ # into the given database. Can be run as a command line utility, or the function can be required by other packages.
7
+ #
8
+ # == Examples
9
+ # import_study.rb alz_1 /path/to/the/rails/db/production.sqlite3
10
+ #
11
+ # == Usage
12
+ # import_visit.rb <study_code> <database_file>
13
+ #
14
+ # Study codes are one of:
15
+ # alz_1, alz_2, cms_wais, cms_uwmr, esprit_1, esprit_2, gallagher_pd, pib_pilot, ries_pilot, ries_1,
16
+ # tbi1000_1, tbi1000_2, tbi1000_3, tbiva, wrap140
17
+ #
18
+ # For help use: import_study.rb -h
19
+ #
20
+ # == Options
21
+ # -h, --help Displays help message
22
+ #
23
+ # == Author
24
+ # K.J. Kosmatka, kk4@medicine.wisc.edu
25
+ #
26
+ # == Copyright
27
+ # Copyright (c) 2009 WADRC Imaging Core.
28
+ #
29
+
30
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
31
+
32
+ require 'visit_raw_data_directory'
33
+ require 'pathname'
34
+ require 'rdoc/usage'
35
+ require 'logger'
36
+
37
+ #:stopdoc:
38
+ STUDIES = {
39
+ :alz_1 => { :dir => '/Data/vtrak1/raw/alz_2000',
40
+ :logfile => 'alz.visit1.scan.log',
41
+ :filter => /^alz...$|^alz..._[AB]/i,
42
+ :codename => 'johnson.alz.visit1'
43
+ },
44
+ :alz_2 => { :dir => '/Data/vtrak1/raw/alz_2000',
45
+ :logfile => 'alz.visit2.scan.log',
46
+ :filter => /^alz..._2$/,
47
+ :codename => 'johnson.alz.visit2'
48
+ },
49
+ :bendlin_wmad => { :dir => '/Data/vtrak1/raw/bendlin_WMAD/ge3T_750_scanner',
50
+ :logfile => 'bendlin.wmad.scan.log',
51
+ :filter => /^wmad/,
52
+ :codename => 'bendlin.wmad.visit1'
53
+ },
54
+ :cms_wais => { :dir => '/Data/vtrak1/raw/cms/wais',
55
+ :logfile => 'cms.wais.scan.log',
56
+ :filter => /^pc/,
57
+ :codename => 'johnson.cms.visit1.wais'
58
+ },
59
+ :cms_uwmr => { :dir => '/Data/vtrak1/raw/cms/uwmr',
60
+ :logfile => 'cms.uwmr.scan.log',
61
+ :filter => /^cms...$/,
62
+ :codename => 'johnson.cms.visit1.uwmr'
63
+ },
64
+ :esprit_1 => { :dir => '/Data/vtrak1/raw/esprit/baseline',
65
+ :logfile => 'esprit.baseline.scan.log',
66
+ :filter => /^esp3/,
67
+ :codename => 'carlsson.esprit.visit1.baseline'
68
+ },
69
+ :esprit_2 => { :dir => '/Data/vtrak1/raw/esprit/9month',
70
+ :logfile => 'esprit.9month.scan.log',
71
+ :filter => /^esp3/,
72
+ :codename => 'carlsson.esprit.visit2.9month'
73
+ },
74
+ :gallagher_pd => { :dir => '/Data/vtrak1/raw/gallagher.pd',
75
+ :logfile => 'gallagher.scan.log',
76
+ :filter => /^pd..._/,
77
+ :codename => 'gallagher.pd.visit1'
78
+ },
79
+ :pib_pilot => { :dir => '/Data/vtrak1/raw/pib_pilot_mri',
80
+ :logfile => 'pib.mri.pilot.scan.log',
81
+ :filter => /^cpr0/,
82
+ :codename => 'johnson.pibmripilot.visit1.uwmr'
83
+ },
84
+ :ries_1 => { :dir => '/Data/vtrak1/raw/ries.aware.visit1',
85
+ :logfile => 'ries.aware.visit1.scan.log',
86
+ :filter => /^awr0/,
87
+ :codename => 'ries.aware.visit1'
88
+ },
89
+ :ries_pilot => { :dir => '/Data/vtrak1/raw/ries.aware.visit1',
90
+ :logfile => 'ries.aware.pilot.scan.log',
91
+ :filter => /^awrP/,
92
+ :codename => 'ries.aware.pilot'
93
+ },
94
+ :tbi1000_1 => { :dir => '/Data/vtrak1/raw/tbi_1000',
95
+ :logfile => 'tbi1000.visit1.scan.log',
96
+ :filter => /^tbi...$/,
97
+ :codename => 'johnson.tbi1000.visit1'
98
+ },
99
+ :tbi1000_2 => { :dir => '/Data/vtrak1/raw/tbi_1000',
100
+ :logfile => 'tbi1000.visit2.scan.log',
101
+ :filter => /^tbi..._2/,
102
+ :codename => 'johnson.tbi1000.visit2'
103
+ },
104
+ :tbi1000_3 => { :dir => '/Data/vtrak1/raw/johnson.tbi.aware.visit3',
105
+ :logfile => 'tbiaware.visit3.scan.log',
106
+ :filter => /^tbi..._3$/,
107
+ :codename => 'johnson.tbiaware.visit3'
108
+ },
109
+ :tbiva => { :dir => '/Data/vtrak1/raw/johnson.tbi-va.visit1',
110
+ :logfile => 'tbiva.scan.log',
111
+ :filter => /^tbi/,
112
+ :codename => 'johnson.tbiva.visit1'
113
+ },
114
+ :wrap140 => { :dir => '/Data/vtrak1/raw/wrap140',
115
+ :logfile => 'wrap140.scan.log',
116
+ :filter => /^wrp/,
117
+ :codename => 'johnson.wrap140.visit1'
118
+ }
119
+ }
120
+ #:startdoc:
121
+
122
+
123
+ # == Function
124
+ # Imports an entire study.
125
+ #
126
+ # == Arguments
127
+ # study -- a hash specifying the following keys:
128
+ # :dir => the directory holding all the individual visit directories for this study
129
+ # :logfile => a file name where logging can be written
130
+ # :filter => a regex that matches all of the visit directory names that should be scanned
131
+ # :codename => the study codename, e.g. 'johnson.alz.visit1'
132
+ #
133
+ # dbfile -- the database into which meta-data will be inserted
134
+ #
135
+ def import_study(study, dbfile)
136
+ studydir = Pathname.new(study[:dir])
137
+ log = Logger.new(study[:logfile], shift_age = 7, shift_size = 1048576)
138
+
139
+ studydir.entries.each do |visit|
140
+ next if visit.to_s =~ /^\./
141
+ next unless visit.to_s =~ study[:filter]
142
+ visitdir = studydir + visit
143
+ v = VisitRawDataDirectory.new( visitdir.to_s, study[:codename] )
144
+ begin
145
+ v.scan
146
+ v.db_insert!(dbfile)
147
+ rescue Exception => e
148
+ puts "There was a problem scanning a dataset in #{visitdir}... skipping."
149
+ puts "Exception message: #{e.message}"
150
+ log.error "There was a problem scanning a dataset in #{visitdir}... skipping."
151
+ log.error "Exception message: #{e.message}"
152
+ ensure
153
+ v = nil
154
+ end
155
+ end
156
+ end
157
+
158
+ if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
159
+ RDoc::usage() if (ARGV[0] == '-h' or ARGV.size != 2)
160
+ study = STUDIES[ARGV[0].to_sym]
161
+ raise(IndexError, "Study Not Recognized.") if study.nil?
162
+ dbfile = ARGV[1]
163
+ raise(IOError, "DB File not writable or not existant") unless File.writable?(dbfile)
164
+ begin
165
+ import_study(study, dbfile)
166
+ rescue IndexError, IOError => e
167
+ puts "There was an error importing study #{study}. #{e}"
168
+ raise e
169
+ end
170
+ end
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # == Synopsis
4
+ # A simple utility for importing imaging data collected during one visit into the WADRC Data Tools web
5
+ # application. Data from a visit is contained in one big directory that may have many subdirectories.
6
+ # Each individual imaging scan may be composed of an entire directory of dicom files or one single p-file.
7
+ # This utility scans through all of the image data sets and retrieved meta-data about the scans from their
8
+ # header information.
9
+ #
10
+ # == Examples
11
+ # import_visit.rb /Data/vtrak1/raw/alz_2000/alz001 johnson.alz.visit1 /path/to/the/rails/db/production.sqlite3
12
+ # import_visit.rb /Data/vtrak1/raw/wrap140/wrp001_5917_03042008 johnson.wrap140.visit1 /path/to/the/rails/db/production.sqlite3
13
+ #
14
+ # == Usage
15
+ # import_visit.rb <raw_data_directory> <scan_procedure_codename> <database_file>
16
+ #
17
+ # For help use: import_visit.rb -h
18
+ #
19
+ # == Options
20
+ # -h, --help Displays help message
21
+ # -v, --visit Visit raw data directory, absolute path
22
+ # -p, --scan_procedure scan_procedure codename, e.g. johnson.alz.visit1
23
+ # -d, --database Database file into which information will imported
24
+ #
25
+ # == Author
26
+ # K.J. Kosmatka, kk4@medicine.wisc.edu
27
+ #
28
+ # == Copyright
29
+ # Copyright (c) 2009 WADRC Imaging Core.
30
+ #
31
+
32
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
33
+
34
+ require 'visit_raw_data_directory'
35
+ require 'pathname'
36
+ require 'rdoc/usage'
37
+ require 'logger'
38
+
39
+ # == Function
40
+ # Imports imaging data collected during a single visit into the WADRC Data Tools web application database.
41
+ #
42
+ # == Usage
43
+ # import_visit(raw_directory, scan_procedure_codename, database)
44
+ #
45
+ # == Example
46
+ # import_visit('/Data/vtrak1/raw/alz_2000/alz001','johnson.alz.visit1','/path/to/the/rails/db/production.sqlite3')
47
+ #
48
+ def import_visit(raw_directory, scan_procedure_codename, database)
49
+ log = Logger.new(File.basename(raw_directory))
50
+ v = VisitRawDataDirectory.new(raw_directory, scan_procedure_codename)
51
+ puts "+++ Importing #{v.visit_directory} as part of #{v.scan_procedure_name} +++"
52
+ begin
53
+ v.scan
54
+ v.db_insert!(database)
55
+ rescue Exception => e
56
+ puts "There was a problem scanning a dataset in #{v.visit_directory}... skipping."
57
+ puts "Exception message: #{e.message}"
58
+ log.error "There was a problem scanning a dataset in #{v.visit_directory}... skipping."
59
+ log.error "Exception message: #{e.message}"
60
+ ensure
61
+ v = nil
62
+ end
63
+ end
64
+
65
+
66
+
67
+ if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
68
+ RDoc::usage() if (ARGV[0] == '-h' or ARGV.size != 3)
69
+ raw_directory = ARGV[0]
70
+ scan_procedure_codename = ARGV[1]
71
+ database = ARGV[2]
72
+ raise(IOError, "Database #{database} not writable or doesn't exist.") unless File.writable?(database)
73
+ import_visit(raw_directory, scan_procedure_codename, database)
74
+ end
@@ -0,0 +1,6 @@
1
+ require 'raw_image_file'
2
+ require 'raw_image_dataset'
3
+ require 'visit_raw_data_directory'
4
+
5
+ module Metamri
6
+ end
@@ -0,0 +1,33 @@
1
+ require 'mysql'
2
+
3
+ class Mysql
4
+ def summary
5
+ self.list_tables.each do |tbl|
6
+ next if tbl =~ /^tws/
7
+ puts "+" * 160
8
+ puts "%80s" % tbl
9
+ puts "+" * 160
10
+ columns = self.query("select * from #{tbl}").fetch_hash.keys
11
+ columns.in_chunks_of(6).each do |chunk|
12
+ puts "%-25s " * chunk.size % chunk
13
+ end
14
+ puts "\n\n"
15
+ end
16
+ end
17
+ end
18
+
19
+
20
+
21
+ class Array
22
+ def chunks(number_of_chunks)
23
+ chunks_of( (self.size/number_of_chunks.to_f).ceil )
24
+ end
25
+ def in_chunks_of(chunk_size)
26
+ nchunks = (self.size/chunk_size.to_f).ceil
27
+ chunks = Array.new(nchunks) { [] }
28
+ self.each_with_index do |item,index|
29
+ chunks[ index/chunk_size ] << item
30
+ end
31
+ return chunks
32
+ end
33
+ end
@@ -0,0 +1,147 @@
1
+
2
+ require 'rubygems'
3
+ require 'sqlite3'
4
+
5
+ =begin rdoc
6
+ A #Dataset defines a single 3D or 4D image, i.e. either a volume or a time series
7
+ of volumes. This encapsulation will provide easy manipulation of groups of raw
8
+ image files including basic reconstruction.
9
+ =end
10
+ class RawImageDataset
11
+
12
+ # The directory that contains all the raw images and related files that make up
13
+ # this data set.
14
+ attr_reader :directory
15
+ # An array of #RawImageFile objects that compose the complete data set.
16
+ attr_reader :raw_image_files
17
+ # From the first raw image file in the dataset
18
+ attr_reader :series_description
19
+ # From the first raw image file in the dataset
20
+ attr_reader :rmr_number
21
+ # From the first raw image file in the dataset
22
+ attr_reader :timestamp
23
+ # A key string unique to a dataset composed of the rmr number and the timestamp.
24
+ attr_reader :dataset_key
25
+ # the file scanned
26
+ attr_reader :scanned_file
27
+ # the scanner source
28
+ attr_reader :scanner_source
29
+
30
+
31
+ =begin rdoc
32
+ * dir: The directory containing the files.
33
+ * files: An array of #RawImageFile objects that compose the complete data set.
34
+
35
+ Initialization raises errors in several cases:
36
+ * directory doesn't exist => IOError
37
+ * any of the raw image files is not actually a RawImageFile => IndexError
38
+ * series description, rmr number, or timestamp cannot be extracted from the first RawImageFile => IndexError
39
+ =end
40
+ def initialize(directory, raw_image_files)
41
+ @directory = File.expand_path(directory)
42
+ raise(IOError, "#{@directory} not found.") if not File.directory?(@directory)
43
+ raise(IOError, "No raw image files supplied.") if (raw_image_files.nil? or raw_image_files.empty?)
44
+ raw_image_files.each do |im|
45
+ raise(IndexError, im.to_s + " is not a RawImageFile") if im.class.to_s != "RawImageFile"
46
+ end
47
+ @raw_image_files = raw_image_files
48
+ @series_description = @raw_image_files.first.series_description
49
+ raise(IndexError, "No series description found") if @series_description.nil?
50
+ @rmr_number = @raw_image_files.first.rmr_number
51
+ raise(IndexError, "No rmr found") if @rmr_number.nil?
52
+ @timestamp = get_earliest_timestamp
53
+ raise(IndexError, "No timestamp found") if @timestamp.nil?
54
+ @dataset_key = @rmr_number + "::" + @timestamp.to_s
55
+ @scanned_file = @raw_image_files.first.filename
56
+ raise(IndexError, "No scanned file found") if @scanned_file.nil?
57
+ @scanner_source = @raw_image_files.first.source
58
+ raise(IndexError, "No scanner source found") if @scanner_source.nil?
59
+ end
60
+
61
+ =begin rdoc
62
+ Generates an SQL insert statement for this dataset that can be used to populate
63
+ the Johnson Lab rails TransferScans application database backend. The motivation
64
+ for this is that many dataset inserts can be collected into one db transaction
65
+ at the visit level, or even higher when doing a whole file system scan.
66
+ =end
67
+ def db_insert(visit_id)
68
+ "INSERT INTO image_datasets
69
+ (rmr, series_description, path, timestamp, created_at, updated_at, visit_id,
70
+ glob, rep_time, bold_reps, slices_per_volume, scanned_file)
71
+ VALUES ('#{@rmr_number}', '#{@series_description}', '#{@directory}', '#{@timestamp.to_s}', '#{DateTime.now}',
72
+ '#{DateTime.now}', '#{visit_id}', '#{self.glob}', '#{@raw_image_files.first.rep_time}',
73
+ '#{@raw_image_files.first.bold_reps}', '#{@raw_image_files.first.num_slices}', '#{@scanned_file}')"
74
+ end
75
+
76
+ def db_update(dataset_id)
77
+ "UPDATE image_datasets SET
78
+ rmr = '#{@rmr_number}',
79
+ series_description = '#{@series_description}',
80
+ path = '#{@directory}',
81
+ timestamp = '#{@timestamp.to_s}',
82
+ updated_at = '#{DateTime.now.to_s}',
83
+ glob = '#{self.glob}',
84
+ rep_time = '#{@raw_image_files.first.rep_time}',
85
+ bold_reps = '#{@raw_image_files.first.bold_reps}',
86
+ slices_per_volume = '#{@raw_image_files.first.num_slices}',
87
+ scanned_file = '#{@scanned_file}'
88
+ WHERE id = '#{dataset_id}'"
89
+ end
90
+
91
+ def db_fetch
92
+ "SELECT * FROM image_datasets
93
+ WHERE rmr = '#{@rmr_number}'
94
+ AND path = '#{@directory}'
95
+ AND timestamp LIKE '#{@timestamp.to_s.split(/\+|Z/).first}%'"
96
+ end
97
+
98
+ def attributes_for_active_record
99
+ { :rmr => @rmr_number,
100
+ :series_description => @series_description,
101
+ :path => @directory,
102
+ :timestamp => @timestamp.to_s,
103
+ :glob => glob,
104
+ :rep_time => @raw_image_files.first.rep_time,
105
+ :bold_reps => @raw_image_files.first.bold_reps,
106
+ :slices_per_volume => @raw_image_files.first.num_slices,
107
+ :scanned_file => @scanned_file }
108
+ end
109
+
110
+
111
+
112
+ =begin rdoc
113
+ Returns a globbing wildcard that is used by to3D to gather files for
114
+ reconstruction. If no compatible glob is found for the data set, nil is returned.
115
+ This is always the case for pfiles. For example if the first file in a data set is I.001, then:
116
+ <tt>dataset.glob</tt>
117
+ <tt>=> "I.*"</tt>
118
+ including the quotes, which are necessary becuase some data sets (functional dicoms)
119
+ have more component files than shell commands can handle.
120
+ =end
121
+ def glob
122
+ case @raw_image_files.first.filename
123
+ when /^E.*dcm$/
124
+ return 'E*.dcm'
125
+ when /\.dcm$/
126
+ return '*.dcm'
127
+ when /^I\./
128
+ return 'I.*'
129
+ when /^I/
130
+ return 'I*.dcm'
131
+ when /\.0/
132
+ return '*.0*'
133
+ else
134
+ return nil
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ # Gets the earliest timestamp among the raw image files in this dataset.
141
+ def get_earliest_timestamp
142
+ @timestamp = (@raw_image_files.sort_by { |i| i.timestamp }).first.timestamp
143
+ end
144
+
145
+
146
+ end
147
+ #### END OF CLASS ####