npy 0.1.0 → 0.1.1

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.
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: []