nifti 0.0.1 → 0.0.2

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