metamri 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 ####