magro 0.1.0 → 0.4.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 +5 -5
- data/.coveralls.yml +1 -0
- data/.gitignore +4 -0
- data/.travis.yml +8 -3
- data/CHANGELOG.md +21 -0
- data/Gemfile +7 -1
- data/LICENSE.txt +1 -1
- data/README.md +17 -12
- data/ext/magro/imgrw.c +411 -0
- data/ext/magro/imgrw.h +18 -0
- data/ext/magro/magro.c +1 -394
- data/ext/magro/magro.h +2 -9
- data/lib/magro.rb +2 -0
- data/lib/magro/filter.rb +118 -0
- data/lib/magro/io.rb +10 -4
- data/lib/magro/transform.rb +77 -0
- data/lib/magro/version.rb +1 -1
- data/magro.gemspec +12 -7
- metadata +22 -66
data/ext/magro/imgrw.h
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#ifndef MAGRO_IO_H
|
2
|
+
#define MAGRO_IO_H 1
|
3
|
+
|
4
|
+
#include <stdio.h>
|
5
|
+
#include <stdlib.h>
|
6
|
+
#include <string.h>
|
7
|
+
|
8
|
+
#include <setjmp.h>
|
9
|
+
#include <png.h>
|
10
|
+
#include <jpeglib.h>
|
11
|
+
|
12
|
+
#include <ruby.h>
|
13
|
+
#include <numo/narray.h>
|
14
|
+
#include <numo/template.h>
|
15
|
+
|
16
|
+
void init_io_module();
|
17
|
+
|
18
|
+
#endif /* MAGRO_IO_H */
|
data/ext/magro/magro.c
CHANGED
@@ -1,403 +1,10 @@
|
|
1
1
|
#include "magro.h"
|
2
2
|
|
3
3
|
VALUE mMagro;
|
4
|
-
VALUE mIO;
|
5
|
-
|
6
|
-
/**
|
7
|
-
* @!visibility private
|
8
|
-
*/
|
9
|
-
static
|
10
|
-
VALUE magro_io_read_png(VALUE self, VALUE filename_)
|
11
|
-
{
|
12
|
-
char* filename = StringValuePtr(filename_);
|
13
|
-
FILE* file_ptr = fopen(filename, "rb");
|
14
|
-
unsigned char header[8];
|
15
|
-
png_structp png_ptr;
|
16
|
-
png_infop info_ptr;
|
17
|
-
png_bytep* row_ptr_ptr;
|
18
|
-
png_bytep row_ptr;
|
19
|
-
png_uint_32 width, height;
|
20
|
-
int color_type;
|
21
|
-
int bit_depth;
|
22
|
-
png_uint_32 y;
|
23
|
-
int n_dims = 0;
|
24
|
-
int n_ch;
|
25
|
-
size_t shape[3] = { 0 };
|
26
|
-
VALUE nary;
|
27
|
-
uint8_t* nary_ptr;
|
28
|
-
|
29
|
-
if (file_ptr == NULL) {
|
30
|
-
return Qnil;
|
31
|
-
}
|
32
|
-
|
33
|
-
if (fread(header, 1, 8, file_ptr) < 8) {
|
34
|
-
fclose(file_ptr);
|
35
|
-
return Qnil;
|
36
|
-
}
|
37
|
-
|
38
|
-
if (png_sig_cmp(header, 0, 8)) {
|
39
|
-
fclose(file_ptr);
|
40
|
-
return Qnil;
|
41
|
-
}
|
42
|
-
|
43
|
-
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
44
|
-
if (png_ptr == NULL) {
|
45
|
-
fclose(file_ptr);
|
46
|
-
return Qnil;
|
47
|
-
}
|
48
|
-
info_ptr = png_create_info_struct(png_ptr);
|
49
|
-
if (info_ptr == NULL) {
|
50
|
-
png_destroy_read_struct(&png_ptr, NULL, NULL);
|
51
|
-
fclose(file_ptr);
|
52
|
-
return Qnil;
|
53
|
-
}
|
54
|
-
if (setjmp(png_jmpbuf(png_ptr))) {
|
55
|
-
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
56
|
-
fclose(file_ptr);
|
57
|
-
return Qnil;
|
58
|
-
}
|
59
|
-
|
60
|
-
png_init_io(png_ptr, file_ptr);
|
61
|
-
png_set_sig_bytes(png_ptr, 8);
|
62
|
-
|
63
|
-
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16, NULL);
|
64
|
-
row_ptr_ptr = png_get_rows(png_ptr, info_ptr);
|
65
|
-
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
|
66
|
-
|
67
|
-
if (color_type == PNG_COLOR_TYPE_PALETTE) {
|
68
|
-
png_set_palette_to_rgb(png_ptr);
|
69
|
-
png_read_update_info(png_ptr, info_ptr);
|
70
|
-
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
|
71
|
-
}
|
72
|
-
|
73
|
-
switch (color_type) {
|
74
|
-
case PNG_COLOR_TYPE_GRAY:
|
75
|
-
n_ch = 1;
|
76
|
-
n_dims = 2;
|
77
|
-
shape[0] = height;
|
78
|
-
shape[1] = width;
|
79
|
-
break;
|
80
|
-
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
81
|
-
n_ch = 2;
|
82
|
-
n_dims = 3;
|
83
|
-
shape[0] = height;
|
84
|
-
shape[1] = width;
|
85
|
-
shape[2] = 2;
|
86
|
-
break;
|
87
|
-
case PNG_COLOR_TYPE_RGB:
|
88
|
-
n_ch = 3;
|
89
|
-
n_dims = 3;
|
90
|
-
shape[0] = height;
|
91
|
-
shape[1] = width;
|
92
|
-
shape[2] = 3;
|
93
|
-
break;
|
94
|
-
case PNG_COLOR_TYPE_RGB_ALPHA:
|
95
|
-
n_ch = 4;
|
96
|
-
n_dims = 3;
|
97
|
-
shape[0] = height;
|
98
|
-
shape[1] = width;
|
99
|
-
shape[2] = 4;
|
100
|
-
break;
|
101
|
-
default:
|
102
|
-
n_dims = 0;
|
103
|
-
break;
|
104
|
-
}
|
105
|
-
|
106
|
-
if (n_dims == 0) {
|
107
|
-
fclose(file_ptr);
|
108
|
-
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
109
|
-
return Qnil;
|
110
|
-
}
|
111
|
-
|
112
|
-
nary = rb_narray_new(numo_cUInt8, n_dims, shape);
|
113
|
-
nary_ptr = (uint8_t*)na_get_pointer_for_write(nary);
|
114
|
-
|
115
|
-
for (y = 0; y < height; y++) {
|
116
|
-
row_ptr = row_ptr_ptr[y];
|
117
|
-
memcpy(nary_ptr + y * width * n_ch, row_ptr, width * n_ch);
|
118
|
-
}
|
119
|
-
|
120
|
-
fclose(file_ptr);
|
121
|
-
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
122
|
-
|
123
|
-
return nary;
|
124
|
-
}
|
125
|
-
|
126
|
-
/**
|
127
|
-
* @!visibility private
|
128
|
-
*/
|
129
|
-
static
|
130
|
-
VALUE magro_io_save_png(VALUE self, VALUE filename_, VALUE image)
|
131
|
-
{
|
132
|
-
char* filename = StringValuePtr(filename_);
|
133
|
-
FILE* file_ptr = fopen(filename, "wb");
|
134
|
-
png_structp png_ptr;
|
135
|
-
png_infop info_ptr;
|
136
|
-
png_bytep* row_ptr_ptr;
|
137
|
-
png_uint_32 width, height;
|
138
|
-
int color_type;
|
139
|
-
int bit_depth = 8;
|
140
|
-
png_uint_32 y;
|
141
|
-
int n_ch;
|
142
|
-
int n_dims;
|
143
|
-
narray_t* image_nary;
|
144
|
-
uint8_t* image_ptr;
|
145
|
-
|
146
|
-
if (CLASS_OF(image) != numo_cUInt8) {
|
147
|
-
image = rb_funcall(numo_cUInt8, rb_intern("cast"), 1, image);
|
148
|
-
}
|
149
|
-
if (!RTEST(nary_check_contiguous(image))) {
|
150
|
-
image = nary_dup(image);
|
151
|
-
}
|
152
|
-
|
153
|
-
GetNArray(image, image_nary);
|
154
|
-
n_dims = NA_NDIM(image_nary);
|
155
|
-
height = (png_uint_32)NA_SHAPE(image_nary)[0];
|
156
|
-
width = (png_uint_32)NA_SHAPE(image_nary)[1];
|
157
|
-
image_ptr = (uint8_t*)na_get_pointer_for_read(image);
|
158
|
-
|
159
|
-
n_ch = 1;
|
160
|
-
if (n_dims == 3) {
|
161
|
-
n_ch = (int)NA_SHAPE(image_nary)[2];
|
162
|
-
}
|
163
|
-
|
164
|
-
switch (n_ch) {
|
165
|
-
case 4:
|
166
|
-
color_type = PNG_COLOR_TYPE_RGBA;
|
167
|
-
break;
|
168
|
-
case 3:
|
169
|
-
color_type = PNG_COLOR_TYPE_RGB;
|
170
|
-
break;
|
171
|
-
case 2:
|
172
|
-
color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
|
173
|
-
break;
|
174
|
-
default:
|
175
|
-
color_type = PNG_COLOR_TYPE_GRAY;
|
176
|
-
break;
|
177
|
-
}
|
178
|
-
|
179
|
-
if (file_ptr == NULL) {
|
180
|
-
return Qfalse;
|
181
|
-
}
|
182
|
-
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
183
|
-
if (png_ptr == NULL) {
|
184
|
-
fclose(file_ptr);
|
185
|
-
return Qfalse;
|
186
|
-
}
|
187
|
-
info_ptr = png_create_info_struct(png_ptr);
|
188
|
-
if (info_ptr == NULL) {
|
189
|
-
png_destroy_read_struct(&png_ptr, NULL, NULL);
|
190
|
-
fclose(file_ptr);
|
191
|
-
return Qfalse;
|
192
|
-
}
|
193
|
-
if (setjmp(png_jmpbuf(png_ptr))) {
|
194
|
-
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
195
|
-
fclose(file_ptr);
|
196
|
-
return Qfalse;
|
197
|
-
}
|
198
|
-
|
199
|
-
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type,
|
200
|
-
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
201
|
-
|
202
|
-
row_ptr_ptr = png_malloc(png_ptr, height * sizeof(png_bytep));
|
203
|
-
for (y = 0; y < height; y++) {
|
204
|
-
row_ptr_ptr[y] = png_malloc(png_ptr, width * n_ch * sizeof(png_byte));
|
205
|
-
memcpy(row_ptr_ptr[y], image_ptr + y * width * n_ch, width * n_ch);
|
206
|
-
}
|
207
|
-
|
208
|
-
png_init_io(png_ptr, file_ptr);
|
209
|
-
png_set_rows(png_ptr, info_ptr, row_ptr_ptr);
|
210
|
-
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
|
211
|
-
|
212
|
-
fclose(file_ptr);
|
213
|
-
for (y = 0; y < height; y++) {
|
214
|
-
png_free(png_ptr, row_ptr_ptr[y]);
|
215
|
-
}
|
216
|
-
png_free(png_ptr, row_ptr_ptr);
|
217
|
-
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
218
|
-
|
219
|
-
return Qtrue;
|
220
|
-
}
|
221
|
-
|
222
|
-
struct my_error_mgr {
|
223
|
-
struct jpeg_error_mgr pub;
|
224
|
-
jmp_buf setjmp_buffer;
|
225
|
-
};
|
226
|
-
|
227
|
-
static void
|
228
|
-
my_error_exit(j_common_ptr cinfo)
|
229
|
-
{
|
230
|
-
struct my_error_mgr* my_err = (struct my_error_mgr*)cinfo->err;
|
231
|
-
(*cinfo->err->output_message)(cinfo);
|
232
|
-
longjmp(my_err->setjmp_buffer, 1);
|
233
|
-
}
|
234
|
-
|
235
|
-
/**
|
236
|
-
* @!visibility private
|
237
|
-
*/
|
238
|
-
static
|
239
|
-
VALUE magro_io_read_jpg(VALUE self, VALUE filename_)
|
240
|
-
{
|
241
|
-
char* filename = StringValuePtr(filename_);
|
242
|
-
FILE* file_ptr = fopen(filename, "rb");
|
243
|
-
struct jpeg_decompress_struct jpeg;
|
244
|
-
struct my_error_mgr err;
|
245
|
-
unsigned int width, height;
|
246
|
-
int n_colors;
|
247
|
-
size_t shape[3] = { 0 };
|
248
|
-
int n_dims;
|
249
|
-
unsigned int y;
|
250
|
-
VALUE nary;
|
251
|
-
uint8_t* nary_ptr;
|
252
|
-
JSAMPLE* tmp;
|
253
|
-
|
254
|
-
|
255
|
-
if (file_ptr == NULL) {
|
256
|
-
return Qnil;
|
257
|
-
}
|
258
|
-
|
259
|
-
jpeg.err = jpeg_std_error(&err.pub);
|
260
|
-
err.pub.error_exit = my_error_exit;
|
261
|
-
if (setjmp(err.setjmp_buffer)) {
|
262
|
-
return Qnil;
|
263
|
-
}
|
264
|
-
|
265
|
-
jpeg_create_decompress(&jpeg);
|
266
|
-
jpeg_stdio_src(&jpeg, file_ptr);
|
267
|
-
jpeg_read_header(&jpeg, TRUE);
|
268
|
-
jpeg_start_decompress(&jpeg);
|
269
|
-
|
270
|
-
width = jpeg.output_width;
|
271
|
-
height = jpeg.output_height;
|
272
|
-
n_colors = jpeg.out_color_components;
|
273
|
-
|
274
|
-
n_dims = n_colors == 1 ? 2 : 3;
|
275
|
-
shape[0] = height;
|
276
|
-
shape[1] = width;
|
277
|
-
shape[2] = n_colors;
|
278
|
-
nary = rb_narray_new(numo_cUInt8, n_dims, shape);
|
279
|
-
nary_ptr = (uint8_t*)na_get_pointer_for_write(nary);
|
280
|
-
|
281
|
-
for (y = 0; y < height; y++) {
|
282
|
-
tmp = nary_ptr + y * width * n_colors;
|
283
|
-
jpeg_read_scanlines(&jpeg, &tmp, 1);
|
284
|
-
}
|
285
|
-
|
286
|
-
fclose(file_ptr);
|
287
|
-
jpeg_finish_decompress(&jpeg);
|
288
|
-
jpeg_destroy_decompress(&jpeg);
|
289
|
-
|
290
|
-
return nary;
|
291
|
-
}
|
292
|
-
|
293
|
-
/**
|
294
|
-
* @!visibility private
|
295
|
-
*/
|
296
|
-
static
|
297
|
-
VALUE magro_io_save_jpg(int argc, VALUE* argv, VALUE self)
|
298
|
-
{
|
299
|
-
VALUE filename_;
|
300
|
-
VALUE image;
|
301
|
-
VALUE quality_;
|
302
|
-
char* filename;
|
303
|
-
FILE* file_ptr;
|
304
|
-
struct jpeg_compress_struct jpeg;
|
305
|
-
struct my_error_mgr err;
|
306
|
-
narray_t* image_nary;
|
307
|
-
int quality;
|
308
|
-
int n_dims, n_ch;
|
309
|
-
unsigned int width, height, y;
|
310
|
-
uint8_t* image_ptr;
|
311
|
-
JSAMPLE* tmp;
|
312
|
-
|
313
|
-
rb_scan_args(argc, argv, "21", &filename_, &image, &quality_);
|
314
|
-
|
315
|
-
if (NIL_P(quality_)) {
|
316
|
-
quality = 95;
|
317
|
-
} else {
|
318
|
-
quality = NUM2INT(quality_);
|
319
|
-
}
|
320
|
-
|
321
|
-
filename = StringValuePtr(filename_);
|
322
|
-
|
323
|
-
if (CLASS_OF(image) != numo_cUInt8) {
|
324
|
-
image = rb_funcall(numo_cUInt8, rb_intern("cast"), 1, image);
|
325
|
-
}
|
326
|
-
if (!RTEST(nary_check_contiguous(image))) {
|
327
|
-
image = nary_dup(image);
|
328
|
-
}
|
329
|
-
|
330
|
-
jpeg.err = jpeg_std_error(&err.pub);
|
331
|
-
err.pub.error_exit = my_error_exit;
|
332
|
-
if (setjmp(err.setjmp_buffer)) {
|
333
|
-
return Qfalse;
|
334
|
-
}
|
335
|
-
|
336
|
-
jpeg_create_compress(&jpeg);
|
337
|
-
|
338
|
-
file_ptr = fopen(filename, "wb");
|
339
|
-
if (file_ptr == NULL) {
|
340
|
-
jpeg_destroy_compress(&jpeg);
|
341
|
-
return Qfalse;
|
342
|
-
}
|
343
|
-
|
344
|
-
GetNArray(image, image_nary);
|
345
|
-
n_dims = NA_NDIM(image_nary);
|
346
|
-
height = (unsigned int)NA_SHAPE(image_nary)[0];
|
347
|
-
width = (unsigned int)NA_SHAPE(image_nary)[1];
|
348
|
-
image_ptr = (uint8_t*)na_get_pointer_for_read(image);
|
349
|
-
|
350
|
-
n_ch = 1;
|
351
|
-
if (n_dims == 3) {
|
352
|
-
n_ch = (int)NA_SHAPE(image_nary)[2];
|
353
|
-
}
|
354
|
-
|
355
|
-
jpeg_stdio_dest(&jpeg, file_ptr);
|
356
|
-
|
357
|
-
jpeg.image_height = height;
|
358
|
-
jpeg.image_width = width;
|
359
|
-
jpeg.input_components = n_ch;
|
360
|
-
|
361
|
-
switch (n_ch) {
|
362
|
-
case 3:
|
363
|
-
jpeg.in_color_space = JCS_RGB;
|
364
|
-
break;
|
365
|
-
case 1:
|
366
|
-
jpeg.in_color_space = JCS_GRAYSCALE;
|
367
|
-
break;
|
368
|
-
default:
|
369
|
-
jpeg.in_color_space = JCS_UNKNOWN;
|
370
|
-
break;
|
371
|
-
}
|
372
|
-
|
373
|
-
jpeg_set_defaults(&jpeg);
|
374
|
-
|
375
|
-
jpeg_set_quality(&jpeg, quality, TRUE);
|
376
|
-
|
377
|
-
jpeg_start_compress(&jpeg, TRUE);
|
378
|
-
|
379
|
-
for (y = 0; y < height; y++) {
|
380
|
-
tmp = image_ptr + y * width * n_ch;
|
381
|
-
jpeg_write_scanlines(&jpeg, &tmp, 1);
|
382
|
-
}
|
383
|
-
|
384
|
-
jpeg_finish_compress(&jpeg);
|
385
|
-
jpeg_destroy_compress(&jpeg);
|
386
|
-
|
387
|
-
fclose(file_ptr);
|
388
|
-
|
389
|
-
return Qtrue;
|
390
|
-
}
|
391
4
|
|
392
5
|
void Init_magro()
|
393
6
|
{
|
394
|
-
rb_require("numo/narray");
|
395
|
-
|
396
7
|
mMagro = rb_define_module("Magro");
|
397
8
|
|
398
|
-
|
399
|
-
rb_define_module_function(mIO, "read_png", magro_io_read_png, 1);
|
400
|
-
rb_define_module_function(mIO, "save_png", magro_io_save_png, 2);
|
401
|
-
rb_define_module_function(mIO, "read_jpg", magro_io_read_jpg, 1);
|
402
|
-
rb_define_module_function(mIO, "save_jpg", magro_io_save_jpg, -1);
|
9
|
+
init_io_module();
|
403
10
|
}
|
data/ext/magro/magro.h
CHANGED
@@ -1,15 +1,8 @@
|
|
1
1
|
#ifndef MAGRO_H
|
2
2
|
#define MAGRO_H 1
|
3
3
|
|
4
|
-
#include <math.h>
|
5
|
-
#include <string.h>
|
6
|
-
|
7
|
-
#include <setjmp.h>
|
8
|
-
#include <png.h>
|
9
|
-
#include <jpeglib.h>
|
10
|
-
|
11
4
|
#include <ruby.h>
|
12
|
-
|
13
|
-
#include
|
5
|
+
|
6
|
+
#include "imgrw.h"
|
14
7
|
|
15
8
|
#endif /* MAGRO_H */
|
data/lib/magro.rb
CHANGED
data/lib/magro/filter.rb
ADDED
@@ -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
|