scnnr 1.0.0 → 1.1.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
  SHA1:
3
- metadata.gz: 701520f0f2410d7300d04bac498aec68db34597f
4
- data.tar.gz: 15d6e7b77a2ad5c91d96fa302d4eb1fc800792f3
3
+ metadata.gz: bd33e72ddd88f87380fb7e690b9d24065f5c8130
4
+ data.tar.gz: e32a656bdf4f46aa0f367ae63aa35e2357bcac54
5
5
  SHA512:
6
- metadata.gz: 99379cccbdb705692acd048f32355638b4084c19071c7c2833604fb1356164a36982aab1e7aa505d66495650524faf1d88d492e2eb854308b38b052ed821d0c0
7
- data.tar.gz: ab1d3a36414710519e793259a1a9f655da7c6333971ac145e834bbb62fe02257985092cfd54568c78dd6aa86b4947e5961c9270adf44618adae7a121dba6c62b
6
+ metadata.gz: fedf81404ac294b6abdfe54b63b31c76407da144fac72f93fe3875b3e7af59af74068f152b7cc45002618c22afcda25a4a8848a2b531b2a1ff86d0a0f537cc6d
7
+ data.tar.gz: be1b7eab268b70f526241ffc23d02f732b676dc95dbe7ea1bd24573e365e9b8f0f95d566aea6f35ccc2a7714499ff88c8723060fa8078a7748edf49293461c78
data/CHANGELOG.md CHANGED
@@ -4,13 +4,17 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.1.0] - 2018-03-23
8
+ ### Added
9
+ - Support `public` parameter for `Scnnr::Client#recognize_image`.
10
+ - Add `Scnnr::Client#coordinate` to request `POST /v1/coordinates`.
7
11
 
8
12
  ## [1.0.0] - 2017-11-01
9
13
  ### Added
10
14
  - Allow timeouts greater than 25s when recognising images.
11
15
 
12
16
  ### Fixed
13
- - Fix that `Scnnr::Client#fetch` with a long timeout does not make multiple request.
17
+ - Fix that `Scnnr::Client#fetch` with a long timeout does not make multiple requests.
14
18
 
15
19
  ### Removed
16
20
  - Remove `async` from `Scnnr::Response`.
data/Gemfile CHANGED
@@ -8,14 +8,20 @@ gem 'rake', '~> 10.0'
8
8
 
9
9
  group :development do
10
10
  gem 'pry', '~> 0.10'
11
+
12
+ gem 'guard'
13
+ gem 'guard-rspec'
14
+ gem 'guard-rubocop'
11
15
  end
12
16
 
13
17
  group :test do
14
18
  gem 'rspec', '~> 3.5'
15
19
  gem 'rspec_junit_formatter', '~> 0.3'
20
+
16
21
  gem 'rubocop', '~> 0.49.0'
17
22
  gem 'rubocop-junit-formatter', '~> 0.1'
18
23
  gem 'rubocop-rspec', '~> 1.15.0'
24
+
19
25
  gem 'webmock', '~> 3.0'
20
26
  end
21
27
 
data/Guardfile ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # More info at https://github.com/guard/guard#readme
4
+
5
+ guard :rspec, cmd: 'bundle exec rspec' do
6
+ require 'guard/rspec/dsl'
7
+ dsl = Guard::RSpec::Dsl.new(self)
8
+
9
+ # Feel free to open issues for suggestions and improvements
10
+
11
+ # RSpec files
12
+ rspec = dsl.rspec
13
+ watch(rspec.spec_helper) { rspec.spec_dir }
14
+ watch(rspec.spec_support) { rspec.spec_dir }
15
+ watch(rspec.spec_files)
16
+
17
+ # Ruby files
18
+ ruby = dsl.ruby
19
+ dsl.watch_spec_files_for(ruby.lib_files)
20
+ end
21
+
22
+ guard :rubocop do
23
+ watch(/.+\.rb$/)
24
+ watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
25
+ end
data/README.md CHANGED
@@ -28,14 +28,18 @@ end
28
28
  ```
29
29
 
30
30
  ## Examples
31
+
31
32
  ### Basic usage
33
+
34
+ #### Recognize images
35
+
32
36
  Request image recognition by an image URL.
33
37
 
34
38
  ```
35
39
  url = 'https://example.com/dummy.jpg'
36
40
  recognition = client.recognize_url(url)
37
41
 
38
- # you can override config.timeout.
42
+ # You can override config.timeout.
39
43
  recognition = client.recognize_url(url, timeout: 10)
40
44
  ```
41
45
 
@@ -44,6 +48,9 @@ Request image recognition by a binary image.
44
48
  ```
45
49
  img = File.open('dummy_image_file', 'rb')
46
50
  recognition = client.recognize_image(img)
51
+
52
+ # You can use public parameter as well.
53
+ recognition = client.recognize_image(img, timeout: 10, public: true)
47
54
  ```
48
55
 
49
56
  `Recognition` class represents the image recognition result from API.
@@ -81,6 +88,39 @@ recognition.to_h
81
88
  "category"=>"dress",
82
89
  "labels"=>[{"name"=>"グリーン", "score"=>0.9765959}, {"name"=>"ワンピース", "score"=>0.94697183}, {"name"=>"カーキ", "score"=>0.8136864}, {"name"=>"無地", "score"=>0.54719794}, {"name"=>"フレア", "score"=>0.51572186}]}],
83
90
  "state"=>"finished"}
91
+
92
+ # If you recognize an image with `public` parameter, the `recognition` has `image`.
93
+ recognition.to_h
94
+ => {"id"=>"20170829/ed4c674c-7970-4e9c-9b26-1b6076b36b49",
95
+ "image"=>
96
+ {"url"=>"some-image-url",
97
+ "size"=>{"height"=>400, "width"=>400}},
98
+ "objects"=>
99
+ [{"bounding_box"=>{"bottom"=>0.2696995, "left"=>0.3842466, "right"=>0.57190025, "top"=>0.14457992},
100
+ "category"=>"hat",
101
+ "labels"=>
102
+ [{"name"=>"ハット", "score"=>0.9985399},
103
+ {"name"=>"中折れ", "score"=>0.99334323},
104
+ {"name"=>"ストローハット", "score"=>0.95629793},
105
+ {"name"=>"ベージュ", "score"=>0.9062561},
106
+ {"name"=>"つば広ハット", "score"=>0.7737022},
107
+ {"name"=>"ホワイト", "score"=>0.5695046}]},
108
+ {"bounding_box"=>{"bottom"=>0.95560884, "left"=>0.41641566, "right"=>0.5212327, "top"=>0.8452401},
109
+ "category"=>"shoe",
110
+ "labels"=>
111
+ [{"name"=>"サンダル", "score"=>0.93934095},
112
+ {"name"=>"ホワイト", "score"=>0.74320596},
113
+ {"name"=>"パンプス", "score"=>0.70763165},
114
+ {"name"=>"サボ", "score"=>0.69153166},
115
+ {"name"=>"ストラップ", "score"=>0.66519636},
116
+ {"name"=>"ウェッジソール", "score"=>0.6325865},
117
+ {"name"=>"オープントゥ", "score"=>0.61965847},
118
+ {"name"=>"アンクルストラップ", "score"=>0.576824},
119
+ {"name"=>"厚底", "score"=>0.53842664}]},
120
+ {"bounding_box"=>{"bottom"=>0.7018228, "left"=>0.35182703, "right"=>0.6113004, "top"=>0.25296396},
121
+ "category"=>"dress",
122
+ "labels"=>[{"name"=>"グリーン", "score"=>0.9765959}, {"name"=>"ワンピース", "score"=>0.94697183}, {"name"=>"カーキ", "score"=>0.8136864}, {"name"=>"無地", "score"=>0.54719794}, {"name"=>"フレア", "score"=>0.51572186}]}],
123
+ "state"=>"finished"}
84
124
  ```
85
125
 
86
126
  If the timeout value is zero or `nil`, you will get `Recognition` instance whose state is `queued`.
@@ -99,6 +139,29 @@ recognition.finished?
99
139
  => true
100
140
  ```
101
141
 
142
+ #### Generate fashion coordinates
143
+
144
+ Request fashion coordinates generation.
145
+
146
+ ```
147
+ coordinate = client.coordinate('tops', ['グレー', 'パーカー'], casual: 0.7, girly: 0.3)
148
+
149
+ coordinate.to_h
150
+ => {"items"=>
151
+ [{"category"=>"tops", "labels"=>[{"name"=>"グレー", "score"=>nil}, {"name"=>"パーカー", "score"=>nil}]},
152
+ {"category"=>"shoe",
153
+ "labels"=>
154
+ [{"name"=>"ネイビー", "score"=>nil},
155
+ {"name"=>"スニーカー", "score"=>nil},
156
+ {"name"=>"ランニング", "score"=>nil}]},
157
+ {"category"=>"dress",
158
+ "labels"=>
159
+ [{"name"=>"デニム", "score"=>nil},
160
+ {"name"=>"ブルー", "score"=>nil},
161
+ {"name"=>"サロペット", "score"=>nil},
162
+ {"name"=>"オーバーオール", "score"=>nil}]}]}
163
+ ```
164
+
102
165
  ### Error handling
103
166
 
104
167
  If the recognition processing is not completed within the timeout time or the recognition failed,
data/lib/scnnr/client.rb CHANGED
@@ -5,7 +5,11 @@ module Scnnr
5
5
  require 'net/http'
6
6
  require 'json'
7
7
 
8
- ENDPOINT_BASE = 'https://api.scnnr.cubki.jp'
8
+ TASTES = %i[
9
+ boyish casual celebrity conservative
10
+ feminine girly gyaru harajuku
11
+ mode natural_style
12
+ ].freeze
9
13
 
10
14
  def initialize
11
15
  yield(self.config) if block_given?
@@ -16,36 +20,51 @@ module Scnnr
16
20
  end
17
21
 
18
22
  def recognize_image(image, options = {})
19
- PollingManager.start(self, merge_options(options)) do |opts|
20
- uri = construct_uri('recognitions', opts)
23
+ options = merge_options options
24
+ PollingManager.start(self, options) do |opts|
25
+ uri = construct_uri('recognitions', %i[timeout public], opts)
21
26
  response = post_connection(uri, opts).send_stream(image)
22
- handle_response(response)
27
+ Response.new(response).build_recognition
23
28
  end
24
29
  end
25
30
 
26
31
  def recognize_url(url, options = {})
27
- PollingManager.start(self, merge_options(options)) do |opts|
28
- uri = construct_uri('remote/recognitions', opts)
32
+ options = merge_options options
33
+ PollingManager.start(self, options) do |opts|
34
+ uri = construct_uri('remote/recognitions', %i[timeout], opts)
29
35
  response = post_connection(uri, opts).send_json({ url: url })
30
- handle_response(response)
36
+ Response.new(response).build_recognition
31
37
  end
32
38
  end
33
39
 
34
40
  def fetch(recognition_id, options = {})
41
+ options = merge_options options
35
42
  return request(recognition_id, options) if options.delete(:polling) == false
36
- options = merge_options(options)
37
43
  PollingManager.new(options.delete(:timeout)).polling(self, recognition_id, options)
38
44
  end
39
45
 
46
+ def coordinate(category, labels, taste = {}, options = {})
47
+ options = merge_options options
48
+ uri = construct_uri('coordinates', %i[], options)
49
+ payload = {
50
+ item: { category: category, labels: labels },
51
+ taste: TASTES.each_with_object({}) { |key, memo| memo[key] = taste[key] if taste[key] },
52
+ }
53
+ response = post_connection(uri, options).send_json(payload)
54
+ Response.new(response).build_coordinate
55
+ end
56
+
40
57
  private
41
58
 
42
59
  def merge_options(options = {})
43
60
  self.config.to_h.merge(options)
44
61
  end
45
62
 
46
- def construct_uri(path, options = {})
47
- options = merge_options(options)
48
- URI.parse("#{ENDPOINT_BASE}/#{options[:api_version]}/#{path}?timeout=#{options[:timeout]}")
63
+ def construct_uri(path, allowed_params, options = {})
64
+ Routing.new(
65
+ path, options[:api_version],
66
+ options, allowed_params
67
+ ).to_url
49
68
  end
50
69
 
51
70
  def get_connection(uri, options = {})
@@ -57,15 +76,9 @@ module Scnnr
57
76
  end
58
77
 
59
78
  def request(recognition_id, options = {})
60
- options = merge_options(options)
61
- uri = construct_uri("recognitions/#{recognition_id}", options)
79
+ uri = construct_uri("recognitions/#{recognition_id}", %i[timeout], options)
62
80
  response = get_connection(uri, options).send_request
63
- handle_response(response)
64
- end
65
-
66
- def handle_response(response)
67
- response = Response.new(response)
68
- response.build_recognition
81
+ Response.new(response).build_recognition
69
82
  end
70
83
  end
71
84
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scnnr
4
+ class Coordinate
5
+ class Item
6
+ attr_reader :category, :labels
7
+
8
+ def initialize(attrs = {})
9
+ @category = attrs['category']
10
+ @labels = (attrs['labels'] || []).map { |label| Label.new('name' => label) }
11
+ end
12
+
13
+ def to_h
14
+ { 'category' => self.category, 'labels' => self.labels.map(&:to_h) }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scnnr
4
+ class Coordinate
5
+ attr_reader :items
6
+
7
+ def initialize(attrs = {})
8
+ @items = attrs['items'].map { |item| Coordinate::Item.new(item) }
9
+ end
10
+
11
+ def to_h
12
+ { 'items' => self.items.map(&:to_h) }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scnnr
4
+ class Label
5
+ attr_reader :name, :score
6
+
7
+ def initialize(attrs = {})
8
+ @name = attrs['name']
9
+ @score = attrs['score']
10
+ end
11
+
12
+ def to_h
13
+ { 'name' => self.name, 'score' => self.score }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scnnr
4
+ class Recognition
5
+ class Image
6
+ attr_reader :url, :size
7
+
8
+ def initialize(attrs = {})
9
+ @url = attrs['url']
10
+ @size = attrs['size']
11
+ end
12
+
13
+ def to_h
14
+ { 'url' => self.url, 'size' => self.size }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -3,16 +3,10 @@
3
3
  module Scnnr
4
4
  class Recognition
5
5
  class Object
6
- class Label
7
- attr_reader :name, :score
8
-
9
- def initialize(attrs = {})
10
- @name = attrs['name']
11
- @score = attrs['score']
12
- end
13
-
14
- def to_h
15
- { 'name' => self.name, 'score' => self.score }
6
+ class Label < Scnnr::Label
7
+ def initialize(*args)
8
+ warn "[DEPRECATION] `#{self.class.name}` is deprecated. Please use `Scnnr::Label` instead."
9
+ super
16
10
  end
17
11
  end
18
12
  end
@@ -8,7 +8,7 @@ module Scnnr
8
8
  def initialize(attrs = {})
9
9
  @bounding_box = BoundingBox.new(attrs['bounding_box'])
10
10
  @category = attrs['category']
11
- @labels = (attrs['labels'] || []).map { |label| Label.new(label) }
11
+ @labels = (attrs['labels'] || []).map { |label| Scnnr::Label.new(label) }
12
12
  end
13
13
 
14
14
  def to_h
@@ -2,10 +2,11 @@
2
2
 
3
3
  module Scnnr
4
4
  class Recognition
5
- attr_reader :id, :objects, :state, :error
5
+ attr_reader :id, :image, :objects, :state, :error
6
6
 
7
7
  def initialize(attrs = {})
8
8
  @id = attrs['id']
9
+ @image = Image.new(attrs['image']) if attrs['image']
9
10
  @objects = (attrs['objects'] || []).map { |obj| Object.new(obj) }
10
11
  @state = attrs['state']&.intern
11
12
  @error = attrs['error']
@@ -24,7 +25,12 @@ module Scnnr
24
25
  end
25
26
 
26
27
  def to_h
27
- { 'id' => self.id, 'objects' => self.objects.map(&:to_h), 'state' => self.state.to_s }
28
+ {
29
+ 'id' => self.id,
30
+ 'image' => self.image&.to_h,
31
+ 'objects' => self.objects.map(&:to_h),
32
+ 'state' => self.state.to_s,
33
+ }
28
34
  end
29
35
  end
30
36
  end
@@ -18,13 +18,14 @@ module Scnnr
18
18
  end
19
19
 
20
20
  def build_recognition
21
- case @response
22
- when Net::HTTPSuccess
23
- recognition = Recognition.new(self.parsed_body)
24
- handle_recognition(recognition)
25
- else
26
- handle_error
27
- end
21
+ raise_error_response unless @response.is_a? Net::HTTPSuccess
22
+ recognition = Recognition.new(self.parsed_body)
23
+ handle_recognition(recognition)
24
+ end
25
+
26
+ def build_coordinate
27
+ raise_error_response unless @response.is_a? Net::HTTPSuccess
28
+ Coordinate.new(self.parsed_body)
28
29
  end
29
30
 
30
31
  private
@@ -39,7 +40,7 @@ module Scnnr
39
40
  end
40
41
  end
41
42
 
42
- def handle_error
43
+ def raise_error_response
43
44
  case @response
44
45
  when Net::HTTPNotFound
45
46
  raise RecognitionNotFound, self.parsed_body
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scnnr
4
+ class Routing
5
+ API_SCHEME = URI::HTTPS
6
+ API_HOST = 'api.scnnr.cubki.jp'
7
+
8
+ attr_reader :path_prefix
9
+
10
+ def initialize(path, path_prefix, params, allowed_params)
11
+ @path = path
12
+ @path_prefix = path_prefix
13
+ @queries = build_queries params, allowed_params
14
+ end
15
+
16
+ def to_url
17
+ API_SCHEME.build(
18
+ host: API_HOST,
19
+ path: self.path,
20
+ query: query_string
21
+ )
22
+ end
23
+
24
+ def queries
25
+ @queries.reject { |_, val| val.nil? }
26
+ end
27
+
28
+ def path
29
+ '/' + [self.path_prefix, @path]
30
+ .map { |value| value.sub(%r{\A/}, '').sub(%r{/\z}, '') }
31
+ .join('/')
32
+ end
33
+
34
+ private
35
+
36
+ def query_string
37
+ params = self.queries
38
+ return if params.empty?
39
+ params
40
+ .map { |pair| pair.map { |val| URI.encode_www_form_component val }.join('=') }
41
+ .join('&')
42
+ end
43
+
44
+ def build_queries(params, allowed_params)
45
+ {}.tap do |queries|
46
+ (allowed_params || []).each do |param|
47
+ case param.intern
48
+ when :timeout then queries[:timeout] = params[:timeout] if params[:timeout]&.positive?
49
+ else queries[param] = params[param]
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/scnnr/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Scnnr
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
data/lib/scnnr.rb CHANGED
@@ -2,12 +2,17 @@
2
2
 
3
3
  require 'scnnr/configuration'
4
4
  require 'scnnr/errors'
5
+ require 'scnnr/label'
5
6
  require 'scnnr/recognition'
7
+ require 'scnnr/recognition/image'
6
8
  require 'scnnr/recognition/object'
7
9
  require 'scnnr/recognition/object/bounding_box'
8
10
  require 'scnnr/recognition/object/label'
11
+ require 'scnnr/coordinate'
12
+ require 'scnnr/coordinate/item'
9
13
  require 'scnnr/client'
10
14
  require 'scnnr/connection'
15
+ require 'scnnr/routing'
11
16
  require 'scnnr/polling_manager'
12
17
  require 'scnnr/response'
13
18
  require 'scnnr/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scnnr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - NEWROPE Co. Ltd.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-02 00:00:00.000000000 Z
11
+ date: 2018-03-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -39,6 +39,7 @@ files:
39
39
  - CHANGELOG.md
40
40
  - CODE_OF_CONDUCT.md
41
41
  - Gemfile
42
+ - Guardfile
42
43
  - LICENSE
43
44
  - README.md
44
45
  - Rakefile
@@ -48,13 +49,18 @@ files:
48
49
  - lib/scnnr/client.rb
49
50
  - lib/scnnr/configuration.rb
50
51
  - lib/scnnr/connection.rb
52
+ - lib/scnnr/coordinate.rb
53
+ - lib/scnnr/coordinate/item.rb
51
54
  - lib/scnnr/errors.rb
55
+ - lib/scnnr/label.rb
52
56
  - lib/scnnr/polling_manager.rb
53
57
  - lib/scnnr/recognition.rb
58
+ - lib/scnnr/recognition/image.rb
54
59
  - lib/scnnr/recognition/object.rb
55
60
  - lib/scnnr/recognition/object/bounding_box.rb
56
61
  - lib/scnnr/recognition/object/label.rb
57
62
  - lib/scnnr/response.rb
63
+ - lib/scnnr/routing.rb
58
64
  - lib/scnnr/version.rb
59
65
  - scnnr.gemspec
60
66
  homepage: https://github.com/NEWROPE/scnnr-ruby
@@ -77,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
77
83
  version: '0'
78
84
  requirements: []
79
85
  rubyforge_project:
80
- rubygems_version: 2.6.13
86
+ rubygems_version: 2.6.14
81
87
  signing_key:
82
88
  specification_version: 4
83
89
  summary: ''