brainmap-ImageData 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,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 ####