brainmap-metamri 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest ADDED
@@ -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
data/README.rdoc ADDED
@@ -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
data/Rakefile ADDED
@@ -0,0 +1,18 @@
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 }
@@ -0,0 +1,161 @@
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_visit.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__),'..')
31
+
32
+ require 'visit_raw_data_directory'
33
+ require 'pathname'
34
+ require 'logger'
35
+
36
+ #:stopdoc:
37
+ STUDIES = {
38
+ :alz_1 => { :dir => '/Data/vtrak1/raw/alz_2000',
39
+ :logfile => 'alz.visit1.scan.log',
40
+ :filter => /^alz...$|^alz..._[AB]/i,
41
+ :codename => 'johnson.alz.visit1'
42
+ },
43
+ :alz_2 => { :dir => '/Data/vtrak1/raw/alz_2000',
44
+ :logfile => 'alz.visit2.scan.log',
45
+ :filter => /^alz..._2$/,
46
+ :codename => 'johnson.alz.visit2'
47
+ },
48
+ :cms_wais => { :dir => '/Data/vtrak1/raw/cms/wais',
49
+ :logfile => 'cms.wais.scan.log',
50
+ :filter => /^pc/,
51
+ :codename => 'johnson.cms.visit1.wais'
52
+ },
53
+ :cms_uwmr => { :dir => '/Data/vtrak1/raw/cms/uwmr',
54
+ :logfile => 'cms.uwmr.scan.log',
55
+ :filter => /^cms...$/,
56
+ :codename => 'johnson.cms.visit1.uwmr'
57
+ },
58
+ :esprit_1 => { :dir => '/Data/vtrak1/raw/esprit/baseline',
59
+ :logfile => 'esprit.baseline.scan.log',
60
+ :filter => /^esp3/,
61
+ :codename => 'carlsson.esprit.visit1.baseline'
62
+ },
63
+ :esprit_2 => { :dir => '/Data/vtrak1/raw/esprit/9month',
64
+ :logfile => 'esprit.9month.scan.log',
65
+ :filter => /^esp3/,
66
+ :codename => 'carlsson.esprit.visit2.9month'
67
+ },
68
+ :gallagher_pd => { :dir => '/Data/vtrak1/raw/gallagher_pd',
69
+ :logfile => 'gallagher.scan.log',
70
+ :filter => /^pd..._/,
71
+ :codename => 'gallagher.pd.visit1'
72
+ },
73
+ :pib_pilot => { :dir => '/Data/vtrak1/raw/pib_pilot_mri',
74
+ :logfile => 'pib.mri.pilot.scan.log',
75
+ :filter => /^cpr0/,
76
+ :codename => 'johnson.pibmripilot.visit1.uwmr'
77
+ },
78
+ :ries_1 => { :dir => '/Data/vtrak1/raw/ries.aware.visit1',
79
+ :logfile => 'ries.aware.visit1.scan.log',
80
+ :filter => /^awr0/,
81
+ :codename => 'ries.aware.visit1'
82
+ },
83
+ :ries_pilot => { :dir => '/Data/vtrak1/raw/ries.aware.visit1',
84
+ :logfile => 'ries.aware.pilot.scan.log',
85
+ :filter => /^awrP/,
86
+ :codename => 'ries.aware.pilot'
87
+ },
88
+ :tbi1000_1 => { :dir => '/Data/vtrak1/raw/tbi_1000',
89
+ :logfile => 'tbi1000.visit1.scan.log',
90
+ :filter => /^tbi...$/,
91
+ :codename => 'johnson.tbi1000.visit1'
92
+ },
93
+ :tbi1000_2 => { :dir => '/Data/vtrak1/raw/tbi_1000',
94
+ :logfile => 'tbi1000.visit2.scan.log',
95
+ :filter => /^tbi..._2/,
96
+ :codename => 'johnson.tbi1000.visit2'
97
+ },
98
+ :tbi1000_3 => { :dir => '/Data/vtrak1/raw/tbi_aware',
99
+ :logfile => 'tbiaware.visit3.scan.log',
100
+ :filter => /^tbi..._3$/,
101
+ :codename => 'johnson.tbiaware.visit3'
102
+ },
103
+ :tbiva => { :dir => '/Data/vtrak1/raw/johnson.tbi-va.visit1',
104
+ :logfile => 'tbiva.scan.log',
105
+ :filter => /^tbi/,
106
+ :codename => 'johnson.tbiva.visit1'
107
+ },
108
+ :wrap140 => { :dir => '/Data/vtrak1/raw/wrap140',
109
+ :logfile => 'wrap140.scan.log',
110
+ :filter => /^wrp/,
111
+ :codename => 'johnson.wrap140.visit1'
112
+ }
113
+ }
114
+ #:startdoc:
115
+
116
+
117
+ # == Function
118
+ # Imports an entire study.
119
+ #
120
+ # == Arguments
121
+ # study -- a hash specifying the following keys:
122
+ # :dir => the directory holding all the individual visit directories for this study
123
+ # :logfile => a file name where logging can be written
124
+ # :filter => a regex that matches all of the visit directory names that should be scanned
125
+ # :codename => the study codename, e.g. 'johnson.alz.visit1'
126
+ #
127
+ # dbfile -- the database into which meta-data will be inserted
128
+ #
129
+ def import_study(study, dbfile)
130
+ studydir = Pathname.new(study[:dir])
131
+ log = Logger.new(study[:logfile], shift_age = 7, shift_size = 1048576)
132
+
133
+ studydir.entries.each do |visit|
134
+ next if visit.to_s =~ /^\./
135
+ next unless visit.to_s =~ study[:filter]
136
+ visitdir = studydir + visit
137
+ v = VisitRawDataDirectory.new( visitdir.to_s, study[:codename] )
138
+ begin
139
+ v.scan
140
+ v.db_insert!(dbfile)
141
+ rescue Exception => e
142
+ puts "There was a problem scanning a dataset in #{visitdir}... skipping."
143
+ puts "Exception message: #{e.message}"
144
+ LOG.error "There was a problem scanning a dataset in #{visitdir}... skipping."
145
+ LOG.error "Exception message: #{e.message}"
146
+ ensure
147
+ v = nil
148
+ end
149
+ end
150
+ end
151
+
152
+
153
+
154
+ if __FILE__ == $0
155
+ study = STUDIES[ARGV[0].to_sym]
156
+ dbfile = ARGV[1]
157
+ import_study(study, dbfile)
158
+ end
159
+
160
+
161
+
@@ -0,0 +1,73 @@
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__),'..')
33
+
34
+ require 'visit_raw_data_directory'
35
+ require 'pathname'
36
+ require 'rdoc/usage'
37
+
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
+ v = VisitRawDataDirectory.new(raw_directory, scan_procedure_codename)
50
+ puts "+++ Importing #{v.visit_directory} as part of #{v.scan_procedure_name} +++"
51
+ begin
52
+ v.scan
53
+ puts v
54
+ v.db_insert!(database)
55
+ rescue Exception => e
56
+ puts "There was a problem scanning a dataset in #{visitdir}... skipping."
57
+ puts "Exception message: #{e.message}"
58
+ LOG.error "There was a problem scanning a dataset in #{visitdir}... skipping."
59
+ LOG.error "Exception message: #{e.message}"
60
+ ensure
61
+ v = nil
62
+ end
63
+ end
64
+
65
+
66
+
67
+ if __FILE__ == $0
68
+ RDoc::usage() if ARGV[0] == '-h'
69
+ raw_directory = ARGV[0]
70
+ scan_procedure_codename = ARGV[1]
71
+ database = ARGV[2]
72
+ import_visit(raw_directory, scan_procedure_codename, database)
73
+ end
data/lib/metamri.rb ADDED
@@ -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 ####