levdon 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 61b634c08e78823b483f945704724721b32f52fc
4
+ data.tar.gz: 4a481f533a47d9e78fc6678536882d30334b2237
5
+ SHA512:
6
+ metadata.gz: 4a5fef5c1ddb5ee772b2d4a7082e1a3867e61863c89ab709d8770fa0eedc189dfc9d21fe7e4fb6422446fe24449f119d99b5c329b17824a77e3503ce087b15ee
7
+ data.tar.gz: 955b51cf623e9ac600cdd339f05466bd434045c5080c7a349a471d01c496cbcf19b739dc8cc87f55a8a6825146b6f879eda8d7ee84f9b76a846d9552d272f23b
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in levdon.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Akihito Nakatsuka
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Levdon
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/levdon`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'levdon'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install levdon
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/levdon.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
41
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "levdon"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/levdon.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'levdon/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "levdon"
8
+ spec.version = Levdon::VERSION
9
+ spec.authors = ["Pegara, Inc."]
10
+ spec.email = ["info@pegara.com"]
11
+ spec.homepage = "https://www.leviadon.com"
12
+ spec.summary = %q{AI(DeepLearning) contents filter library for mastodon.}
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.14"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rmagick", "~> 2.16.0"
25
+ spec.add_development_dependency "multipart-post", "~> 2.0.0"
26
+ end
@@ -0,0 +1,3 @@
1
+ module Levdon
2
+ VERSION = "0.1.0"
3
+ end
data/lib/levdon.rb ADDED
@@ -0,0 +1,801 @@
1
+ require "levdon/version"
2
+
3
+ require "json" # default
4
+ require "securerandom" # default
5
+ require "fileutils" # default
6
+ require "stringio" # default
7
+ require "open-uri" # default
8
+
9
+ require "rmagick" # sudo gem install rmagick
10
+ require "net/http/post/multipart" # sudo gem install multipart-post #https://github.com/nicksieger/multipart-post
11
+
12
+
13
+ module Levdon
14
+ APPLICATION_ID = "Levdon"
15
+ API_URL = ENV['EATER_URL'] ? ENV['EATER_URL'] : "https://api.leviadon.com/upload"
16
+ API_VERSION = "0.1.1"
17
+ ENABLE_SSL = API_URL.index("https://") ? true : false
18
+ VUUID = SecureRandom.uuid
19
+
20
+ ONTOLOGY_LIST = {
21
+ :CLASS_ADULT => { :available => true, :key => 'adult', :desc => '' },
22
+ :CLASS_VIOLENCE => { :available => false, :key => 'violence', :desc => 'closed beta does not support a this class.' },
23
+ :CLASS_GENERAL_OBJECT => { :available => false, :key => 'general_object', :desc => 'closed beta does not support a this class.' },
24
+ :CLASS_MEDICAL => { :available => false, :key => 'medical', :desc => 'closed beta does not support a this class.' },
25
+ :CLASS_MEAL => { :available => false, :key => 'meal', :desc => 'closed beta does not support a this class.' },
26
+ :CLASS_3D => { :available => false, :key => '3D', :desc => 'closed beta does not support a this class.' },
27
+ :CLASS_2D => { :available => false, :key => '2D', :desc => 'closed beta does not support a this class.' },
28
+ :CLASS_FACE => { :available => false, :key => 'face', :desc => 'closed beta does not support a this class.' },
29
+ :CLASS_BODY => { :available => false, :key => 'body', :desc => 'closed beta does not support a this class.' },
30
+ :CLASS_POSE => { :available => false, :key => 'pose', :desc => 'closed beta does not support a this class.' },
31
+ :CLASS_ANIMAL => { :available => false, :key => 'animal', :desc => 'closed beta does not support a this class.' },
32
+ :CLASS_OCR => { :available => false, :key => 'ocr', :desc => 'closed beta does not support a this class.' }
33
+ }
34
+
35
+ ONTOLOGY_REV_LIST = {}
36
+ ONTOLOGY_LIST.map{|k,v| ONTOLOGY_REV_LIST[v[:key]] = k }
37
+
38
+ class NonBlockLineStream
39
+ def initialize(input_stream, output_stream)
40
+ @input_stream = input_stream
41
+ @output_stream = output_stream
42
+ @read_queue = []
43
+ @write_queue = []
44
+ @rlnb_buffer = ""
45
+ end
46
+
47
+ def read
48
+ if(@read_queue.length)
49
+ return @read_queue.shift
50
+ end
51
+ return nil
52
+ end
53
+
54
+ def write(s)
55
+ @write_queue.push(s)
56
+ begin
57
+ wrote_len = @output_stream.write_nonblock(@write_queue[0])
58
+ if(@write_queue[0].length == wrote_len)
59
+ @write_queue.shift
60
+ else
61
+ @write_queue[0] = @write_queue[0].slice(wrote_len,@write_queue[0].length)
62
+ end
63
+ rescue => e
64
+ puts "Write error"
65
+ puts e.class
66
+ puts e.message
67
+ puts e.backtrace
68
+ end
69
+
70
+ @write_queue.length
71
+ end
72
+
73
+ def poll
74
+ has_event = false
75
+ begin
76
+ if(@io)
77
+ if(@write_queue.length > 0)
78
+ wrote_len = @output_stream.write_nonblock(@write_queue[0])
79
+ if(@write_queue[0].length = wrote_len)
80
+ @write_queue.shift
81
+ else
82
+ @write_queue[0] = @write_queue[0].slice(wrote_len,@write_queue[0].length)
83
+ end
84
+ has_event = true
85
+ end
86
+ end
87
+ rescue => e
88
+ puts "Write error"
89
+ puts e.class
90
+ puts e.message
91
+ puts e.backtrace
92
+ end
93
+
94
+ begin
95
+ stream = @input_stream.read_nonblock(8192)
96
+ @rlnb_buffer << stream
97
+ if(@rlnb_buffer.index("\n"))
98
+ sp = @rlnb_buffer.split("\n")
99
+ last = ""
100
+ if(sp.length > 1)
101
+ last = sp.pop
102
+ end
103
+ sp.each{|e|
104
+ @read_queue.push(e)
105
+ }
106
+ @rlnb_buffer = last
107
+ end
108
+ # while ch = @input_stream.read_nonblock(1)
109
+ # @rlnb_buffer << ch
110
+ # if ch == "\n" then
111
+ # @read_queue.push(@rlnb_buffer)
112
+ # @rlnb_buffer = ""
113
+ # end
114
+ # end
115
+ rescue Errno::EAGAIN
116
+ rescue => e
117
+ puts "Read error"
118
+ puts e.class
119
+ puts e.message
120
+ puts e.backtrace
121
+ end
122
+ return has_event
123
+ end
124
+ end
125
+
126
+ class Worker #http://qiita.com/yuroyoro/items/92c5bc864fa9c05127a9
127
+ attr_reader :pid
128
+
129
+ def initialize(&block)
130
+ @child_read, @parent_write = create_pipe
131
+ @parent_read, @child_write = create_pipe
132
+ @block = block
133
+ @io_stream = NonBlockLineStream.new(@parent_read,@parent_write)
134
+ end
135
+
136
+ def create_pipe
137
+ IO.pipe.map{|pipe| pipe.tap{|_| _.set_encoding("ASCII-8BIT", "ASCII-8BIT") } }
138
+ end
139
+
140
+ def run
141
+ @pid = fork do
142
+ @parent_read.close
143
+ @parent_write.close
144
+ write_to_parent(:ready)
145
+ loop do
146
+ args = read_from_parent
147
+ break if args == :stop
148
+ result = @block.call(*args)
149
+ write_object(result, @child_write)
150
+ end
151
+
152
+ @child_read.close
153
+ @child_write.close
154
+ end
155
+
156
+ wait_after_fork if @pid
157
+ end
158
+
159
+ def execute(*msg)
160
+ write_to_child(msg)
161
+ Thread.new { read_from_child }
162
+ end
163
+
164
+ def async_execute(*msg)
165
+ nonblock_write_to_child(msg)
166
+ end
167
+
168
+ def stop
169
+ return unless alive?
170
+ write_to_child(:stop)
171
+ Process.wait(@pid)
172
+ end
173
+
174
+ def alive?
175
+ Process.kill(0, @pid)
176
+ true
177
+ rescue Errno::ESRCH
178
+ false
179
+ end
180
+
181
+ def poll
182
+ @io_stream.poll
183
+ end
184
+
185
+ def write_object(obj, write)
186
+ data = Marshal.dump(obj).gsub("\n", "@NDELIMITER@") + "\n"
187
+ write.write data
188
+ end
189
+
190
+ def read_object(read)
191
+ data = read.gets
192
+ Marshal.load(data.chomp.gsub("@NDELIMITER@", "\n"))
193
+ end
194
+
195
+ def nonblock_read_from_child()
196
+ data = @io_stream.read
197
+ if(data)
198
+ return Marshal.load(data.chomp.gsub("@NDELIMITER@", "\n"))
199
+ end
200
+ return nil
201
+ end
202
+
203
+ def nonblock_write_to_child(obj)
204
+ data = Marshal.dump(obj).gsub("\n", "@NDELIMITER@") + "\n"
205
+ @io_stream.write(data)
206
+ end
207
+
208
+ def read_from_child
209
+ read_object(@parent_read)
210
+ end
211
+
212
+ def write_to_child(obj)
213
+ write_object(obj, @parent_write)
214
+ end
215
+
216
+ def read_from_parent
217
+ read_object(@child_read)
218
+ end
219
+
220
+ def write_to_parent(obj)
221
+ write_object(obj, @child_write)
222
+ end
223
+
224
+ def wait_after_fork
225
+ @child_read.close
226
+ @child_write.close
227
+
228
+ install_exit_handler
229
+ install_signal_handler
230
+
231
+ Thread.new {
232
+ result = read_from_child
233
+ raise "Failed to start worker pid #{ @pid }" unless result == :ready
234
+ result
235
+ }
236
+ end
237
+
238
+ def install_exit_handler
239
+ at_exit do
240
+ next unless alive?
241
+ begin
242
+ Process.kill("KILL", @pid)
243
+ Process.wait(@pid)
244
+ rescue Errno::ESRCH
245
+ # noop
246
+ rescue => e
247
+ puts "error at_exit: #{ e }"
248
+ raise e
249
+ end
250
+ end
251
+ end
252
+
253
+ def install_signal_handler
254
+ [:INT, :QUIT].each do |signal|
255
+ old_handler = Signal.trap(signal) {
256
+ Process.kill(signal, @pid)
257
+ Process.wait(@pid)
258
+ old_handler.call
259
+ }
260
+ end
261
+ end
262
+ end
263
+
264
+ def self.identify_image(path,&block)
265
+ proc = lambda() {|e|
266
+ unless File.directory?(e)
267
+ begin
268
+ fname = File.basename(e)
269
+ # TODO
270
+ if(fname.index("@STANDARDIZED@_") == 0 or fname.index("@Q@_") == 0)
271
+ return nil
272
+ end
273
+
274
+ fsize = File.binread(e).size
275
+ if(fsize > 1024*1024*5)
276
+ obj = {}
277
+ obj[:source_path] = e
278
+ obj[:source_filename] = File.basename(e)
279
+ obj[:error] = "Too big file :" + fsize.to_s
280
+ return obj
281
+ end
282
+ if(fsize < 1024*10)
283
+ obj = {}
284
+ obj[:source_path] = e
285
+ obj[:source_filename] = File.basename(e)
286
+ obj[:error] = "Too small file :" + fsize.to_s
287
+ return obj
288
+ end
289
+
290
+ time = Time.new
291
+ img = Magick::Image.from_blob( File.read(e) ).shift
292
+ #puts (Time.new - time).to_s + " : " + fsize.to_s + " : "+e
293
+ if %w(JPEG GIF PNG).member?(img.format)
294
+ obj = {}
295
+ obj[:source_path] = e
296
+ obj[:source_filename] = File.basename(e)
297
+ obj[:image] = img;
298
+ obj[:time] = Time.new - time
299
+ obj[:size] = fsize
300
+ return obj
301
+ else
302
+ obj = {}
303
+ obj[:source_path] = e
304
+ obj[:source_filename] = File.basename(e)
305
+ obj[:image] = img;
306
+ obj[:error] = "Unknown file"
307
+ return obj
308
+ end
309
+
310
+ rescue => err
311
+ puts "Warning: cought a something error."
312
+ obj = {}
313
+ obj[:source_path] = e
314
+ obj[:source_filename] = File.basename(e)
315
+ obj[:image] = img;
316
+ obj[:error] = err
317
+ return obj
318
+ end
319
+ end
320
+ return nil
321
+ }
322
+ unless File.directory?((path))
323
+ obj = proc.call(path)
324
+ block.call(obj) if(obj)
325
+ else
326
+ Dir.glob(path + "/" + "**/*").each{|e|
327
+ obj = proc.call(e)
328
+ block.call(obj) if(obj)
329
+ }
330
+ end
331
+ end
332
+
333
+ def self.find_image(path,options={:no_check => false},&block)
334
+ unless File.exist?(path)
335
+ puts "Error"
336
+ puts " File does not exist => " + path
337
+ return
338
+ end
339
+ proc = lambda() {|e|
340
+ unless File.directory?(e)
341
+ begin
342
+ if(options and options[:no_check] == true)
343
+ if %w(.JPG .JPEG .GIF .PNG).member?(File.extname(e).upcase)
344
+ obj = {}
345
+ obj[:source_path] = e
346
+ obj[:source_filename] = File.basename(e)
347
+ return obj
348
+ end
349
+ else
350
+ if %w(.JPG .JPEG .GIF .PNG).member?(File.extname(e).upcase)
351
+ img = Magick::Image.from_blob( File.read(e) ).shift
352
+ if %w(JPEG GIF PNG).member?(img.format.to_s)
353
+ obj = {}
354
+ obj[:source_path] = e
355
+ obj[:source_filename] = File.basename(e)
356
+ obj[:image] = img;
357
+ return obj
358
+ end
359
+ end
360
+ end
361
+ rescue => e
362
+ puts "Warning: caught a something error."
363
+ puts e.class
364
+ puts e.message
365
+ puts e.backtrace
366
+ # nothing to do
367
+ end
368
+ end
369
+ return nil
370
+ }
371
+ unless File.directory?(path)
372
+ obj = proc.call(path)
373
+ block.call(obj) if(obj)
374
+ else
375
+ Dir.glob(path + "/" + "**/*").each{|e|
376
+ obj = proc.call(e)
377
+ block.call(obj) if(obj)
378
+ }
379
+ end
380
+ end
381
+
382
+ def self.high_quality_resize(stream)
383
+ img = Magick::Image.from_blob(stream).first
384
+ width = img.columns
385
+ height = img.rows
386
+ if(width > 512 and height > 512)
387
+ img = img.resize_to_fit(512, 512)
388
+ end
389
+ img.to_blob {
390
+ self.quality = 90
391
+ self.format = "jpeg"
392
+ }
393
+ end
394
+
395
+ def self.low_quality_resize(stream)
396
+ img = Magick::Image.from_blob(stream).first
397
+ img = img.resize(224,224)
398
+ img.to_blob {
399
+ self.quality = 80
400
+ self.format = "jpeg"
401
+ }
402
+ end
403
+
404
+ def self.prob_resize(stream)
405
+ if(Random.rand() < 0.9)
406
+ return low_quality_resize(stream)
407
+ end
408
+ return high_quality_resize(stream)
409
+ end
410
+
411
+
412
+ def self.standardize(path,options={:mode=>:delete})
413
+ mode = options[:mode]
414
+ dest = options[:dest]
415
+ unless(mode)
416
+ puts "Error"
417
+ puts "Require parameter"
418
+ puts " Specify a :mode => :delete or :move or :rename"
419
+ exit(9)
420
+ end
421
+ unless(%w{delete delete rename}.member?(mode.to_s))
422
+ puts "Error"
423
+ puts "Require parameter"
424
+ puts " Specify a :mode => :delete or :move or :rename"
425
+ exit(9)
426
+ end
427
+
428
+ dirname = path
429
+ unless File.directory?(path)
430
+ dirname = File.dirname(path)
431
+ end
432
+ puts "Entry path : " + path
433
+ puts "Destination : " + dirname
434
+
435
+
436
+ identify_image(path) {|e|
437
+ source_path = e[:source_path]
438
+ source_filename = e[:source_filename];
439
+ dst_dirname = File.dirname(source_path)
440
+ if(e[:error])
441
+ File.delete(source_path) if File.exist?(source_path)
442
+ if(mode == :delete)
443
+ File.delete(source_path) if File.exist?(source_path)
444
+ next
445
+ elsif(mode == :rename)
446
+ FileUtils.mv(source_path,File.join(dst_dirname,mark+SecureRandom.uuid+"."+source_filename.split(".")[-1]))
447
+ next
448
+ else
449
+ next
450
+ end
451
+ end
452
+ img = e[:image]
453
+ width = img.columns
454
+ height = img.rows
455
+ mark = ""
456
+
457
+ # Check already standardized file or not.
458
+ if source_filename.index("@STANDARDIZED@") == 0 or source_filename.index("@Q@") == 0
459
+ puts "already done : " + source_path
460
+ next
461
+ # else
462
+ # puts "processing : " + source_path
463
+ end
464
+
465
+ # Validaiton check
466
+ if(width > 8192 or height > 8192)
467
+ #puts "too big image: " + source_path
468
+ mark = "@BIG@_"
469
+ elsif(width < 300 or height < 300)
470
+ #puts "thumb image : " + source_path
471
+ mark = "@THUMB@_"
472
+ end
473
+
474
+ puts e[:time].to_s + " : " + e[:size].to_s + " : " + mark + " : " + source_path
475
+
476
+ if(mark.length > 0)
477
+ if(mode == :delete)
478
+ File.delete(source_path) if File.exist?(source_path)
479
+ next
480
+ elsif(mode == :rename)
481
+ FileUtils.mv(source_path,File.join(dst_dirname,mark+SecureRandom.uuid+"."+source_filename.split(".")[-1]))
482
+ next
483
+ elsif(mode == :move)
484
+ if(dest)
485
+ if(File.exist?(dest))
486
+ if(File.directory?(dest))
487
+ FileUtils.mv(source_path,File.join(dest,mark+SecureRandom.uuid+"."+source_filename.split(".")[-1]))
488
+ next
489
+ else
490
+ puts "Error"
491
+ puts "Destination is not directory. => " + dest
492
+ exit(9)
493
+ end
494
+ else
495
+ puts "Error"
496
+ puts "Destination path does not exist. => " + dest
497
+ exit(9)
498
+ end
499
+ else
500
+ puts "Error"
501
+ puts "Destination path is nil."
502
+ puts "You should specify a destination path for :move mode."
503
+ exit(9)
504
+ end
505
+ end
506
+ end
507
+
508
+ # keeping ratio
509
+ if(width > 1024 or height > 1024)
510
+ img.resize_to_fit!(1024, 1024)
511
+ end
512
+
513
+ fname = source_filename.split(".")[0]
514
+ img.format = 'JPEG'
515
+ begin
516
+ # Reformat and save
517
+ img.write(File.join(dst_dirname,"@STANDARDIZED@_"+SecureRandom.uuid+".jpg")) { self.quality = 90 } # 1~100
518
+ # Delete source file
519
+ File.delete(source_path) if File.exist?(source_path)
520
+ rescue => e
521
+ puts "Write error"
522
+ puts e.class
523
+ puts e.message
524
+ puts e.backtrace
525
+ end
526
+ # 10~30KB 80 : Q-TRAIN
527
+ # 30~60KB 90 : Q-SOURCE
528
+ # 50~200KB 100 : Q-SOURCE
529
+ }
530
+ puts "Done"
531
+ end
532
+
533
+
534
+
535
+ class LevdonImpl
536
+
537
+ # def LevdonImpl.dealloc(workers)
538
+ # proc {|id|
539
+ # begin
540
+ # workers.each(&:stop)
541
+ # workers = nil
542
+ # rescue => e
543
+ # # nothing to do
544
+ # end
545
+ # }
546
+ # end
547
+
548
+ def initialize()
549
+ @access_token = "DUMMY"
550
+ @blocks = {}
551
+ @timeout = 10.0 # default 10sec
552
+ @request_cluster_num = 1
553
+
554
+ # prediction parameters
555
+ @ontologies = [:CLASS_ADULT]
556
+ end
557
+
558
+
559
+ def async_start(access_token,&block)
560
+ @access_token = access_token
561
+ @workers = @request_cluster_num.times.map{
562
+ Worker.new{|*args|
563
+ ret = nil
564
+ error = nil
565
+ user_object = nil
566
+ begin
567
+ parameter = args[0]
568
+ f = parameter[:target]
569
+ request_id = parameter[:request_id]
570
+ ontologies = parameter[:ontologies]
571
+ url = URI.parse(API_URL)
572
+
573
+ options = {}
574
+ options['ontologies'] = []
575
+ error = "Class is not specified." if ontologies.length == 0
576
+ ontologies.each{|key|
577
+ obj = ONTOLOGY_LIST[key]
578
+ unless obj
579
+ error = "Invalid key => " + key.to_s
580
+ break
581
+ end
582
+ unless obj[:available]
583
+ error = key.to_s + " class is not available.\n Reason => #{obj[:desc]}"
584
+ break
585
+ end
586
+ options['ontologies'] += [obj[:key]]
587
+ }
588
+ if(!error)
589
+ stream = load(f)
590
+ if(stream)
591
+ io = StringIO.new(Levdon.prob_resize(stream))
592
+ req = Net::HTTP::Post::Multipart.new url.path,
593
+ "api_version" => API_VERSION,
594
+ "upload" => UploadIO.new(io, "image/jpeg", "image.jpg"),
595
+ "request_id" => request_id,
596
+ "access_token" => access_token,
597
+ "constant_id" => APPLICATION_ID,
598
+ "options" => JSON.generate(options)
599
+
600
+ n = Net::HTTP.new(url.host, url.port)
601
+ n.use_ssl = ENABLE_SSL
602
+
603
+ res = n.start {|http| http.request(req) }
604
+
605
+ j = JSON.parse(res.body)
606
+ if(res.code == '200' and j['results'])
607
+ # TODO: data struct problem
608
+ ret = {}
609
+ j['results'].each{|k,v|
610
+ ret[ONTOLOGY_REV_LIST[k]] = v
611
+ }
612
+ else
613
+ ret = nil
614
+ error = j['desc']
615
+ end
616
+ else
617
+ error = "Could not read a '#{f.to_s}'."
618
+ end
619
+ end
620
+ rescue => e
621
+ error = ""
622
+ error += e.class.to_s + "\n"
623
+ error += e.message.to_s + "\n"
624
+ error += e.backtrace.to_s + "\n"
625
+ ensure
626
+ if(io)
627
+ begin
628
+ io.close()
629
+ rescue IOError
630
+ # nothing to do
631
+ rescue => e
632
+ error = ""
633
+ error += e.class.to_s + "\n"
634
+ error += e.message.to_s + "\n"
635
+ error += e.backtrace.to_s + "\n"
636
+ end
637
+ end
638
+ end
639
+
640
+ {:results => ret, :error => error,:parameter => parameter}
641
+ }
642
+ }
643
+
644
+ @workers.map(&:run).each(&:join)
645
+ @roundrobin_counter = 0
646
+ @task_stacking_counter = []
647
+ @workers.length.times{|i| @task_stacking_counter.push([i,0]) }
648
+ #ObjectSpace.define_finalizer(self,LevdonImpl.dealloc(@workers))
649
+
650
+ # CTRL + C
651
+ Signal.trap(:INT) {
652
+ puts "Stop " + APPLICATION_ID + " clients "
653
+ close()
654
+ exit(0)
655
+ }
656
+
657
+ block.call({:error => nil})
658
+ end
659
+
660
+ def start(access_token)
661
+ ret = nil
662
+ async_start(access_token){|e|
663
+ ret = e
664
+ }
665
+ while(ret == nil)
666
+ sleep(0.01)
667
+ end
668
+ ret
669
+ end
670
+
671
+ def option(obj)
672
+ obj.each{|k,v|
673
+ if(k == :CLASS)
674
+ @ontologies = v
675
+ elsif(k == :PARALLEL)
676
+ @request_cluster_num = v
677
+ else
678
+ puts "Invalid option : " + k.to_s
679
+ end
680
+ }
681
+ end
682
+
683
+ def load(any)
684
+ if(any.class.name == "String")
685
+ if(any.index("http://") == 0 or any.index("https://") == 0)
686
+ # from network
687
+ return open(any).read
688
+ else
689
+ # TODO File path checker
690
+ if(any.length < 1024)
691
+ # from file
692
+ return File.open(any).read # File IO
693
+ else
694
+ # from memory
695
+ return any
696
+ end
697
+ end
698
+ else
699
+ # from memory
700
+ return any
701
+ end
702
+ return nil
703
+ end
704
+
705
+ def poll
706
+ @workers.each(&:poll)
707
+ @workers.map(&:nonblock_read_from_child).each_with_index{|ret,i|
708
+ begin
709
+ if(ret)
710
+ request_id = ret[:parameter][:request_id]
711
+ key = VUUID + request_id
712
+ if(@blocks.has_key?(key))
713
+ @task_stacking_counter[i][1] -= 1
714
+ @blocks[key].call(ret)
715
+ @blocks.delete(key)
716
+ else
717
+ puts "Error"
718
+ puts "Invalid response from server."
719
+ end
720
+ end
721
+ rescue => e
722
+ puts "Response error"
723
+ puts e.class
724
+ puts e.message
725
+ puts e.backtrace
726
+ end
727
+ }
728
+ end
729
+
730
+ def enqueue(*args)
731
+ target = @task_stacking_counter.sort{|a,b| a[1]-b[1]}[0][0]
732
+ @task_stacking_counter[target][1] += 1
733
+ #target = @roundrobin_counter % @workers.length
734
+ #@roundrobin_counter += 1
735
+
736
+ worker = @workers[target]
737
+ worker.async_execute(*args)
738
+ end
739
+
740
+ def async_predict(parameter,&block)
741
+ raise "expected key: request_id" if(parameter[:request_id])
742
+ raise "expected key: ontologies" if(parameter[:ontologies])
743
+ raise "required key: target. Specify a file path or URL or data." unless(parameter[:target])
744
+ parameter[:request_id] = SecureRandom.uuid.gsub("-","")
745
+ parameter[:ontologies] = @ontologies
746
+ enqueue(parameter)
747
+ @blocks[VUUID+parameter[:request_id]] = block
748
+ end
749
+ def queue_size
750
+ @blocks.length
751
+ end
752
+
753
+ def close
754
+ begin
755
+ @workers.each(&:stop)
756
+ rescue => e
757
+ # nothing to do
758
+ end
759
+ end
760
+
761
+ def predict(parameter)
762
+ raise "expected key: request_id" if(parameter[:request_id])
763
+ raise "expected key: ontologies" if(parameter[:ontologies])
764
+ raise "required key: target. Specify a file path or URL or data." unless(parameter[:target])
765
+ parameter[:request_id] = SecureRandom.uuid.gsub("-","")
766
+ parameter[:ontologies] = @ontologies
767
+ enqueue(parameter)
768
+ begin
769
+ time = Time.new
770
+ loop {
771
+ if(Time.new - time > @timeout)
772
+ puts ("Timeout")
773
+ break
774
+ end
775
+ result = nil
776
+ @workers.each(&:poll)
777
+ @workers.map(&:nonblock_read_from_child).each_with_index{|ret,i|
778
+ result = ret
779
+ break if(ret)
780
+ }
781
+ if(result)
782
+ return result
783
+ else
784
+ sleep(0.01)
785
+ end
786
+ }
787
+ rescue => e
788
+ puts "Predict error"
789
+ puts e.class
790
+ puts e.message
791
+ puts e.backtrace
792
+ end
793
+ return nil
794
+ end
795
+
796
+ end
797
+
798
+ def self.new
799
+ LevdonImpl.new
800
+ end
801
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: levdon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Pegara, Inc.
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-05-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rmagick
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.16.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.16.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: multipart-post
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.0
69
+ description:
70
+ email:
71
+ - info@pegara.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - bin/console
82
+ - bin/setup
83
+ - levdon.gemspec
84
+ - lib/levdon.rb
85
+ - lib/levdon/version.rb
86
+ homepage: https://www.leviadon.com
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.6.11
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: AI(DeepLearning) contents filter library for mastodon.
110
+ test_files: []