brainmap-ImageData 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{ImageData}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Kristopher J. Kosmatka"]
9
+ s.date = %q{2009-08-06}
10
+ s.description = %q{Extraction of MRI metadata and insertion into compatible sqlite3 databases.}
11
+ s.email = %q{kk4@medicine.wisc.edu}
12
+ s.extra_rdoc_files = ["lib/CLUs/import_study.rb", "lib/CLUs/import_visit.rb", "lib/mysql_tools.rb", "lib/raw_image_dataset.rb", "lib/raw_image_file.rb", "lib/series_description.rb", "lib/visit_raw_data_directory.rb", "LICENSE", "README.rdoc"]
13
+ s.files = ["lib/CLUs/import_study.rb", "lib/CLUs/import_visit.rb", "lib/mysql_tools.rb", "lib/raw_image_dataset.rb", "lib/raw_image_file.rb", "lib/series_description.rb", "lib/visit_raw_data_directory.rb", "LICENSE", "Manifest", "Rakefile", "README.rdoc", "test/raw_image_dataset_test.rb", "test/raw_image_file_test.rb", "test/visit_duplication_test.rb", "test/visit_test.rb", "ImageData.gemspec"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/brainmap/ImageData}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "ImageData", "--main", "README.rdoc"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{imagedata}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{Extraction of MRI metadata and insertion into compatible sqlite3 databases.}
21
+ s.test_files = ["test/raw_image_dataset_test.rb", "test/raw_image_file_test.rb", "test/visit_duplication_test.rb", "test/visit_test.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 2
26
+
27
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
+ else
29
+ end
30
+ else
31
+ end
32
+ end
data/LICENSE ADDED
@@ -0,0 +1,3 @@
1
+ == ImageData
2
+
3
+ Put appropriate LICENSE for your project here.
@@ -0,0 +1,15 @@
1
+ lib/CLUs/import_study.rb
2
+ lib/CLUs/import_visit.rb
3
+ lib/mysql_tools.rb
4
+ lib/raw_image_dataset.rb
5
+ lib/raw_image_file.rb
6
+ lib/series_description.rb
7
+ lib/visit_raw_data_directory.rb
8
+ LICENSE
9
+ Manifest
10
+ Rakefile
11
+ README.rdoc
12
+ test/raw_image_dataset_test.rb
13
+ test/raw_image_file_test.rb
14
+ test/visit_duplication_test.rb
15
+ 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,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('ImageData', '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/ImageData"
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
@@ -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,131 @@
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
+
28
+
29
+ =begin rdoc
30
+ * dir: The directory containing the files.
31
+ * files: An array of #RawImageFile objects that compose the complete data set.
32
+
33
+ Initialization raises errors in several cases:
34
+ * directory doesn't exist => IOError
35
+ * any of the raw image files is not actually a RawImageFile => IndexError
36
+ * series description, rmr number, or timestamp cannot be extracted from the first RawImageFile => IndexError
37
+ =end
38
+ def initialize(directory, raw_image_files)
39
+ @directory = File.expand_path(directory)
40
+ raise(IOError, "#{@directory} not found.") if not File.directory?(@directory)
41
+ raise(IOError, "No raw image files supplied.") if (raw_image_files.nil? or raw_image_files.empty?)
42
+ raw_image_files.each do |im|
43
+ raise(IndexError, im.to_s + " is not a RawImageFile") if im.class.to_s != "RawImageFile"
44
+ end
45
+ @raw_image_files = raw_image_files
46
+ @series_description = @raw_image_files.first.series_description
47
+ raise(IndexError, "No series description found") if @series_description.nil?
48
+ @rmr_number = @raw_image_files.first.rmr_number
49
+ raise(IndexError, "No rmr found") if @rmr_number.nil?
50
+ @timestamp = get_earliest_timestamp
51
+ raise(IndexError, "No timestamp found") if @timestamp.nil?
52
+ @dataset_key = @rmr_number + "::" + @timestamp.to_s
53
+ @scanned_file = @raw_image_files.first.filename
54
+ raise(IndexError, "No scanned file found") if @scanned_file.nil?
55
+ end
56
+
57
+ =begin rdoc
58
+ Generates an SQL insert statement for this dataset that can be used to populate
59
+ the Johnson Lab rails TransferScans application database backend. The motivation
60
+ for this is that many dataset inserts can be collected into one db transaction
61
+ at the visit level, or even higher when doing a whole file system scan.
62
+ =end
63
+ def db_insert(visit_id)
64
+ "INSERT INTO image_datasets
65
+ (rmr, series_description, path, timestamp, created_at, updated_at, visit_id,
66
+ glob, rep_time, bold_reps, slices_per_volume, scanned_file)
67
+ VALUES ('#{@rmr_number}', '#{@series_description}', '#{@directory}', '#{@timestamp.to_s}', '#{DateTime.now}',
68
+ '#{DateTime.now}', '#{visit_id}', '#{self.glob}', '#{@raw_image_files.first.rep_time}',
69
+ '#{@raw_image_files.first.bold_reps}', '#{@raw_image_files.first.num_slices}', '#{@scanned_file}')"
70
+ end
71
+
72
+ def db_update(dataset_id)
73
+ "UPDATE image_datasets SET
74
+ rmr = '#{@rmr_number}',
75
+ series_description = '#{@series_description}',
76
+ path = '#{@directory}',
77
+ timestamp = '#{@timestamp.to_s}',
78
+ updated_at = '#{DateTime.now.to_s}',
79
+ glob = '#{self.glob}',
80
+ rep_time = '#{@raw_image_files.first.rep_time}',
81
+ bold_reps = '#{@raw_image_files.first.bold_reps}',
82
+ slices_per_volume = '#{@raw_image_files.first.num_slices}',
83
+ scanned_file = '#{@scanned_file}'
84
+ WHERE id = '#{dataset_id}'"
85
+ end
86
+
87
+ def db_fetch
88
+ "SELECT * FROM image_datasets
89
+ WHERE rmr = '#{@rmr_number}'
90
+ AND path = '#{@directory}'
91
+ AND timestamp LIKE '#{@timestamp.to_s.split(/\+|Z/).first}%'"
92
+ end
93
+
94
+
95
+
96
+ =begin rdoc
97
+ Returns a globbing wildcard that is used by to3D to gather files for
98
+ reconstruction. If no compatible glob is found for the data set, nil is returned.
99
+ This is always the case for pfiles. For example if the first file in a data set is I.001, then:
100
+ <tt>dataset.glob</tt>
101
+ <tt>=> "I.*"</tt>
102
+ including the quotes, which are necessary becuase some data sets (functional dicoms)
103
+ have more component files than shell commands can handle.
104
+ =end
105
+ def glob
106
+ case @raw_image_files.first.filename
107
+ when /^E.*dcm$/
108
+ return 'E*.dcm'
109
+ when /\.dcm$/
110
+ return '*.dcm'
111
+ when /^I\./
112
+ return 'I.*'
113
+ when /^I/
114
+ return 'I*.dcm'
115
+ when /\.0/
116
+ return '*.0*'
117
+ else
118
+ return nil
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ # Gets the earliest timestamp among the raw image files in this dataset.
125
+ def get_earliest_timestamp
126
+ @timestamp = (@raw_image_files.sort_by { |i| i.timestamp }).first.timestamp
127
+ end
128
+
129
+
130
+ end
131
+ #### END OF CLASS ####