deepstack 1.4.0 → 1.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '02929889eabbeeb8b76cd2f588cef7452ef0bb6dd3687fad749105e6956191e9'
4
- data.tar.gz: 2625f0bc5aac522d23461530dda2e581718c4f967c053db13c13ff0dcaa46f92
3
+ metadata.gz: 8894b856fe894840f7b0178501000f4bd3c05dc0f8e5d3199f3782ac3bfbff86
4
+ data.tar.gz: 4e42c6f313e00d6c909de463c7a5a3874861d48a06a73c62970c5a064beedf7c
5
5
  SHA512:
6
- metadata.gz: fd37e76b9ad0fd30ddcc8a995fe2267d01f056cb3aa3a1d134b6335d91b6b300f61ce9929ab6b2e183ead1036135d2854a53858306395caadfaca7f2baf083d2
7
- data.tar.gz: 275bba97d88a57d51de45a0ae2be230df5d2a8c749324ea748cd2a16c9e45de9c8f6c44d0d61fd860985400093e1caec636f1905bfbcab680631b39087bfbffa
6
+ metadata.gz: 6eff77cc8ea43965ba5ba60aaccc2eea39839d7986ff5c88304f7a09dd12487d758b83b62743a216b6bb98a1f1f27807815b3c0dcef085573b6878809c6eea89
7
+ data.tar.gz: aa7f18c4fbfb6e1cac966f315c540bcbb08e9d52d391283d264df849b78ad0a1b442ea0217d8ced45e888882b0260f156cd4a8768aaeec21cf5f744bc62669cc
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # DeepStack Ruby Wrapper
2
+
3
+ A Ruby wrapper for [DeepStack](https://www.deepstack.cc/) HTTP API
4
+
5
+ ## Usage
6
+
7
+ Install the gem with `gem install deepstack`
8
+
9
+ ~~~ ruby
10
+ require 'deepstack'
11
+
12
+ deepstack = DeepStack.new('http://192.168.1.10:2000')
13
+ image = File.read('image.jpg')
14
+
15
+ # Find bounding rects for objects
16
+ predictions = deepstack.detect_objects(image)
17
+ # => [{"confidence"=>0.86599416, "label"=>"dog", "y_min"=>355, "x_min"=>648, "y_max"=>540, "x_max"=>797},
18
+ # {"confidence"=>0.918332, "label"=>"person", "y_min"=>113, "x_min"=>442, "y_max"=>524, "x_max"=>601},
19
+ # {"confidence"=>0.9292374, "label"=>"person", "y_min"=>83, "x_min"=>294, "y_max"=>521, "x_max"=>447}]
20
+
21
+ # Find bounding rects for faces
22
+ faces = deepstack.detect_faces(image)
23
+ # => [{"confidence"=>0.86419886, "y_min"=>236, "x_min"=>876, "y_max"=>730, "x_max"=>1203},
24
+ # {"confidence"=>0.8811783, "y_min"=>164, "x_min"=>1617, "y_max"=>692, "x_max"=>1985}]
25
+
26
+ # Register a face for face recognition
27
+ deepstack.register_face('face1', image)
28
+
29
+ # List all the registered faces in the system
30
+ face_list = deepstack.face_list
31
+ # => ["face1", "face2"]
32
+
33
+ # Perform a face recognition, return identified userids
34
+ faces = deepstack.recognize_face(image)
35
+ # => [{"confidence"=>0, "userid"=>"unknown", "y_min"=>236, "x_min"=>876, "y_max"=>730, "x_max"=>1203},
36
+ # {"confidence"=>0.9824197, "userid"=>"face1", "y_min"=>164, "x_min"=>1617, "y_max"=>692, "x_max"=>1985}]
37
+
38
+ # Delete a face from the system
39
+ deepstack.delete_face('face1')
40
+
41
+ # Detect similarities between two face images
42
+ image2 = File.read('image2.jpg')
43
+ similarity = deepstack.face_match(image, image2)
44
+ # => 0.3333
45
+
46
+ # Perform Scene recognition
47
+ scene = deepstack.identify_scene(image)
48
+ # => {"success"=>true, "confidence"=>0.27867314, "label"=>"archive", "duration"=>0}
49
+ ~~~
50
+
51
+ ### Error Handling
52
+
53
+ The methods will:
54
+
55
+ * Raise an exception on a connection error.
56
+ * Return nil when DeepStack reports failure (`success=false`)
57
+
58
+ See the [documentation](https://www.rubydoc.info/gems/deepstack) for more details.
59
+
60
+ ## Development
61
+
62
+ A Linux development machine is needed in order to run the tests. The test will launch DeepStack docker instances
63
+ to test against. By default, the deepstack docker will listen on ports `8001`-`8004`.
64
+ To change this, copy `rakelib/deepstack.yml.sample` to `rakelib/deepstack.yml` and change the port numbers as required.
65
+
66
+ To run the tests run `rake`.
67
+
68
+ To manually stop the DeepStack docker instance, run `rake deepstack:stop`
69
+
70
+ To install this gem onto your local machine, run `bundle exec rake install`.
71
+
72
+ ## Contributing
73
+
74
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/jimtng/deepstack-ruby).
75
+
76
+ ## License
77
+
78
+ The gem is available as open source under the terms of the EPL 2.0 License
@@ -6,29 +6,53 @@ require_relative 'face'
6
6
  require_relative 'detection'
7
7
  require_relative 'scene'
8
8
  require_relative 'custom_model'
9
+ require_relative 'image'
9
10
  require_relative 'version'
10
11
 
11
- # DeepStack API
12
+ #
13
+ # DeepStack API wrapper
14
+ #
15
+ # All the methods that communicate with the server can raise an +Errno::ECONNREFUSED+ exception
16
+ # if the server is down or the connection details are incorrect.
17
+ #
12
18
  class DeepStack
13
19
  include DeepStack::Face
14
20
  include DeepStack::Detection
15
21
  include DeepStack::Scene
22
+ include DeepStack::Image
16
23
  include DeepStack::CustomModel
17
24
 
18
25
  #
19
- # Create a deepstack object connected to the given URL
26
+ # Create a deepstack object for the given server URL. HTTP connections are not made within the constructor
27
+ # so the deepstack object can be created against a server that's currently down without
28
+ # raising an exception.
29
+ #
30
+ # A persistent HTTP connection will be initiated on the first method request.
20
31
  #
21
32
  # @param [String] base_url the url to DeepStack's server:port
22
- # @param [String] api_key an optional API-KEY to use when connecting to DeepStack
23
- # @param [String] admin_key an optional ADMIN-KEY to use when connecting to DeepStack
33
+ # @param [String] api_key an optional +API-KEY+ to use when connecting to DeepStack
34
+ # @param [String] admin_key an optional +ADMIN-KEY+ to use when connecting to DeepStack
35
+ # @param [Integer] verify_mode sets the flags for server the certification verification at
36
+ # beginning of an SSL/TLS session.
37
+ # +OpenSSL::SSL::VERIFY_NONE+ or +OpenSSL::SSL::VERIFY_PEER+ are acceptable.
38
+ # The default is +OpenSSL::SSL::VERIFY_PEER+.
24
39
  #
25
40
  # @example
26
41
  # DeepStack.new('http://127.0.0.1:5000')
27
42
  #
28
- def initialize(base_url, api_key: nil, admin_key: nil)
43
+ # # Using an API KEY
44
+ # DeepStack.new('http://127.0.0.1:5000', api_key: 'secret', admin_key: 'supersecret')
45
+ #
46
+ # # SSL connection with a self-signed certificate
47
+ # DeepStack.new('https://localhost:443', verify_mode: OpenSSL::SSL::VERIFY_NONE)
48
+ #
49
+ def initialize(base_url, api_key: nil, admin_key: nil, verify_mode: nil)
29
50
  @base_url = base_url
30
51
  @auth = { api_key: api_key, admin_key: admin_key }.select { |_k, v| v } # remove nil values
31
- @http_mutex = Mutex.new
52
+ uri = URI(base_url)
53
+ @http = Net::HTTP.new(uri.hostname, uri.port)
54
+ @http.use_ssl = uri.instance_of?(URI::HTTPS)
55
+ @http.verify_mode = verify_mode if verify_mode
32
56
  end
33
57
 
34
58
  #
@@ -57,12 +81,12 @@ class DeepStack
57
81
  end
58
82
 
59
83
  #
60
- # Close the HTTP connection to DeepStack server
84
+ # Close the persistent HTTP connection to DeepStack server. This should be called after
85
+ # a period of inactivity to close the TCP connection. Subsequent API calls will
86
+ # re-open the connection automatically.
61
87
  #
62
88
  def close
63
- @http_mutex.synchronize do
64
- @http.finish if @http&.started?
65
- end
89
+ @http.finish if @http&.started?
66
90
  end
67
91
 
68
92
  private
@@ -79,11 +103,8 @@ class DeepStack
79
103
  form_data = combine_images_and_args(images.flatten, **args)
80
104
  req = Net::HTTP::Post.new(uri)
81
105
  req.set_form(form_data, 'multipart/form-data')
82
- @http_mutex.synchronize do
83
- @http ||= Net::HTTP.start(uri.hostname, uri.port)
84
- @http.start unless @http.started?
85
- @http.request(req)
86
- end
106
+ @http.start unless @http.started?
107
+ @http.request(req)
87
108
  end
88
109
 
89
110
  def combine_images_and_args(*images, **args)
@@ -86,8 +86,8 @@ class DeepStack
86
86
  # image2 = File.read('obama2.jpg')
87
87
  # puts deepstack.face_match(image1, image2) > 0.6 ? 'similar' : 'different'
88
88
  #
89
- # @param [Array] *images two images to compare
90
- # @param [kwargs] **args optional arguments to the API call
89
+ # @param [Array] images two images to compare
90
+ # @param [kwargs] args optional arguments to the API call
91
91
  #
92
92
  # @return [Float] The similarity score (0-1)
93
93
  # @return [nil] if failed
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ class DeepStack
6
+ # Support Image Enhance feature
7
+ module Image
8
+ #
9
+ # Enhance image {https://docs.deepstack.cc/api-reference/index.html#image-enhance}
10
+ #
11
+ # @param [Image] image the raw image data or a File object of an image file
12
+ #
13
+ # @return [Image] the enhanced image object
14
+ # @return [nil] if failed
15
+ #
16
+ def enhance_image(image)
17
+ target = 'vision/enhance'
18
+ result = api_post(target, image)
19
+ return unless result&.dig('success') == true
20
+
21
+ Base64.decode64(result['base64'])
22
+ end
23
+ end
24
+ end
@@ -4,14 +4,15 @@ class DeepStack
4
4
  # Scene Recognition
5
5
  module Scene
6
6
  #
7
- # Return
7
+ # Call the scene recognition API to classify an image into one of the supported scenes.
8
8
  #
9
9
  # @param [Object] image binary data or a File object
10
10
  # @param [Hash] options additional fields for DeepStack, e.g. min_confidence: 0.5
11
11
  #
12
- # @return [Hash] if successful, DeepStack result hash {'label' => 'scene', 'confidence' => 2.2}
12
+ # @return [Hash] if successful, DeepStack result hash +{'label' => 'scene', 'confidence' => 2.2}+
13
13
  #
14
14
  # @return [nil] if error
15
+ #
15
16
  def identify_scene(image, **options)
16
17
  target = 'vision/scene'
17
18
  api_post(target, image, **options)
@@ -5,5 +5,5 @@
5
5
  #
6
6
  class DeepStack
7
7
  # @return [String] Version of DeepStack helper libraries
8
- VERSION = '1.4.0'
8
+ VERSION = '1.6.1'
9
9
  end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake'
4
+ require 'pp'
5
+ require 'deepstack'
6
+
7
+ Rake.application.rake_require 'docker_support', ['rakelib']
8
+ Rake.application.rake_require 'deepstack', ['rakelib']
9
+
10
+ # http is used in persistence check below
11
+ class DeepStack
12
+ attr_reader :http
13
+ end
14
+
15
+ def deepstack
16
+ @deepstack ||= DeepStack.new("http://localhost:#{port[:no_auth][:http]}")
17
+ end
18
+
19
+ def auth_deepstack
20
+ @auth_deepstack ||= DeepStack.new("http://localhost:#{port[:auth][:http]}", api_key: 'myapikey',
21
+ admin_key: 'myadminkey')
22
+ end
23
+
24
+ def deepstack_ssl
25
+ @deepstack_ssl ||= DeepStack.new("https://localhost:#{port[:no_auth][:https]}",
26
+ verify_mode: OpenSSL::SSL::VERIFY_NONE)
27
+ end
28
+
29
+ def restart_deepstack_server
30
+ Rake::Task['deepstack:stop1'].execute
31
+ Rake::Task['deepstack:stop2'].execute
32
+ Rake::Task['deepstack:stop3'].execute
33
+ Rake::Task['deepstack:start'].execute
34
+ end
35
+
36
+ # rubocop:disable Metrics/BlockLength
37
+ RSpec.describe DeepStack do
38
+ image = File.read('spec/test_images/person-dog.jpg')
39
+
40
+ context 'Basic Usage' do
41
+ it 'can be initialized with a url' do
42
+ expect(deepstack).not_to be_nil
43
+ end
44
+
45
+ it 'can close and reopen its http connection' do
46
+ result = deepstack.face_list
47
+ expect(result).to be_an Array
48
+ expect(deepstack.http.started?).to be true
49
+ deepstack.close
50
+ expect(deepstack.http.started?).to be false
51
+ result = deepstack.face_list
52
+ expect(deepstack.http.started?).to be true
53
+ expect(result).to be_an Array
54
+ expect(deepstack.http.started?).to be true
55
+ end
56
+
57
+ it 'can recover from a server restart' do
58
+ result = deepstack.face_list
59
+ expect(result).to be_an Array
60
+
61
+ Rake::Task['deepstack:stop1'].execute
62
+ expect { deepstack.face_list }.to raise_exception(Errno::ECONNREFUSED)
63
+ Rake::Task['deepstack:start'].execute
64
+ sleep 1
65
+
66
+ result = deepstack.face_list
67
+ expect(result).to be_an Array
68
+ end
69
+
70
+ it 'can work with an api key' do
71
+ result = auth_deepstack.detect_objects(image)
72
+ expect(result).to be_an Array
73
+ end
74
+
75
+ it 'can use ssl' do
76
+ result = deepstack_ssl.face_list
77
+ expect(result).to be_an Array
78
+ end
79
+
80
+ it 'does not raise an exception in its constructor' do
81
+ result = DeepStack.new('http://192.168.254.254')
82
+ expect(result).to be_truthy
83
+ end
84
+ end
85
+
86
+ context 'Object Detection' do
87
+ it 'can detect objects' do
88
+ result = deepstack.detect_objects(image)
89
+ # pp result
90
+ expect(result).to be_an Array
91
+ expect(result.size).to be > 0
92
+ expect(result.first).to be_a Hash
93
+ expect(result.first.keys).to include(*%w[confidence label x_max x_min y_max y_min])
94
+ end
95
+ end
96
+
97
+ context 'Face Detection' do
98
+ it 'can detect faces' do
99
+ result = deepstack.detect_faces(image)
100
+ # pp result
101
+ expect(result).to be_an Array
102
+ expect(result.first).to be_a Hash
103
+ expect(result.first.keys).to include(*%w[confidence x_min y_min x_max y_max])
104
+ end
105
+ end
106
+
107
+ context 'Face Recognition' do
108
+ it 'can return a list of registered faces' do
109
+ faces = deepstack.face_list
110
+ expect(faces).to be_an Array
111
+ end
112
+
113
+ it 'can register a face with one image object' do
114
+ # image = File.read('spec/test_images/idriselba3.jpeg')
115
+ result = deepstack.register_face('user1', image)
116
+ expect(result).to be true
117
+
118
+ faces = deepstack.face_list
119
+ expect(faces).to include 'user1'
120
+ end
121
+
122
+ it 'can register a face given a File object of an image file' do
123
+ result = File.open('spec/test_images/person-dog.jpg') do |img|
124
+ deepstack.register_face('user2', img)
125
+ end
126
+ expect(result).to be true
127
+
128
+ faces = deepstack.face_list
129
+ expect(faces).to include 'user2'
130
+ end
131
+
132
+ it 'can register a face with multiple image objects' do
133
+ face_count_before = deepstack.face_list.size
134
+ images = []
135
+ images << File.read('spec/test_images/person-dog.jpg')
136
+ images << File.read('spec/test_images/person-dog.jpg')
137
+ images << File.read('spec/test_images/person-dog.jpg')
138
+
139
+ result = deepstack.register_face('user3', images)
140
+ expect(result).to be true
141
+
142
+ faces = deepstack.face_list
143
+ expect(faces).to include 'user3'
144
+ expect(faces.size).to be > face_count_before
145
+ end
146
+
147
+ it 'can register multiple faces/users' do
148
+ deepstack.delete_faces(deepstack.face_list)
149
+ faces = deepstack.face_list
150
+ expect(faces.size).to be 0
151
+
152
+ 3.times do |i|
153
+ deepstack.register_face("user#{i}", image)
154
+ end
155
+ faces = deepstack.face_list
156
+ expect(faces.size).to be 3
157
+
158
+ 3.times do |i|
159
+ expect(faces).to include "user#{i}"
160
+ end
161
+ end
162
+
163
+ it 'can recognize faces from an image' do
164
+ result = deepstack.recognize_faces(image)
165
+ # pp result
166
+ expect(result).to be_an Array
167
+ expect(result.first.keys).to include(*%w[confidence userid x_min y_min x_max y_max])
168
+ end
169
+
170
+ it 'can delete a registered face' do
171
+ face_count_before = deepstack.face_list.size
172
+ result = deepstack.delete_face('user1')
173
+ expect(result).to be true
174
+ expect(deepstack.face_list.size).to be < face_count_before
175
+ end
176
+
177
+ it 'can delete all registered faces' do
178
+ faces = deepstack.face_list
179
+ expect(faces.size).to be > 0
180
+ result = deepstack.delete_faces(faces)
181
+ expect(result).to be_a Hash
182
+ faces = deepstack.face_list
183
+ expect(faces.size).to eq 0
184
+ end
185
+
186
+ it 'can perform face matching' do
187
+ image2 = image
188
+ result = deepstack.face_match(image, image2)
189
+ expect(result).to be_a_kind_of(Numeric)
190
+ end
191
+ end
192
+
193
+ context 'Scene Recognition' do
194
+ it 'can detect scene' do
195
+ result = deepstack.identify_scene(image)
196
+ expect(result).to be_a Hash
197
+ # pp result
198
+ expect(result.keys).to include(*%w[confidence label])
199
+ end
200
+ end
201
+
202
+ # context 'Image Enhancer' do
203
+ # # before { skip('Skipping because this test is slow') }
204
+ # it 'can enhance image' do
205
+ # result = deepstack.enhance_image(image)
206
+ # expect(result).to be_truthy
207
+ # end
208
+ # end
209
+
210
+ context 'Custom Model' do
211
+ it 'can use a custom model' do
212
+ result = deepstack.custom_model('combined', image)
213
+ expect(result).to be_an Array
214
+ end
215
+ end
216
+ end
217
+ # rubocop:enable Metrics/BlockLength
metadata CHANGED
@@ -1,15 +1,99 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deepstack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jimmy Tanagra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-11 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-05-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.21'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.21'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop-rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
13
97
  description:
14
98
  email:
15
99
  - jcode@tanagra.id.au
@@ -17,13 +101,16 @@ executables: []
17
101
  extensions: []
18
102
  extra_rdoc_files: []
19
103
  files:
104
+ - README.md
20
105
  - lib/deep_stack/custom_model.rb
21
106
  - lib/deep_stack/deep_stack.rb
22
107
  - lib/deep_stack/detection.rb
23
108
  - lib/deep_stack/face.rb
109
+ - lib/deep_stack/image.rb
24
110
  - lib/deep_stack/scene.rb
25
111
  - lib/deep_stack/version.rb
26
112
  - lib/deepstack.rb
113
+ - spec/deepstack_spec.rb
27
114
  homepage: https://github.com/jimtng/deepstack-ruby
28
115
  licenses:
29
116
  - EPL-2.0
@@ -51,4 +138,5 @@ rubygems_version: 3.0.3.1
51
138
  signing_key:
52
139
  specification_version: 4
53
140
  summary: A Ruby wrapper for DeepStack API
54
- test_files: []
141
+ test_files:
142
+ - spec/deepstack_spec.rb