magro 0.2.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e24a0d3e82248b27f8a652e84ebe382363234a082732af2b1c18a55ea0dc6c3
4
- data.tar.gz: dc3bc91550338e59455d2ea51effcaf6934b0970c7169434a2cce7422c852252
3
+ metadata.gz: 50da8b3b6adbd0f21ad6ae09373a4659be55806775a8c136f3d432616868b0f6
4
+ data.tar.gz: 860678b90cd8010f6968cf91b94e7572f98d4124aa310314aff41a4dc5861d03
5
5
  SHA512:
6
- metadata.gz: 000e7447d4f24a0dd6c97610c5c057ee96feb6eb0698a4e3564e7cec29ce6103a1ac3479f12a90fd1d1436ea89329418097197ad346879cc00018cca25e25aa0
7
- data.tar.gz: f8a09aad3d5fcadc92e753a453213f8c33e66fbde0522a263afeb02b7a3ceaaf3245c6801f9d61bde41fe7015070391bd3cba8298dc2a1ca275e4ed4e110c60b
6
+ metadata.gz: 43c79eac7ae51192d1a24d3fa17492d00507a2b6092ab10b62c5fdba83d24f589ff8fd7655d1bc0ea15746f4512e0c02903748d0be12355076c6953661461c85
7
+ data.tar.gz: 75e279fd89956ad161f1e5a188f3ffa272d1f881f350823d4de33abcca9d71bc6120e097d21a32ffb2e25f9cd7497db7cc375c06c755027880581d67b522f20e
@@ -1,3 +1,7 @@
1
+ # 0.3.0
2
+ - Add [filter module](https://yoshoku.github.io/magro/doc/Magro/Filter.html) consists of image filtering methods.
3
+ - Change to use round instead of ceil in [quantization of resize method](https://github.com/yoshoku/magro/commit/1b3308ddfb98a650889483af3cd2045aaf6b8837) when given integer image.
4
+
1
5
  # 0.2.0
2
6
  - Add [transform module](https://yoshoku.github.io/magro/doc/Magro/Transform.html) and resize method.
3
7
  - Fix some configulation files.
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Build Status](https://travis-ci.org/yoshoku/magro.svg?branch=master)](https://travis-ci.org/yoshoku/magro)
4
4
  [![Gem Version](https://badge.fury.io/rb/magro.svg)](https://badge.fury.io/rb/magro)
5
5
  [![BSD 3-Clause License](https://img.shields.io/badge/License-BSD%203--Clause-orange.svg)](https://github.com/yoshoku/numo-liblinear/blob/master/LICENSE.txt)
6
- [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](https://yoshoku.github.io/magro/doc/)
6
+ [![Documentation](http://img.shields.io/badge/api-reference-blue.svg)](https://yoshoku.github.io/magro/doc/)
7
7
 
8
8
  Magro is an image processing library in Ruby.
9
9
  Magro uses [Numo::NArray](https://github.com/ruby-numo/numo-narray) arrays as image objects.
@@ -5,5 +5,6 @@ require 'numo/narray'
5
5
  require 'magro/version'
6
6
  require 'magro/magro'
7
7
 
8
+ require 'magro/filter'
8
9
  require 'magro/io'
9
10
  require 'magro/transform'
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Magro
4
+ # Filter module provides functions for image filtering.
5
+ module Filter
6
+ module_function
7
+
8
+ # Applies box filter to image.
9
+ # This method performs zero padding as a preprocessing.
10
+ #
11
+ # @example
12
+ # image = Magro::IO.imread('foo.png')
13
+ # kernel = Numo::DFloat[
14
+ # [1, 1, 1],
15
+ # [1, 1, 1],
16
+ # [1, 1, 1]
17
+ # ]
18
+ # blured_image = Magro::Filter.filter2d(image, kernel)
19
+ # Magro::IO.imsave('bar.png', blured_image)
20
+ #
21
+ # @param image [Numo::UInt8] (shape: [height, width, n_channels]) Input image to be filtered.
22
+ # @param kernel [Numo::DFloat] (shape: [kernel_height, kernel_width]) Box filter.
23
+ # @param scale [Float/Nil] Scale parameter for box filter. If nil is given, the box filter is normalized with sum of filter values.
24
+ # @param offset [Integer] Offset value of filtered image.
25
+ # @raise [ArgumentError] This error is raised when class of input image is not Numo::NArray.
26
+ # @return [Numo::UInt8] (shape: [height, width, n_channels]) Filtered image.
27
+ def filter2d(image, kernel, scale: nil, offset: 0)
28
+ raise ArgumentError, 'Expect class of image to be Numo::NArray.' unless image.is_a?(Numo::NArray)
29
+ filter_h, filter_w = kernel.shape
30
+ padded = zero_padding(image, filter_h, filter_w)
31
+ n_channels = image.shape[2]
32
+ if n_channels.nil?
33
+ filter1ch(padded, kernel, scale, offset)
34
+ else
35
+ image.class.zeros(*image.shape).tap do |filtered|
36
+ n_channels.times do |c|
37
+ filtered[true, true, c] = filter1ch(padded[true, true, c], kernel, scale, offset)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ # Convolve two 2-dimensional arrays.
44
+ #
45
+ # @param arr1 [Numo::NArray] (shape: [row1, col1]) First input array.
46
+ # @param arr2 [Numo::NArray] (shape: [row2, col2]) Second input array.
47
+ # @raise [ArgumentError] This error is raised when class of input array is not Numo::NArray.
48
+ # @return [Numo::NArray] (shape: [row1 - row2 + 1, col1 - col2 + 1]) Convolution of arr1 with arr2.
49
+ def convolve2d(arr1, arr2)
50
+ raise ArgumentError, 'Expect class of first input array to be Numo::NArray.' unless arr1.is_a?(Numo::NArray)
51
+ raise ArgumentError, 'Expect class of second input array to be Numo::NArray.' unless arr2.is_a?(Numo::NArray)
52
+ raise ArgumentError, 'Expect first input array to be 2-dimensional array.' unless arr1.ndim == 2
53
+ raise ArgumentError, 'Expect second input array to be 2-dimensional array.' unless arr2.ndim == 2
54
+ row1, col1 = arr1.shape
55
+ row2, col2 = arr2.shape
56
+ # FIXME: lib/numo/narray/extra.rb:1098: warning: Using the last argument as keyword parameters is deprecated
57
+ # convolved = im2col(arr1, row2, col2).dot(arr2.flatten)
58
+ convolved = arr2.flatten.dot(im2col(arr1, row2, col2).transpose)
59
+ convolved.reshape(row1 - row2 + 1, col1 - col2 + 1)
60
+ end
61
+
62
+ # private
63
+
64
+ def zero_padding(image, filter_h, filter_w)
65
+ image_h, image_w, n_channels = image.shape
66
+ pad_h = filter_h / 2
67
+ pad_w = filter_w / 2
68
+ out_h = image_h + pad_h * 2
69
+ out_w = image_w + pad_w * 2
70
+ if n_channels.nil?
71
+ image.class.zeros(out_h, out_w).tap do |padded|
72
+ padded[pad_h...(pad_h + image_h), pad_w...(pad_w + image_w)] = image
73
+ end
74
+ else
75
+ image.class.zeros(out_h, out_w, n_channels).tap do |padded|
76
+ n_channels.times do |c|
77
+ padded[pad_h...(pad_h + image_h), pad_w...(pad_w + image_w), c] = image[true, true, c]
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def filter1ch(image, kernel, scale, offset)
84
+ scale ||= kernel.sum
85
+ kernel *= scale.zero? ? 1.0 : 1.fdiv(scale)
86
+ filtered = convolve2d(image, kernel)
87
+ filtered = (filtered + offset).round.clip(image.class::MIN, image.class::MAX) if integer_narray?(image)
88
+ filtered = image.class.cast(filtered) unless filtered.is_a?(image.class)
89
+ filtered
90
+ end
91
+
92
+ def im2col(image, filter_h, filter_w)
93
+ height, width = image.shape
94
+ rows = height - filter_h + 1
95
+ cols = width - filter_w + 1
96
+ mat = image.class.zeros(filter_h, filter_w, rows, cols)
97
+ filter_h.times do |y|
98
+ y_end = y + rows
99
+ filter_w.times do |x|
100
+ x_end = x + cols
101
+ mat[y, x, true, true] = image[y...y_end, x...x_end]
102
+ end
103
+ end
104
+ mat.transpose(2, 3, 0, 1).reshape(rows * cols, filter_h * filter_w)
105
+ end
106
+
107
+ INTEGER_NARRAY = %w[Numo::Int8 Numo::Int16 Numo::Int32 Numo::Int64
108
+ Numo::UInt8 Numo::UInt16 Numo::UInt32 Numo::UInt64].freeze
109
+
110
+ private_constant :INTEGER_NARRAY
111
+
112
+ def integer_narray?(image)
113
+ INTEGER_NARRAY.include?(image.class.to_s)
114
+ end
115
+
116
+ private_class_method :zero_padding, :filter1ch, :im2col, :integer_narray?
117
+ end
118
+ end
@@ -58,7 +58,7 @@ module Magro
58
58
 
59
59
  resized = a * (1 - x_d) * (1 - y_d) + b * x_d * (1 - y_d) + c * (1 - x_d) * y_d + d * x_d * y_d
60
60
 
61
- resized = resized.ceil.clip(image.class::MIN, image.class::MAX) if integer_narray?(image)
61
+ resized = resized.round.clip(image.class::MIN, image.class::MAX) if integer_narray?(image)
62
62
  resized = image.class.cast(resized) unless resized.is_a?(image.class)
63
63
  resized.reshape(new_height, new_width)
64
64
  end
@@ -3,5 +3,5 @@
3
3
  # Magro is an image processing library in Ruby.
4
4
  module Magro
5
5
  # The version of Magro you are using.
6
- VERSION = '0.2.0'
6
+ VERSION = '0.3.0'
7
7
  end
@@ -14,6 +14,11 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = 'https://github.com/yoshoku/magro'
15
15
  spec.license = 'BSD-3-Clause'
16
16
 
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/yoshoku/magro'
19
+ spec.metadata['changelog_uri'] = 'https://github.com/yoshoku/magro/blob/master/CHANGELOG.md'
20
+ spec.metadata['documentation_uri'] = 'https://yoshoku.github.io/magro/doc/'
21
+
17
22
  # Specify which files should be added to the gem when it is released.
18
23
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
24
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: magro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yoshoku
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-05-03 00:00:00.000000000 Z
11
+ date: 2020-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: numo-narray
@@ -49,6 +49,7 @@ files:
49
49
  - ext/magro/magro.c
50
50
  - ext/magro/magro.h
51
51
  - lib/magro.rb
52
+ - lib/magro/filter.rb
52
53
  - lib/magro/io.rb
53
54
  - lib/magro/transform.rb
54
55
  - lib/magro/version.rb
@@ -56,7 +57,11 @@ files:
56
57
  homepage: https://github.com/yoshoku/magro
57
58
  licenses:
58
59
  - BSD-3-Clause
59
- metadata: {}
60
+ metadata:
61
+ homepage_uri: https://github.com/yoshoku/magro
62
+ source_code_uri: https://github.com/yoshoku/magro
63
+ changelog_uri: https://github.com/yoshoku/magro/blob/master/CHANGELOG.md
64
+ documentation_uri: https://yoshoku.github.io/magro/doc/
60
65
  post_install_message:
61
66
  rdoc_options: []
62
67
  require_paths: