Class: RawImageDataset

Inherits:
Object
  • Object
show all
Defined in:
lib/metamri/raw_image_dataset.rb

Overview

A #RawImageDataset defines a single 3D or 4D image, i.e. either a volume or a time series of volumes. This encapsulation will provide easy manipulation of groups of raw image files including basic reconstruction.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(directory, raw_image_files) ⇒ RawImageDataset

  • dir: The directory containing the files.

  • files: An array of #RawImageFile objects that compose the complete data set.

Initialization raises errors in several cases:

  • directory doesn’t exist => IOError

  • any of the raw image files is not actually a RawImageFile => IndexError

  • series description, rmr number, or timestamp cannot be extracted from the first RawImageFile => IndexError

Raises:

  • (IOError)


59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/metamri/raw_image_dataset.rb', line 59

def initialize(directory, raw_image_files)    
  @read_errors = Array.new
  @directory = File.expand_path(directory)
  raise(IOError, "#{@directory} not found.") if not File.directory?(@directory)
  raise(IOError, "No raw image files supplied.") unless raw_image_files
  
  # If only a single raw_image_file was supplied, put it into an array for processing.
  raw_image_files = [raw_image_files] if raw_image_files.class.to_s == "RawImageFile"

  raw_image_files.each do |im|
    raise(IndexError, im.to_s + " is not a RawImageFile") if im.class.to_s != "RawImageFile"
  end
  @raw_image_files = raw_image_files
  
  @series_description = @raw_image_files.first.series_description
  validates_metainfo_for :series_description, :msg => "No series description found"
  
  @rmr_number = @raw_image_files.first.rmr_number
  raise(IndexError, "No rmr found") if @rmr_number.nil?
  
  @timestamp = get_earliest_timestamp
  raise(IndexError, "No timestamp found") if @timestamp.nil?
  
  @dataset_key = @rmr_number + "::" + @timestamp.to_s

  @scanned_file = @raw_image_files.first.filename
  raise(IndexError, "No scanned file found") if @scanned_file.nil?
  
  @scanner_source = @raw_image_files.first.source
  raise(IndexError, "No scanner source found") if @scanner_source.nil?
      
  @exam_number = @raw_image_files.first.exam_number.nil? ? nil : @raw_image_files.first.exam_number
  validates_metainfo_for :exam_number, :msg => "No study id / exam number found", :optional => true
  
  @study_description = @raw_image_files.first.study_description
  validates_metainfo_for :study_description, :msg => "No study description found" if dicom?
  
  @protocol_name = @raw_image_files.first.protocol_name
  validates_metainfo_for :protocol_name, :msg => "No protocol name found" if dicom?
  
  @operator_name = @raw_image_files.first.operator_name
  validates_metainfo_for :operator_name, :optional => true if dicom?
  
  @patient_name = @raw_image_files.first.patient_name
  validates_metainfo_for :patient_name if dicom?
  
  @dicom_series_uid = @raw_image_files.first.dicom_series_uid
  validates_metainfo_for :dicom_series_uid if dicom?
  
  @dicom_study_uid = @raw_image_files.first.dicom_study_uid
  validates_metainfo_for :dicom_study_uid if dicom?
  
  @dicom_taghash = @raw_image_files.first.dicom_taghash
  validates_metainfo_for :dicom_taghash if dicom?
  
  @image_uid  = @raw_image_files.first.image_uid
  validates_metainfo_for :image_uid if pfile?
  
  $LOG ||= Logger.new(STDOUT)
end

Instance Attribute Details

#dataset_keyObject (readonly)

A key string unique to a dataset composed of the rmr number and the timestamp.



27
28
29
# File 'lib/metamri/raw_image_dataset.rb', line 27

def dataset_key
  @dataset_key
end

#dicom_series_uidObject (readonly)

DICOM Series UID



43
44
45
# File 'lib/metamri/raw_image_dataset.rb', line 43

def dicom_series_uid
  @dicom_series_uid
end

#dicom_study_uidObject (readonly)

DICOM Study UID



45
46
47
# File 'lib/metamri/raw_image_dataset.rb', line 45

def dicom_study_uid
  @dicom_study_uid
end

#dicom_taghashObject (readonly)

Tag Hash of DICOM Keys



47
48
49
# File 'lib/metamri/raw_image_dataset.rb', line 47

def dicom_taghash
  @dicom_taghash
end

#directoryObject (readonly)

The directory that contains all the raw images and related files that make up this data set.



15
16
17
# File 'lib/metamri/raw_image_dataset.rb', line 15

def directory
  @directory
end

#exam_numberObject (readonly)

From the first raw image file in the dataset



25
26
27
# File 'lib/metamri/raw_image_dataset.rb', line 25

def exam_number
  @exam_number
end

#operator_nameObject (readonly)

Scan Tech Initials



39
40
41
# File 'lib/metamri/raw_image_dataset.rb', line 39

def operator_name
  @operator_name
end

#patient_nameObject (readonly)

Patient “Name”, usually StudyID or ENUM



41
42
43
# File 'lib/metamri/raw_image_dataset.rb', line 41

def patient_name
  @patient_name
end

#protocol_nameObject (readonly)

A Description of the Protocol as listed in the DICOM Header



37
38
39
# File 'lib/metamri/raw_image_dataset.rb', line 37

def protocol_name
  @protocol_name
end

#raw_image_filesObject (readonly)

An array of #RawImageFile objects that compose the complete data set.



17
18
19
# File 'lib/metamri/raw_image_dataset.rb', line 17

def raw_image_files
  @raw_image_files
end

#read_errorsObject (readonly)

Array of Read Error Strings



49
50
51
# File 'lib/metamri/raw_image_dataset.rb', line 49

def read_errors
  @read_errors
end

#rmr_numberObject (readonly)

From the first raw image file in the dataset



21
22
23
# File 'lib/metamri/raw_image_dataset.rb', line 21

def rmr_number
  @rmr_number
end

#scanned_fileObject (readonly)

the file scanned



29
30
31
# File 'lib/metamri/raw_image_dataset.rb', line 29

def scanned_file
  @scanned_file
end

#scanner_sourceObject (readonly)

the scanner source



31
32
33
# File 'lib/metamri/raw_image_dataset.rb', line 31

def scanner_source
  @scanner_source
end

#series_descriptionObject (readonly)

From the first raw image file in the dataset



19
20
21
# File 'lib/metamri/raw_image_dataset.rb', line 19

def series_description
  @series_description
end

#study_descriptionObject (readonly)

A Description of the Study as listed in the DICOM Header



35
36
37
# File 'lib/metamri/raw_image_dataset.rb', line 35

def study_description
  @study_description
end

#thumbnailObject (readonly)

A #RawImageDatasetThumbnail object that composes the thumbnail for the dataset.



33
34
35
# File 'lib/metamri/raw_image_dataset.rb', line 33

def thumbnail
  @thumbnail
end

#timestampObject (readonly)

From the first raw image file in the dataset



23
24
25
# File 'lib/metamri/raw_image_dataset.rb', line 23

def timestamp
  @timestamp
end

Class Method Details

.to_table(datasets) ⇒ Object

Creates an Hirb Table for pretty output of dataset info. It takes an array of either RawImageDatasets or RawImageDatasetResources



298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/metamri/raw_image_dataset.rb', line 298

def self.to_table(datasets)
  if datasets.first.class.to_s == "RawImageDatasetResource"
    datasets = datasets.map { |ds| ds.to_metamri_image_dataset }
  end
  
  Hirb::Helpers::AutoTable.render(
    datasets.sort_by{ |ds| [ds.timestamp, File.basename(ds.directory)] }, 
    :headers => { :relative_dataset_path => 'Dataset', :series_details => 'Series Details', :file_count => 'File Count'}, 
    :fields => [:relative_dataset_path, :series_details, :file_count],
    :description => false # Turn off rendering row count description at bottom.
  )
    
end

Instance Method Details

#attributes_for_active_record(options = {}) ⇒ Object

Returns a hash of attributes used for insertion into active record. Options: :thumb => FileHandle to thumbnail includes a thumbnail.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/metamri/raw_image_dataset.rb', line 170

def attributes_for_active_record(options = {})
  attrs = {}
  
  # If the thumbnail is present and valid, add it to the hash.
  # Otherwise don't add the key, or paperclip will delete the attachments (it deletes when given nil)
  if options.has_key?(:thumb)
    thumbnail = options[:thumb]
    unless (thumbnail.class == File || thumbnail == nil)
      raise(IOError, "Thumbnail #{options[:thumb]} must be a #File instead of #{thumbnail.class}.")
    end
    attrs[:thumbnail] = thumbnail
  end

  { :rmr => @rmr_number,
    :series_description => @series_description,
    :path => @directory,
    :timestamp => @timestamp.to_s,
    :glob => glob,
    :rep_time => @raw_image_files.first.rep_time,
    :bold_reps => @raw_image_files.first.bold_reps,
    :slices_per_volume => @raw_image_files.first.num_slices,
    :scanned_file => @scanned_file,
    :dicom_series_uid => @dicom_series_uid,
    :dicom_taghash => @dicom_taghash,
    :image_uid => @image_uid
  }.merge attrs
end

#create_thumbnailObject



198
199
200
201
# File 'lib/metamri/raw_image_dataset.rb', line 198

def create_thumbnail
  @thumbnail = RawImageDatasetThumbnail.new(self)
  @thumbnail.create_thumbnail
end

#db_fetchObject



160
161
162
163
164
165
# File 'lib/metamri/raw_image_dataset.rb', line 160

def db_fetch
  "SELECT * FROM image_datasets 
   WHERE rmr = '#{@rmr_number}' 
   AND path = '#{@directory}' 
   AND timestamp LIKE '#{@timestamp.to_s.split(/\+|Z/).first}%'"
end

#db_insert(visit_id) ⇒ Object

Generates an SQL insert statement for this dataset that can be used to populate the Johnson Lab rails TransferScans application database backend. The motivation for this is that many dataset inserts can be collected into one db transaction at the visit level, or even higher when doing a whole file system scan.



136
137
138
139
140
141
142
143
# File 'lib/metamri/raw_image_dataset.rb', line 136

def db_insert(visit_id)
  "INSERT INTO image_datasets
  (rmr, series_description, path, timestamp, created_at, updated_at, visit_id, 
  glob, rep_time, bold_reps, slices_per_volume, scanned_file, 'dicom_study_uid')
  VALUES ('#{@rmr_number}', '#{@series_description}', '#{@directory}', '#{@timestamp.to_s}', '#{DateTime.now}', 
  '#{DateTime.now}', '#{visit_id}', '#{self.glob}', '#{@raw_image_files.first.rep_time}', 
  '#{@raw_image_files.first.bold_reps}', '#{@raw_image_files.first.num_slices}', '#{@scanned_file}' )"
end

#db_update(dataset_id) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/metamri/raw_image_dataset.rb', line 145

def db_update(dataset_id)
  "UPDATE image_datasets SET
   rmr = '#{@rmr_number}',
   series_description = '#{@series_description}',
   path = '#{@directory}',
   timestamp = '#{@timestamp.to_s}',
   updated_at = '#{DateTime.now.to_s}',
   glob = '#{self.glob}',
   rep_time = '#{@raw_image_files.first.rep_time}',
   bold_reps = '#{@raw_image_files.first.bold_reps}',
   slices_per_volume = '#{@raw_image_files.first.num_slices}',
   scanned_file = '#{@scanned_file}'
   WHERE id = '#{dataset_id}'"
end

#dicom?Boolean

Helper predicate method to check whether the dataset is a DICOM dataset or not. This just sends dicom? to the first raw file in the dataset.

Returns:

  • (Boolean)


342
343
344
# File 'lib/metamri/raw_image_dataset.rb', line 342

def dicom?
  @raw_image_files.first.dicom?
end

#file_countObject



284
285
286
287
288
289
290
291
292
293
294
# File 'lib/metamri/raw_image_dataset.rb', line 284

def file_count
  unless @file_count
    if @raw_image_files.first.dicom? or @raw_image_files.first.geifile?
      @file_count = Dir.open(@directory).reject{ |branch| /(^\.|.yaml$)/.match(branch) }.length
    elsif @raw_image_files.first.pfile?
      @file_count = 1
    else raise "File not recognized as dicom or pfile."
    end
  end
  return @file_count
end

#geifile?Boolean

Helper predicate method to check whether the dataset is a DICOM dataset or not. This just sends dicom? to the first raw file in the dataset.

Returns:

  • (Boolean)


354
355
356
# File 'lib/metamri/raw_image_dataset.rb', line 354

def geifile?
  @raw_image_files.first.geifile?
end

#globObject

Returns a globbing wildcard that is used by to3D to gather files for reconstruction. If no compatible glob is found for the data set, nil is returned. This is always the case for pfiles. For example if the first file in a data set is I.001, then: dataset.glob => "I.*" including the quotes, which are necessary becuase some data sets (functional dicoms) have more component files than shell commands can handle.



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/metamri/raw_image_dataset.rb', line 261

def glob
  case @raw_image_files.first.filename
  when /^E.*dcm$/
    return 'E*.dcm'
  when /\.dcm$/
    return '*.dcm'
  when /^I\./
    return 'I.*'
  when /^I/
    return 'I*.dcm'
  when /.*\.\d{3,4}/
    return '*.[0-9]*'
  when /\.0/
    return '*.0*'
  else
    return nil
  end
  # Note - To exclude just yaml files we could also just use the bash glob
  # '!(*.yaml), but we would have to list all exclusions.  This may turn
  # out easier in the long run.
end

#pfile?Boolean

Helper predicate method to check whether the dataset is a DICOM dataset or not. This just sends dicom? to the first raw file in the dataset.

Returns:

  • (Boolean)


348
349
350
# File 'lib/metamri/raw_image_dataset.rb', line 348

def pfile?
  @raw_image_files.first.pfile?
end

Prints a “success” dot or error mesage if any errors in @read_errors.



121
122
123
124
125
126
127
# File 'lib/metamri/raw_image_dataset.rb', line 121

def print_scan_status
  if @read_errors.empty?
    print "."; STDOUT.flush
  else
    puts @read_errors.join("; ")
  end
end

#relative_dataset_path(visit_dir = nil) ⇒ Object

Returns a relative filepath to the dataset. Handles dicoms by returning the dataset directory, and pfiles by returning either the pfile filename or, if passed a visit directory, the relative path from the visit directory to the pfile (i.e. P00000.7 or raw/P00000.7).



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/metamri/raw_image_dataset.rb', line 316

def relative_dataset_path(visit_dir = nil)
  image_file = @raw_image_files.first
  case image_file.file_type
    when 'dicom', 'geifile'
      relative_dataset_path = File.basename(directory)
    when 'pfile'
      full_dataset_path = Pathname.new(File.join(directory, image_file.filename))
      if visit_dir
        relative_dataset_path = full_dataset_path.relative_path_from(visit_dir)
      else
        relative_dataset_path = image_file.filename
      end
    else raise "Cannot identify #{@raw_image_files.first.filename}"
  end
  
  return relative_dataset_path
end

#series_detailsObject

Reports series details, including description and possibly image quality check comments for #RawImageDatasetResource objects.



336
337
338
# File 'lib/metamri/raw_image_dataset.rb', line 336

def series_details
  @series_description
end

#thumbnail_for_active_recordObject



203
204
205
206
207
# File 'lib/metamri/raw_image_dataset.rb', line 203

def thumbnail_for_active_record
  # Ensure a thumbnail has been created.
  create_thumbnail unless @thumbnail
  return File.open(@thumbnail.path)
end

#to_nifti(nifti_output_directory, nifti_filename, input_options = {}) ⇒ Object

Implements an api for changing image datasets into usable nifti files. Pass in an output path and filename. The to3d code is applied as a mixed-in module. Returns the to3d command that creates the specified options.



215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/metamri/raw_image_dataset.rb', line 215

def to_nifti(nifti_output_directory, nifti_filename, input_options = {} )
  
  # Handle the business logic for choosing the right Nifti Builder here.
  # Currently just extend the default unknown builder, since that's the only one that exists.
  if true
    nifti_output_directory = File.join(nifti_output_directory, 'unknown') if input_options[:append_modality_directory]
    extend(UnknownImageDataset)
  end
  
  nifti_conversion_command, nifti_output_file = self.dataset_to_nifti(nifti_output_directory, nifti_filename, input_options)
  return nifti_conversion_command, nifti_output_file
end

#to_nifti!(nifti_output_directory, nifti_filename, input_options = {}) ⇒ Object

Uses to3d to create the nifti file as specified by to_nifti.

Returns a path to the created dataset as a string if successful.



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/metamri/raw_image_dataset.rb', line 233

def to_nifti!(nifti_output_directory, nifti_filename, input_options = {} )
  begin 
    nifti_conversion_command, nifti_output_file = to_nifti(nifti_output_directory, nifti_filename, input_options)
    puts nifti_conversion_command
    begin
      system "#{nifti_conversion_command}"
      raise ScriptError, "#{nifti_output_file} does not exist." unless File.exist?(nifti_output_file)
    rescue ScriptError => e
      input_options[:no_timing_options] = true
      nifti_conversion_command, nifti_output_file = to_nifti(nifti_output_directory, nifti_filename, input_options)
      system "#{nifti_conversion_command}"
    end
    raise(IOError, "Could not convert image dataset: #{@directory} to #{nifti_output_file}") unless $? == 0
  rescue IOError => e
    $LOG.warn "-- Warning: #{e.message}"
  end
  return nifti_conversion_command, nifti_output_file
end