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)


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
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/metamri/raw_image_dataset.rb', line 65

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?

  @mri_coil_name  = @raw_image_files.first.mri_coil_name

  @mri_station_name  = @raw_image_files.first.mri_station_name

  @mri_manufacturer_model_name   = @raw_image_files.first.mri_manufacturer_model_name 
  
  $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

#mri_coil_nameObject (readonly)

head coil



51
52
53
# File 'lib/metamri/raw_image_dataset.rb', line 51

def mri_coil_name
  @mri_coil_name
end

#mri_manufacturer_model_nameObject (readonly)

mri model name



55
56
57
# File 'lib/metamri/raw_image_dataset.rb', line 55

def mri_manufacturer_model_name
  @mri_manufacturer_model_name
end

#mri_station_nameObject (readonly)

staion name



53
54
55
# File 'lib/metamri/raw_image_dataset.rb', line 53

def mri_station_name
  @mri_station_name
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



315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/metamri/raw_image_dataset.rb', line 315

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.



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/metamri/raw_image_dataset.rb', line 182

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,
    :mri_coil_name => @mri_coil_name, 
    :mri_station_name => @mri_station_name, 
    :mri_manufacturer_model_name => @mri_manufacturer_model_name
  }.merge attrs
end

#create_thumbnailObject



213
214
215
216
# File 'lib/metamri/raw_image_dataset.rb', line 213

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

#db_fetchObject



172
173
174
175
176
177
# File 'lib/metamri/raw_image_dataset.rb', line 172

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.



148
149
150
151
152
153
154
155
# File 'lib/metamri/raw_image_dataset.rb', line 148

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



157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/metamri/raw_image_dataset.rb', line 157

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)


366
367
368
# File 'lib/metamri/raw_image_dataset.rb', line 366

def dicom?
  @raw_image_files.first.dicom?
end

#file_countObject



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

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
    elsif @raw_image_files.first.scan_archive_h5_json?
      @file_count = 1
    else raise "File not recognized as dicom or pfile or scan_archive_h5_json."
    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)


378
379
380
# File 'lib/metamri/raw_image_dataset.rb', line 378

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.



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/metamri/raw_image_dataset.rb', line 276

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)


372
373
374
# File 'lib/metamri/raw_image_dataset.rb', line 372

def pfile?
  @raw_image_files.first.pfile?
end

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



133
134
135
136
137
138
139
# File 'lib/metamri/raw_image_dataset.rb', line 133

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).



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/metamri/raw_image_dataset.rb', line 333

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
    when 'scan_archive_h5_json'
      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

#scan_archive_h5_json?Boolean

Returns:

  • (Boolean)


382
383
384
# File 'lib/metamri/raw_image_dataset.rb', line 382

def scan_archive_h5_json?
    @raw_image_files.first.scan_archive_h5_json?
end

#series_detailsObject

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



360
361
362
# File 'lib/metamri/raw_image_dataset.rb', line 360

def series_details
  @series_description
end

#thumbnail_for_active_recordObject



218
219
220
221
222
# File 'lib/metamri/raw_image_dataset.rb', line 218

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.



230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/metamri/raw_image_dataset.rb', line 230

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.



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/metamri/raw_image_dataset.rb', line 248

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