npy 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +20 -1
  4. data/lib/npy.rb +93 -38
  5. data/lib/npy/version.rb +1 -1
  6. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8382d17139cd8c1b47882130b5612fd7d748923ead1e763a46e9f0c1e819e08
4
- data.tar.gz: 26355f76370731fb6aa3eead0291cb845afa532bdc5ce7bf35a42eccc52a3819
3
+ metadata.gz: 2e81a0b6a617c8f05e1e62f88c05996891328ef154cd4c60d49d233b41b99748
4
+ data.tar.gz: 4f8c8cc599e2bae978b1cc6dabcadb2dd6aab1dec96c1f5f3e07a8a4303f1a24
5
5
  SHA512:
6
- metadata.gz: 5f3e1672b1a9f378bcddd7e8783880d062dd5ffa672b72026ef5a28d862076f2200f2f86fc447feb2bea5e0113606f7bb44b8bb2c174fb1a639d634cc3e745d1
7
- data.tar.gz: a4562cfb5e8511d2624e687b4fa06a304ab07075f6eb44083decc00895618b044190467d06f0793fb44c826c7b0e2f2ae5b1c61216a87c57c62f4fca05d2704c
6
+ metadata.gz: 82babeb75230ec73cb0870b42716589bc2bf9a460ff9943606792b9fe3dfa941f5892c4de1ba7b719afe2fa0e3726b2f5ca5ca268224afe2273c5c66af881cba
7
+ data.tar.gz: 700466088d563a0a8f4ccebce9506d91675b41bc1f47525756637b49218c533bfeef5364f3dea5b71328d690589643947273db00b44b7af8f86a77acdaf59ce3
@@ -1,3 +1,9 @@
1
+ ## 0.1.1
2
+
3
+ - Added support for saving files
4
+ - Added support for npy version 2.0 and 3.0
5
+ - Fixed error with empty shapes
6
+
1
7
  ## 0.1.0
2
8
 
3
9
  - First release
data/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # Npy
2
2
 
3
- Load NumPy `npy` and `npz` files in Ruby - no Python required
3
+ Save and load NumPy `npy` and `npz` files in Ruby - no Python required
4
4
 
5
5
  :fire: Uses [Numo::NArray](https://github.com/ruby-numo/numo-narray) for blazing performance
6
6
 
7
+ [![Build Status](https://travis-ci.org/ankane/npy.svg?branch=master)](https://travis-ci.org/ankane/npy)
8
+
7
9
  ## Installation
8
10
 
9
11
  Add this line to your application’s Gemfile:
@@ -18,6 +20,13 @@ gem 'npy'
18
20
 
19
21
  `npy` files contain a single array
20
22
 
23
+ Save an array
24
+
25
+ ```ruby
26
+ arr = Numo::Int32[0...10]
27
+ Npy.save("x.npy", arr)
28
+ ```
29
+
21
30
  Load an `npy` file
22
31
 
23
32
  ```ruby
@@ -35,6 +44,14 @@ arr = Npy.load_string(byte_str)
35
44
 
36
45
  `npz` files contain multiple arrays
37
46
 
47
+ Save multiple arrays
48
+
49
+ ```ruby
50
+ x = Numo::Int32[0...10]
51
+ y = x * 2
52
+ Npy.save_npz("mnist.npz", x: x, y: y)
53
+ ```
54
+
38
55
  Load an `npz` file
39
56
 
40
57
  ```ruby
@@ -58,6 +75,8 @@ Arrays are lazy loaded for performance
58
75
  ## Resources
59
76
 
60
77
  - [npy format](https://docs.scipy.org/doc/numpy/reference/generated/numpy.lib.format.html#module-numpy.lib.format)
78
+ - [NumPy data types](https://docs.scipy.org/doc/numpy/user/basics.types.html)
79
+ - [Numo::NArray docs](https://ruby-numo.github.io/narray/narray/Numo/NArray.html)
61
80
 
62
81
  ## History
63
82
 
data/lib/npy.rb CHANGED
@@ -11,6 +11,21 @@ module Npy
11
11
 
12
12
  MAGIC_STR = "\x93NUMPY".b
13
13
 
14
+ TYPE_MAP = {
15
+ "|i1" => Numo::Int8,
16
+ "<i2" => Numo::Int16,
17
+ "<i4" => Numo::Int32,
18
+ "<i8" => Numo::Int64,
19
+ "|u1" => Numo::UInt8,
20
+ "<u2" => Numo::UInt16,
21
+ "<u4" => Numo::UInt32,
22
+ "<u8" => Numo::UInt64,
23
+ "<f4" => Numo::SFloat,
24
+ "<f8" => Numo::DFloat,
25
+ "<c8" => Numo::SComplex,
26
+ "<c16" => Numo::DComplex
27
+ }
28
+
14
29
  class << self
15
30
  def load(path)
16
31
  with_file(path) do |f|
@@ -37,54 +52,90 @@ module Npy
37
52
  magic = io.read(6)
38
53
  raise Error, "Invalid npy format" unless magic&.b == MAGIC_STR
39
54
 
40
- major_version = io.read(1)
41
- minor_version = io.read(1)
42
- raise Error, "Unsupported version" unless major_version == "\x01".b
55
+ version = io.read(2)
43
56
 
44
- header_len = io.read(2).unpack1("S<")
57
+ header_len =
58
+ case version
59
+ when "\x01\x00".b
60
+ io.read(2).unpack1("S<")
61
+ when "\x02\x00".b, "\x03\x00".b
62
+ io.read(4).unpack1("I<")
63
+ else
64
+ raise Error, "Unsupported version"
65
+ end
45
66
  header = io.read(header_len)
46
67
  descr, fortran_order, shape = parse_header(header)
47
68
  raise Error, "Fortran order not supported" if fortran_order
48
69
 
49
- klass =
50
- case descr
51
- when "|i1"
52
- Numo::Int8
53
- when "<i2"
54
- Numo::Int16
55
- when "<i4"
56
- Numo::Int32
57
- when "<i8"
58
- Numo::Int64
59
- when "|u1"
60
- Numo::UInt8
61
- when "<u2"
62
- Numo::UInt16
63
- when "<u4"
64
- Numo::UInt32
65
- when "<u8"
66
- Numo::UInt64
67
- when "<f4"
68
- Numo::SFloat
69
- when "<f8"
70
- Numo::DFloat
71
- when "<c8"
72
- Numo::SComplex
73
- when "<c16"
74
- Numo::DComplex
75
- else
76
- raise Error, "Type not supported: #{descr}"
77
- end
70
+ # numo can't handle empty shapes
71
+ empty_shape = shape.empty?
72
+ shape = [1] if empty_shape
78
73
 
79
- klass.from_binary(io.read, shape)
74
+ klass = TYPE_MAP[descr]
75
+ raise Error, "Type not supported: #{descr}" unless klass
76
+
77
+ # use from_string instead of from_binary for max compatibility
78
+ # from_binary introduced in 0.9.0.4
79
+ result = klass.from_string(io.read, shape)
80
+ result = result[0] if empty_shape
81
+ result
80
82
  end
81
83
 
82
84
  def load_npz_io(io)
83
85
  File.new(io)
84
86
  end
85
87
 
88
+ def save(path, arr)
89
+ ::File.open(path, "wb") do |f|
90
+ save_io(f, arr)
91
+ end
92
+ true
93
+ end
94
+
95
+ def save_npz(path, **arrs)
96
+ # use File.open instead passing path to zip file
97
+ # so it overrides instead of appends
98
+ ::File.open(path, "wb") do |f|
99
+ Zip::File.open(f, Zip::File::CREATE) do |zipfile|
100
+ arrs.each do |k, v|
101
+ zipfile.get_output_stream("#{k}.npy") do |f2|
102
+ save_io(f2, v)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ true
108
+ end
109
+
86
110
  private
87
111
 
112
+ def save_io(f, arr)
113
+ empty_shape = arr.is_a?(Numeric)
114
+ arr = Numo::NArray.cast([arr]) if empty_shape
115
+ arr = Numo::NArray.cast(arr) if arr.is_a?(Array)
116
+
117
+ # desc
118
+ descr = TYPE_MAP.find { |k, v| arr.is_a?(v) }
119
+ raise Error, "Unsupported type: #{arr.class.name}" unless descr
120
+
121
+ # shape
122
+ shape = arr.shape
123
+ shape << "" if shape.size == 1
124
+ shape = [] if empty_shape
125
+
126
+ # header
127
+ header = "{'descr': '#{descr[0]}', 'fortran_order': False, 'shape': (#{shape.join(", ")}), }".b
128
+ padding_len = 64 - (11 + header.length) % 64
129
+ padding = "\x20".b * padding_len
130
+ header = "#{header}#{padding}\n"
131
+
132
+ f.write(MAGIC_STR)
133
+ f.write("\x01\x00".b)
134
+ f.write([header.bytesize].pack("S<"))
135
+ f.write(header)
136
+ f.write(arr.to_string)
137
+ end
138
+
88
139
  def with_file(path)
89
140
  ::File.open(path, "rb") do |f|
90
141
  yield f
@@ -93,17 +144,21 @@ module Npy
93
144
 
94
145
  # header is Python dict, so use regex to parse
95
146
  def parse_header(header)
147
+ # sanity check
148
+ raise Error, "Bad header" if !header || header[-1] != "\n"
149
+
96
150
  # descr
97
- m = /'descr': '([^']+)'/.match(header)
151
+ m = /'descr': *'([^']+)'/.match(header)
98
152
  descr = m[1]
99
153
 
100
154
  # fortran_order
101
- m = /'fortran_order': ([^,]+)/.match(header)
155
+ m = /'fortran_order': *([^,]+)/.match(header)
102
156
  fortran_order = m[1] == "True"
103
157
 
104
158
  # shape
105
- m = /'shape': \(([^)]+)\)/.match(header)
106
- shape = m[1].split(", ").map(&:to_i)
159
+ m = /'shape': *\(([^)]*)\)/.match(header)
160
+ # no space in split for max compatibility
161
+ shape = m[1].split(",").map(&:to_i)
107
162
 
108
163
  [descr, fortran_order, shape]
109
164
  end
@@ -1,3 +1,3 @@
1
1
  module Npy
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: npy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-13 00:00:00.000000000 Z
11
+ date: 2019-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: numo-narray
@@ -114,5 +114,5 @@ requirements: []
114
114
  rubygems_version: 3.0.3
115
115
  signing_key:
116
116
  specification_version: 4
117
- summary: Load NumPy npy and npz files in Ruby
117
+ summary: Save and load NumPy npy and npz files in Ruby
118
118
  test_files: []