nifti 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  module NIFTI
2
2
  # The NObject class is the main class for interacting with the NIFTI object.
3
3
  # Reading from and writing to files is executed from instances of this class.
4
- #
4
+ #
5
5
  class NObject
6
6
  # An array which contain any notices/warnings/errors that have been recorded for the NObject instance.
7
7
  attr_reader :errors
@@ -65,9 +65,16 @@ module NIFTI
65
65
  elsif not string == nil
66
66
  raise ArgumentError, "Invalid argument. Expected String (or nil), got #{string.class}."
67
67
  end
68
-
69
68
  end
70
-
69
+
70
+ # Reopen the NIFTI File and retrieve image data, returning, if the retrieval was successful, it as an NImage object
71
+ def get_nimage
72
+ image = self.get_image
73
+ if !image.nil?
74
+ NImage.new(image, self.header["dim"])
75
+ end
76
+ end
77
+
71
78
  # Reopen the NIFTI File and retrieve image data
72
79
  def get_image
73
80
  r = NRead.new(@string, :image => true)
@@ -75,7 +82,7 @@ module NIFTI
75
82
  @image = r.image_rubyarray
76
83
  end
77
84
  end
78
-
85
+
79
86
  # Passes the NObject to the DWrite class, which writes out the header and image to the specified file.
80
87
  #
81
88
  # === Parameters
@@ -101,10 +108,10 @@ module NIFTI
101
108
  raise ArgumentError, "Invalid file_name. Expected String, got #{file_name.class}."
102
109
  end
103
110
  end
104
-
111
+
105
112
  # Following methods are private:
106
- private
107
-
113
+ private
114
+
108
115
  # Returns a NIFTI object by reading and parsing the specified file.
109
116
  # This is accomplished by initializing the NRead class, which loads NIFTI information.
110
117
  #
@@ -112,7 +119,7 @@ module NIFTI
112
119
  #
113
120
  # This method is called automatically when initializing the NObject class with a file parameter,
114
121
  # and in practice should not be called by users.
115
- #
122
+ #
116
123
  def read(string, options={})
117
124
  if string.is_a?(String)
118
125
  @string = string
@@ -124,7 +131,7 @@ module NIFTI
124
131
  @header = r.hdr
125
132
  @extended_header = r.extended_header
126
133
  if r.image_narray
127
- @image = r.image_narray
134
+ @image = r.image_narray
128
135
  elsif r.image_rubyarray
129
136
  @image = r.image_rubyarray
130
137
  end
@@ -150,6 +157,6 @@ module NIFTI
150
157
  @errors << msg
151
158
  @errors.flatten
152
159
  end
153
-
160
+
154
161
  end
155
162
  end
@@ -1,6 +1,8 @@
1
+ require 'zlib'
2
+
1
3
  module NIFTI
2
4
  # The NRead class parses the NIFTI data from a binary string.
3
- #
5
+ #
4
6
  class NRead
5
7
  # An array which records any status messages that are generated while parsing the DICOM string.
6
8
  attr_reader :msg
@@ -14,14 +16,14 @@ module NIFTI
14
16
  attr_reader :image_rubyarray
15
17
  # A narray of image values reshapred to image dimensions
16
18
  attr_reader :image_narray
17
-
19
+
18
20
  # Valid Magic codes for the NIFTI Header
19
21
  MAGIC = %w{ni1 n+1}
20
-
22
+
21
23
  # Create a NRead object to parse a nifti file or binary string and set header and image info instance variables.
22
24
  #
23
25
  # The nifti header will be checked for validity (header size and magic number) and will raise an IOError if invalid.
24
- #
26
+ #
25
27
  # NIFTI header extensions are not yet supported and are not included in the header.
26
28
  #
27
29
  # The header and image are accessible via the hdr and image instance variables. An optional narray matrix may also be available in image_narray if desired by passing in :narray => true as an option.
@@ -43,18 +45,18 @@ module NIFTI
43
45
  @success = false
44
46
  set_stream(source, options)
45
47
  parse_header(options)
46
-
48
+
47
49
  return self
48
50
  end
49
-
51
+
50
52
  # Unpack an image array from vox_offset to the end of a nifti file.
51
- #
53
+ #
52
54
  # === Parameters
53
55
  #
54
56
  # There are no parameters - this reads from the binary string in the @string instance variable.
55
- #
57
+ #
56
58
  # This sets @image_rubyarray to the image data vector and also returns it.
57
- #
59
+ #
58
60
  def read_image
59
61
  raw_image = []
60
62
  @stream.index = @hdr['vox_offset']
@@ -62,25 +64,25 @@ module NIFTI
62
64
  format = @stream.format[type]
63
65
  @image_rubyarray = @stream.decode(@stream.rest_length, type)
64
66
  end
65
-
66
- # Create an narray if the NArray is available
67
+
68
+ # Create an narray if the NArray is available
67
69
  # Tests if a file is readable, and if so, opens it.
68
70
  #
69
71
  # === Parameters
70
72
  #
71
73
  # * <tt>image_array</tt> -- Array. A vector of image data.
72
74
  # * <tt>dim</tt> -- Array. The dim array from the nifti header, specifing number of dimensions (dim[0]) and dimension length of other dimensions to reshape narray into.
73
- #
75
+ #
74
76
  def get_image_narray(image_array, dim)
75
- if defined? NArray
77
+ if Object.const_defined?('NArray')
76
78
  @image_narray = pixel_data = NArray.to_na(image_array).reshape!(*dim[1..dim[0]])
77
79
  else
78
80
  add_msg "Can't find NArray, no image_narray created. Please `gem install narray`"
79
81
  end
80
82
  end
81
-
83
+
82
84
  private
83
-
85
+
84
86
  # Initializes @stream from a binary string or filename
85
87
  def set_stream(source, options)
86
88
  # Are we going to read from a file, or read from a binary string?
@@ -101,65 +103,65 @@ module NIFTI
101
103
  @file.close
102
104
  end
103
105
  end
104
-
106
+
105
107
  # Create a Stream instance to handle the decoding of content from this binary string:
106
108
  @file_endian = false
107
109
  @stream = Stream.new(@str, false)
108
110
  end
109
-
111
+
110
112
  # Parse the NIFTI Header.
111
113
  def parse_header(options = {})
112
114
  check_header
113
115
  @hdr = parse_basic_header
114
116
  @extended_header = parse_extended_header
115
-
117
+
116
118
  # Optional image gathering
117
- read_image if options[:image]
119
+ read_image if options[:image]
118
120
  get_image_narray(@image_rubyarray, @hdr['dim']) if options[:narray]
119
-
121
+
120
122
  @success = true
121
123
  end
122
-
124
+
123
125
  # NIFTI uses the header length (first 4 bytes) to be 348 number of "ni1\0"
124
126
  # or "n+1\0" as the last 4 bytes to be magic numbers to validate the header.
125
- #
127
+ #
126
128
  # The header is usually checked before any data is read, but can be
127
129
  # checked at any point in the process as the stream index is reset to its
128
130
  # original position after validation.
129
- #
130
- # There are no options - the method will raise an IOError if any of the
131
+ #
132
+ # There are no options - the method will raise an IOError if any of the
131
133
  # magic numbers are not valid.
132
134
  def check_header
133
135
  begin
134
136
  starting_index = @stream.index
135
-
137
+
136
138
  # Check sizeof_hdr
137
- @stream.index = 0;
139
+ @stream.index = 0;
138
140
  sizeof_hdr = @stream.decode(4, "UL")
139
141
  raise IOError, "Bad Header Length #{sizeof_hdr}" unless sizeof_hdr == 348
140
-
142
+
141
143
  # Check magic
142
- @stream.index = 344;
144
+ @stream.index = 344;
143
145
  magic = @stream.decode(4, "STR")
144
146
  raise IOError, "Bad Magic Code #{magic} (should be ni1 or n+1)" unless MAGIC.include?(magic)
145
-
147
+
146
148
  rescue IOError => e
147
149
  raise IOError, "Header appears to be malformed: #{e}"
148
150
  else
149
151
  @stream.index = starting_index
150
- end
152
+ end
151
153
  end
152
154
 
153
155
  # Read the nifti header according to its byte signature.
154
- # The file stream will be left open and should be positioned at the end of the 348 byte header.
156
+ # The file stream will be left open and should be positioned at the end of the 348 byte header.
155
157
  def parse_basic_header
156
158
  # The HEADER_SIGNATURE is defined in NIFTI::Constants and used for both reading and writing.
157
159
  header = {}
158
- HEADER_SIGNATURE.each do |header_item|
160
+ HEADER_SIGNATURE.each do |header_item|
159
161
  name, length, type = *header_item
160
162
  header[name] = @stream.decode(length, type)
161
163
  end
162
-
164
+
163
165
  # Extract Freq, Phase & Slice Dimensions from diminfo
164
166
  if header['dim_info']
165
167
  header['freq_dim'] = dim_info_to_freq_dim(header['dim_info'])
@@ -168,7 +170,7 @@ module NIFTI
168
170
  end
169
171
  header['sform_code_descr'] = XFORM_CODES[header['sform_code']]
170
172
  header['qform_code_descr'] = XFORM_CODES[header['qform_code']]
171
-
173
+
172
174
  return header
173
175
  end
174
176
 
@@ -201,7 +203,7 @@ module NIFTI
201
203
  end
202
204
  return extended
203
205
  end
204
-
206
+
205
207
  # Tests if a file is readable, and if so, opens it.
206
208
  #
207
209
  # === Parameters
@@ -213,7 +215,11 @@ module NIFTI
213
215
  if File.readable?(file)
214
216
  if not File.directory?(file)
215
217
  if File.size(file) > 8
216
- @file = File.new(file, "rb")
218
+ begin
219
+ @file = Zlib::GzipReader.new(File.new(file, "rb"))
220
+ rescue Zlib::GzipFile::Error
221
+ @file = File.new(file, "rb")
222
+ end
217
223
  else
218
224
  @msg << "Error! File is too small to contain DICOM information (#{file})."
219
225
  end
@@ -227,38 +233,38 @@ module NIFTI
227
233
  @msg << "Error! The file you have supplied does not exist (#{file})."
228
234
  end
229
235
  end
230
-
236
+
231
237
  # Bitwise Operator to extract Frequency Dimension
232
238
  def dim_info_to_freq_dim(dim_info)
233
239
  extract_dim_info(dim_info, 0)
234
240
  end
235
-
236
- # Bitwise Operator to extract Phase Dimension
241
+
242
+ # Bitwise Operator to extract Phase Dimension
237
243
  def dim_info_to_phase_dim(dim_info)
238
244
  extract_dim_info(dim_info, 2)
239
245
  end
240
-
246
+
241
247
  # Bitwise Operator to extract Slice Dimension
242
248
  def dim_info_to_slice_dim(dim_info)
243
249
  extract_dim_info(dim_info, 4)
244
250
  end
245
-
251
+
246
252
  # Bitwise Operator to extract Frequency, Phase & Slice Dimensions from 2byte diminfo
247
253
  def extract_dim_info(dim_info, offset = 0)
248
254
  (dim_info >> offset) & 0x03
249
255
  end
250
-
256
+
251
257
  # Bitwise Operator to encode Freq, Phase & Slice into Diminfo
252
258
  def fps_into_dim_info(frequency_dim, phase_dim, slice_dim)
253
- ((frequency_dim & 0x03 ) << 0 ) |
254
- ((phase_dim & 0x03) << 2 ) |
259
+ ((frequency_dim & 0x03 ) << 0 ) |
260
+ ((phase_dim & 0x03) << 2 ) |
255
261
  ((slice_dim & 0x03) << 4 )
256
262
  end
257
-
263
+
258
264
  # Add a message (TODO: and maybe print to screen if verbose)
259
265
  def add_msg(msg)
260
266
  @msg << msg
261
267
  end
262
-
268
+
263
269
  end
264
270
  end
@@ -1,3 +1,5 @@
1
+ require 'zlib'
2
+
1
3
  module NIFTI
2
4
 
3
5
  # The NWrite class handles the encoding of an NObject instance to a valid NIFTI string.
@@ -9,7 +11,7 @@ module NIFTI
9
11
  attr_reader :msg
10
12
  # A boolean which reports whether the DICOM string was encoded/written successfully (true) or not (false).
11
13
  attr_reader :success
12
-
14
+
13
15
  # Creates an NWrite instance.
14
16
  #
15
17
  # === Parameters
@@ -26,7 +28,7 @@ module NIFTI
26
28
  # Array for storing error/warning messages:
27
29
  @msg = Array.new
28
30
  end
29
-
31
+
30
32
  # Handles the encoding of NIfTI information to string as well as writing it to file.
31
33
  def write
32
34
  # Check if we are able to create given file:
@@ -40,7 +42,7 @@ module NIFTI
40
42
  @stream = Stream.new(nil, @file_endian)
41
43
  # Tell the Stream instance which file to write to:
42
44
  @stream.set_file(@file)
43
-
45
+
44
46
  # Write Header and Image
45
47
  write_basic_header
46
48
  write_extended_header
@@ -51,9 +53,9 @@ module NIFTI
51
53
  # Mark this write session as successful:
52
54
  @success = true
53
55
  end
54
-
56
+
55
57
  end
56
-
58
+
57
59
  # Write Basic Header
58
60
  def write_basic_header
59
61
  HEADER_SIGNATURE.each do |header_item|
@@ -72,7 +74,7 @@ module NIFTI
72
74
  end
73
75
  end
74
76
  end
75
-
77
+
76
78
  # Write Extended Header
77
79
  def write_extended_header
78
80
  unless @obj.extended_header.empty?
@@ -85,16 +87,14 @@ module NIFTI
85
87
  else
86
88
  @stream.write @stream.encode([0,0,0,0], "BY")
87
89
  end
88
-
89
-
90
90
  end
91
-
91
+
92
92
  # Write Image
93
93
  def write_image
94
94
  type = NIFTI_DATATYPES[@obj.header['datatype']]
95
95
  @stream.write @stream.encode(@obj.image, type)
96
96
  end
97
-
97
+
98
98
  # Tests if the path/file is writable, creates any folders if necessary, and opens the file for writing.
99
99
  #
100
100
  # === Parameters
@@ -106,7 +106,7 @@ module NIFTI
106
106
  if File.exist?(file)
107
107
  # Is it writable?
108
108
  if File.writable?(file)
109
- @file = File.new(file, "wb")
109
+ @file = get_new_file_writer(file)
110
110
  else
111
111
  # Existing file is not writable:
112
112
  @msg << "Error! The program does not have permission or resources to create the file you specified: (#{file})"
@@ -127,16 +127,31 @@ module NIFTI
127
127
  end
128
128
  end
129
129
  # The path to this non-existing file is verified, and we can proceed to create the file:
130
- @file = File.new(file, "wb")
130
+ @file = get_new_file_writer(file)
131
131
  end
132
132
  end
133
-
133
+
134
134
  # Creates various variables used when encoding the DICOM string.
135
135
  #
136
136
  def init_variables
137
137
  # Until a DICOM write has completed successfully the status is 'unsuccessful':
138
138
  @success = false
139
139
  end
140
-
140
+
141
+ private
142
+
143
+ # Opens the file according to it's extension (gziped or uncompressed)
144
+ #
145
+ # === Parameters
146
+ #
147
+ # * <tt>file</tt> -- A path/file string.
148
+ #
149
+ def get_new_file_writer(file)
150
+ if File.extname(file) == '.gz'
151
+ Zlib::GzipWriter.new(File.new(file, 'wb'))
152
+ else
153
+ File.new(file, 'wb')
154
+ end
155
+ end
141
156
  end
142
157
  end
@@ -1,4 +1,4 @@
1
1
  module NIFTI
2
2
  # Current Version of NIFTI
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
@@ -7,18 +7,21 @@ Gem::Specification.new do |s|
7
7
  s.version = NIFTI::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Erik Kastman"]
10
- s.email = ["ekk@medicine.wisc.edu"]
11
- s.homepage = ""
10
+ s.email = ["erik.kastman@gmail.com"]
11
+ s.homepage = "https://github.com/brainmap/nifti"
12
12
  s.summary = %q{A pure Ruby API to the NIfTI Neuroimaging Format}
13
13
  s.description = %q{A pure Ruby API to the NIfTI Neuroimaging Format}
14
14
 
15
- s.rubyforge_project = "nifti"
15
+ s.required_ruby_version = '>= 1.8'
16
16
 
17
17
  s.files = `git ls-files`.split("\n")
18
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
+ s.license = 'LGPLv3'
21
22
  s.add_development_dependency "rspec"
22
23
  s.add_development_dependency "mocha"
24
+ s.add_development_dependency "cucumber"
23
25
  s.add_development_dependency "narray"
26
+ s.add_development_dependency "simplecov"
24
27
  end