clarinet 0.5.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 +7 -0
- data/.gitignore +89 -0
- data/.rspec +1 -0
- data/.rubocop.yml +2299 -0
- data/.travis.yml +6 -0
- data/Gemfile +20 -0
- data/LICENSE +15 -0
- data/README.md +29 -0
- data/Rakefile +12 -0
- data/clarinet.gemspec +19 -0
- data/lib/clarinet.rb +17 -0
- data/lib/clarinet/app.rb +28 -0
- data/lib/clarinet/client.rb +161 -0
- data/lib/clarinet/concept.rb +22 -0
- data/lib/clarinet/concepts.rb +47 -0
- data/lib/clarinet/error/error.rb +29 -0
- data/lib/clarinet/input.rb +63 -0
- data/lib/clarinet/inputs.rb +108 -0
- data/lib/clarinet/model.rb +133 -0
- data/lib/clarinet/models.rb +106 -0
- data/lib/clarinet/status.rb +13 -0
- data/lib/clarinet/utils.rb +102 -0
- data/lib/clarinet/version.rb +5 -0
- data/spec/app_spec.rb +12 -0
- data/spec/fixtures/model-predict-default.txt +11 -0
- data/spec/fixtures/models-get.txt +11 -0
- data/spec/fixtures/models-list.txt +11 -0
- data/spec/fixtures/search-general-concept.txt +11 -0
- data/spec/model_spec.rb +57 -0
- data/spec/models_spec.rb +106 -0
- data/spec/spec_helper.rb +109 -0
- metadata +101 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clarinet
|
4
|
+
class Inputs
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
# @!method []
|
8
|
+
# @see Array#[]
|
9
|
+
# @return [Clarinet::Model]
|
10
|
+
# @!method each
|
11
|
+
# @see Array#each
|
12
|
+
# @!method map
|
13
|
+
# @see Array#map
|
14
|
+
# @!method find
|
15
|
+
# @see Array#find
|
16
|
+
# @!method select
|
17
|
+
# @see Array#select
|
18
|
+
# @!method select
|
19
|
+
# @see Array#select
|
20
|
+
delegate [:[], :each, :map, :find, :select, :reject] => :@inputs
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
def initialize(app, raw_data = [])
|
24
|
+
@app = app
|
25
|
+
|
26
|
+
@raw_data = raw_data
|
27
|
+
|
28
|
+
@inputs = raw_data.map do |input_data|
|
29
|
+
Clarinet::Input.new app, input_data
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Adds an input or multiple inputs
|
34
|
+
# @return [Clarinet::Inputs] Instance of Inputs
|
35
|
+
def create(inputs)
|
36
|
+
inputs = [inputs] unless inputs.is_a? Array
|
37
|
+
inputs = inputs.map { |input| Clarinet::Utils.format_input(input) }
|
38
|
+
|
39
|
+
data = @app.client.inputs_create inputs
|
40
|
+
Clarinet::Inputs.new data[:inputs]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Delete an input or a list of inputs by id
|
44
|
+
# @return [Hash] API response
|
45
|
+
def delete(id)
|
46
|
+
@app.client.input_delete id if id.is_a? String
|
47
|
+
@app.client.inputs_delete id if id.is_a? Array
|
48
|
+
end
|
49
|
+
|
50
|
+
# Delete all inputs
|
51
|
+
# @return [Hash] API response
|
52
|
+
def delete_all
|
53
|
+
@app.client.inputs_delete_all
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get all inputs in app
|
57
|
+
# @param options [Hash] Listing options
|
58
|
+
# @option options [Int] :page (1) The page number
|
59
|
+
# @option options [Int] :per_page (20) Number of models to return per page
|
60
|
+
# @return [Clarinet::Inputs] Inputs instance
|
61
|
+
def list(options = { page: 1, per_page: 20 })
|
62
|
+
data = @app.client.inputs options
|
63
|
+
Clarinet::Inputs.new @app, data[:inputs]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get input by id
|
67
|
+
# @param id [String] The input id
|
68
|
+
# @return [Clarinet::Input] Input instance
|
69
|
+
def get(id)
|
70
|
+
data = @app.client.input id
|
71
|
+
Clarinet::Input.new @app, data[:input]
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get inputs status (number of uploaded, in process or failed inputs)
|
75
|
+
# @return [Hash] API response
|
76
|
+
def status
|
77
|
+
@app.client.inputs_status
|
78
|
+
end
|
79
|
+
|
80
|
+
def merge_concepts(inputs)
|
81
|
+
update 'merge', inputs
|
82
|
+
end
|
83
|
+
|
84
|
+
def overwrite_concepts(inputs)
|
85
|
+
update 'overwrite', inputs
|
86
|
+
end
|
87
|
+
|
88
|
+
def delete_concepts(inputs)
|
89
|
+
update 'remove', inputs
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def update(action, inputs)
|
95
|
+
inputs = [inputs] unless inputs.is_a? Array
|
96
|
+
inputs = inputs.map { |input| Clarinet::Utils.format_input(input) }
|
97
|
+
|
98
|
+
data = {
|
99
|
+
action: action,
|
100
|
+
inputs: inputs
|
101
|
+
}
|
102
|
+
|
103
|
+
response_data = @app.client.inputs_update data
|
104
|
+
Clarinet::Inputs.new @app, response_data[:inputs]
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'httparty'
|
4
|
+
|
5
|
+
module Clarinet
|
6
|
+
class Model
|
7
|
+
|
8
|
+
GENERAL = 'aaa03c23b3724a16a56b629203edc62c'
|
9
|
+
FOOD = 'bd367be194cf45149e75f01d59f77ba7'
|
10
|
+
TRAVEL = 'eee28c313d69466f836ab83287a54ed9'
|
11
|
+
NSFW = 'e9576d86d2004ed1a38ba0cf39ecb4b1'
|
12
|
+
WEDDINGS = 'c386b7a870114f4a87477c0824499348'
|
13
|
+
COLOR = 'eeed0b6733a644cea07cf4c60f87ebb7'
|
14
|
+
|
15
|
+
MAX_INPUT_COUNT = 128
|
16
|
+
|
17
|
+
# @return [Hash] Raw API data used to construct this instance
|
18
|
+
attr_reader :raw_data
|
19
|
+
|
20
|
+
# @return [String] Model id
|
21
|
+
attr_reader :id
|
22
|
+
|
23
|
+
# @return [String] Model name
|
24
|
+
attr_reader :name
|
25
|
+
|
26
|
+
# @return [String] Created at timestamp
|
27
|
+
attr_reader :created_at
|
28
|
+
|
29
|
+
# @return [String]
|
30
|
+
attr_reader :app_id
|
31
|
+
|
32
|
+
# @return [Hash]
|
33
|
+
attr_reader :output_info
|
34
|
+
|
35
|
+
# @return [String]
|
36
|
+
attr_reader :model_version
|
37
|
+
|
38
|
+
# @!visibility private
|
39
|
+
def initialize(app, raw_data)
|
40
|
+
@app = app
|
41
|
+
|
42
|
+
@raw_data = raw_data
|
43
|
+
|
44
|
+
@id = raw_data[:id]
|
45
|
+
@name = raw_data[:name]
|
46
|
+
@created_at = raw_data[:created_at]
|
47
|
+
@app_id = raw_data[:app_id]
|
48
|
+
@output_info = raw_data[:output_info]
|
49
|
+
|
50
|
+
@model_version = raw_data[:model_version]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns all the model's output info
|
54
|
+
# @return [Clarinet::Model] Model instance with complete output_info data
|
55
|
+
def get_output_info
|
56
|
+
response_data = @app.client.model_output_info @id
|
57
|
+
Clarinet::Model.new @app, response_data[:model]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns model ouputs according to inputs
|
61
|
+
# @param inputs [String, Hash, Array<String>, Array<Hash>] An array of objects/object/string pointing to
|
62
|
+
# an image resource. A string can either be a url or base64 image bytes. Object keys explained below:
|
63
|
+
# @!macro predict_inputs
|
64
|
+
# @option inputs [Hash] :image Object with at least +:url+ or +:base64+ key as explained below:
|
65
|
+
# * +:url+ (String) A publicly accessibly
|
66
|
+
# * +:base64+ (String) Base64 string representing image bytes
|
67
|
+
# * +:crop+ (Array<Float>) An array containing the percent to be cropped from top, left, bottom and right
|
68
|
+
# @return [Hash] API response
|
69
|
+
def predict(inputs, config = {})
|
70
|
+
video = config[:video] || false
|
71
|
+
config.delete :video
|
72
|
+
|
73
|
+
inputs = [inputs] unless inputs.is_a? Array
|
74
|
+
inputs = inputs.map { |input| Clarinet::Utils.format_media_predict(input) }
|
75
|
+
|
76
|
+
@app.client.outputs id, inputs, config
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns a list of versions of the model
|
80
|
+
# @param options [Hash] Listing options
|
81
|
+
# @option options [Int] :page (1) The page number
|
82
|
+
# @option options [Int] :per_page (20) Number of models to return per page
|
83
|
+
# @return [Hash] API response
|
84
|
+
def versions(options = { page: 1, per_page: 20 })
|
85
|
+
@app.client.model_versions @id, options
|
86
|
+
end
|
87
|
+
|
88
|
+
# Remove concepts from a model
|
89
|
+
# @param concepts [Array<Hash>] List of concept hashes with id
|
90
|
+
# @return [Clarinet::Model] Model instance
|
91
|
+
def delete_concepts(concepts)
|
92
|
+
concepts = [concepts] unless concepts.is_a? Array
|
93
|
+
update 'remove', { concepts: concepts }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Merge concepts to a model
|
97
|
+
# @param (see #delete_concepts)
|
98
|
+
# @return (see #delete_concepts)
|
99
|
+
def merge_concepts(concepts)
|
100
|
+
concepts = [concepts] unless concepts.is_a? Array
|
101
|
+
update 'merge', { concepts: concepts }
|
102
|
+
end
|
103
|
+
|
104
|
+
# Overwrite concepts in a model
|
105
|
+
# @param (see #delete_concepts)
|
106
|
+
# @return (see #delete_concepts)
|
107
|
+
def overwrite_concepts(concepts)
|
108
|
+
concepts = [concepts] unless concepts.is_a? Array
|
109
|
+
update 'merge', { concepts: concepts }
|
110
|
+
end
|
111
|
+
|
112
|
+
# Create a new model version
|
113
|
+
# @note Training takes some time and the new version will not be immediately available.
|
114
|
+
# @return [Clarinet::Model] Model instance
|
115
|
+
def train
|
116
|
+
response_data = @app.client.model_train @id
|
117
|
+
Clarinet::Model.new @app, response_data[:model]
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def update(action, obj)
|
123
|
+
model_data = obj.merge id: @id
|
124
|
+
data = {
|
125
|
+
models: [Clarinet::Utils.format_model(model_data)]
|
126
|
+
}
|
127
|
+
|
128
|
+
response_data = @app.client.models_update data
|
129
|
+
Clarinet::Model.new @app, response_data[:models].first
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clarinet
|
4
|
+
class Models
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
# @!method []
|
8
|
+
# @see Array#[]
|
9
|
+
# @return [Clarinet::Model]
|
10
|
+
# @!method each
|
11
|
+
# @see Array#each
|
12
|
+
# @!method map
|
13
|
+
# @see Array#map
|
14
|
+
# @!method find
|
15
|
+
# @see Array#find
|
16
|
+
# @!method first
|
17
|
+
# @see Array#first
|
18
|
+
# @!method last
|
19
|
+
# @see Array#last
|
20
|
+
# @!method select
|
21
|
+
# @see Array#select
|
22
|
+
# @!method select
|
23
|
+
# @see Array#select
|
24
|
+
delegate [:[], :each, :map, :find, :first, :last, :select, :reject, :size] => :@models
|
25
|
+
|
26
|
+
# @return [Hash] Raw API data used to construct this instance
|
27
|
+
attr_reader :raw_data
|
28
|
+
|
29
|
+
# @!visibility private
|
30
|
+
def initialize(app, raw_data = [])
|
31
|
+
@app = app
|
32
|
+
@raw_data = raw_data
|
33
|
+
|
34
|
+
@models = raw_data.map do |model_data|
|
35
|
+
Clarinet::Model.new app, model_data
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a Model instance given model id or name. It will call search if name is given.
|
40
|
+
# @param model [String, Hash, Clarinet::Model]
|
41
|
+
# If String, it is assumed to be model id. Otherwise, if Hash is given, it can have any of the following keys:
|
42
|
+
# @option model [String] :id Model id
|
43
|
+
# @option model [String] :name Model name
|
44
|
+
# @option model [String] :version Model version
|
45
|
+
# @option model [String] :type (nil) This can be "concept", "color", "embed", "facedetect", "cluster" or "blur"
|
46
|
+
# @return [Clarinet::Model] Model instance corresponding to the given id
|
47
|
+
# or the first search result
|
48
|
+
def init_model(model)
|
49
|
+
model_data = {}
|
50
|
+
|
51
|
+
model_data[:id] = model if model.is_a? String
|
52
|
+
model_data = model if model.is_a? Hash
|
53
|
+
model_data = model.raw_data if model.is_a? Clarinet::Model
|
54
|
+
|
55
|
+
return Clarinet::Model.new @app, model_data if model_data[:id]
|
56
|
+
|
57
|
+
search_results = search model_data[:name], model_data[:type]
|
58
|
+
|
59
|
+
return search_results.find { |result| result.model_version.id == model_data[:version] }.first if model_data[:version]
|
60
|
+
|
61
|
+
search_results.first
|
62
|
+
end
|
63
|
+
|
64
|
+
# Predict using a specific model
|
65
|
+
# @param model (see #init_model)
|
66
|
+
# @param inputs (see Clarinet::Model#predict)
|
67
|
+
# @macro predict_inputs
|
68
|
+
# @return [Hash] Data returned by the API with symbolized keys
|
69
|
+
def predict(model, inputs)
|
70
|
+
init_model(model).predict(inputs)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return all the models
|
74
|
+
# @param options [Hash] Listing options
|
75
|
+
# @option options [Int] :page (1) The page number
|
76
|
+
# @option options [Int] :per_page (20) Number of models to return per page
|
77
|
+
# @return [Clarinet::Models]
|
78
|
+
def list(options = { page: 1, per_page: 20 })
|
79
|
+
data = @app.client.models options
|
80
|
+
Clarinet::Models.new @app, data[:models]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns a model specified by ID
|
84
|
+
# @param id [String] The model's id
|
85
|
+
# @return [Clarinet::Model] Model instance
|
86
|
+
def get(id)
|
87
|
+
data = @app.client.model id
|
88
|
+
Clarinet::Model.new @app, data[:model]
|
89
|
+
end
|
90
|
+
|
91
|
+
# Search for models by name or type
|
92
|
+
# @param name [String] The model name
|
93
|
+
# @param type [String] This can be "concept", "color", "embed", "facedetect", "cluster" or "blur"
|
94
|
+
# @return [Clarinet::Models] Models instance
|
95
|
+
def search(name, type = nil)
|
96
|
+
query = {
|
97
|
+
name: name,
|
98
|
+
type: type
|
99
|
+
}
|
100
|
+
|
101
|
+
data = @app.client.models_search query
|
102
|
+
Clarinet::Models.new @app, data[:models]
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'addressable/uri'
|
4
|
+
|
5
|
+
module Clarinet
|
6
|
+
# @!visibility private
|
7
|
+
class Utils
|
8
|
+
|
9
|
+
def self.check_response_status(status)
|
10
|
+
status_code = status[:code]
|
11
|
+
|
12
|
+
return if status_code == Clarinet::Status::SUCCESS
|
13
|
+
|
14
|
+
error_class = Clarinet::Error::ApiError
|
15
|
+
error_class = Clarinet::Error::InvalidAuthTokenError if status_code == Clarinet::Status::INVALID_AUTH_TOKEN
|
16
|
+
error_class = Clarinet::Error::ApiKeyNotFoundError if status_code == Clarinet::Status::API_KEY_NOT_FOUND
|
17
|
+
error_class = Clarinet::Error::BadRequestFormatError if status_code == Clarinet::Status::BAD_REQUEST_FORMAT
|
18
|
+
error_class = Clarinet::Error::InvalidRequestError if status_code == Clarinet::Status::INVALID_REQUEST
|
19
|
+
error_class = Clarinet::Error::ImageDecodingError if status_code == Clarinet::Status::IMAGE_DECODING_FAILED
|
20
|
+
|
21
|
+
new_error = error_class.new status[:description]
|
22
|
+
new_error.code = status_code
|
23
|
+
new_error.description = status[:description]
|
24
|
+
new_error.details = status[:details]
|
25
|
+
raise new_error
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.format_model(model_data)
|
29
|
+
formatted = {
|
30
|
+
id: model_data[:id]
|
31
|
+
}
|
32
|
+
|
33
|
+
formatted[:name] = model_data[:name] if model_data.key? :name
|
34
|
+
|
35
|
+
output_info = {}
|
36
|
+
if model_data.key? :concepts_mutually_exclusive
|
37
|
+
output_info[:output_config] = output_info[:output_config] || {}
|
38
|
+
output_info[:output_config][:concepts_mutually_exclusive] = model_data[:concepts_mutually_exclusive]
|
39
|
+
end
|
40
|
+
|
41
|
+
if model_data.key? :closed_environment
|
42
|
+
output_info[:output_config] = output_info[:output_config] || {}
|
43
|
+
output_info[:output_config][:closed_environment] = model_data[:closed_environment]
|
44
|
+
end
|
45
|
+
|
46
|
+
if model_data.key? :concepts
|
47
|
+
output_info[:data] = {
|
48
|
+
concepts: model_data[:concepts].map { |c| format_concept(c) }
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
formatted[:output_info] = output_info
|
53
|
+
formatted
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.format_concept(concept_data)
|
57
|
+
return { id: concept_data } if concept_data.is_a? String
|
58
|
+
concept_data
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.format_input(input_data, include_image = true)
|
62
|
+
input_data = { url: input_data } if input_data.is_a? String
|
63
|
+
|
64
|
+
formatted = {
|
65
|
+
id: input_data[:id],
|
66
|
+
data: {}
|
67
|
+
}
|
68
|
+
|
69
|
+
formatted[:data][:concepts] = input_data.concepts if input_data.key? :concepts
|
70
|
+
formatted[:data][:metadata] = input_data.metadata if input_data.key? :metadata
|
71
|
+
formatted[:data][:geo] = { geo_point: input_data.geo } if input_data.key? :geo
|
72
|
+
|
73
|
+
if include_image
|
74
|
+
formatted[:data][:image] = {
|
75
|
+
url: input_data[:url],
|
76
|
+
base64: input_data[:base64],
|
77
|
+
crop: input_data[:crop],
|
78
|
+
allow_duplicate_url: input_data.fetch(:allow_duplicate_url, false)
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
formatted
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.format_media_predict(input_data, type = :image)
|
86
|
+
if input_data.is_a? String
|
87
|
+
input_data = { base64: input_data } unless valid_url? input_data
|
88
|
+
input_data = { url: input_data } if valid_url? input_data
|
89
|
+
end
|
90
|
+
|
91
|
+
data = {}
|
92
|
+
data[type] = input_data
|
93
|
+
{ data: data }
|
94
|
+
end
|
95
|
+
|
96
|
+
private_class_method def self.valid_url?(url)
|
97
|
+
uri = Addressable::URI.parse url
|
98
|
+
uri.scheme == 'http' || uri.scheme == 'https'
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|