getimg_client 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
+ SHA256:
3
+ metadata.gz: 88f3306bcd0b08d90484dd83f9a8bb613d303cbfd4999d0b14177cd44c072037
4
+ data.tar.gz: 2d557e3cda0acd3099ea52e7cf77f7d1e9eee330b9264c7f95e62ff759e8bcd4
5
+ SHA512:
6
+ metadata.gz: 8954fbd6faa0b69b939303e9a9174121f039b11449e14dd7bb866c5adc9049b460bab4ea84ab4a98114d20504d6a1a9e0a5ea598204aaf4c84837d59c5cfbd02
7
+ data.tar.gz: 7b42386867ab12876da580ce9337510a86dd764fbf4fc8d427f5f3b11e73ecb1bb4259e6234540a0f2098e5d7feb70e29f219217b7e001337cfaada41cdd86b5
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # GetimgClient
2
+
3
+ ## Introduction
4
+ GetimgClient is a Ruby gem designed to interact with the Getimg.ai API, enabling users to generate, manipulate, and enhance images using various AI models. This gem simplifies the process of making API requests to Getimg.ai, allowing for seamless integration of image generation and manipulation capabilities into your Ruby applications.
5
+
6
+ ## Functions
7
+
8
+ ### `set_api_key(api_key)`
9
+ Set or override the API key used for authentication with the Getimg.ai API. This key can also be set by providing the **GETIMG_API_KEY** environment variable.
10
+
11
+ ### `retrieve_models`
12
+ Fetch and cache the list of available models from the API.
13
+
14
+ ### `models`
15
+ Retrieve cached models or fetch them if not already retrieved.
16
+
17
+ ### `generate_image(prompt, model:, **options)`
18
+ Generate an image based on a text prompt and specified model. Supports various image manipulation methods such as image-to-image, inpainting, controlnet, face fixing, and upscaling.
19
+
20
+ ### `get_balance`
21
+ Retrieve the current account balance from the Getimg.ai API.
22
+
23
+ ## Request Routing
24
+ The `generate_image` method routes requests based on the provided options and the model's supported pipelines:
25
+ - **text-to-image**: Default if no images are provided.
26
+ - **image-to-image**: Triggered if a base image is provided.
27
+ - **inpaint**: Triggered if a mask image is provided.
28
+ - **controlnet**: Triggered if controlnet options are provided.
29
+ - **face-fix**: Triggered if the model supports face-fix and a base image is provided.
30
+ - **upscale**: Triggered if the model supports upscale and a base image is provided.
31
+
32
+ ## Parameters
33
+ Parameters for `generate_image` and other methods are consistent with the official Getimg.ai API documentation. For detailed parameter descriptions, refer to the [official API documentation](https://docs.getimg.ai/reference/introduction).
34
+
35
+ ## Error Reporting
36
+ Errors in the `GetimgClient` are handled through exceptions. The `handle_response` method is used to process API responses, raising errors when necessary:
37
+
38
+ - **ArgumentError**: Raised for invalid arguments or unknown models.
39
+ - **StandardError**: Raised for HTTP errors or API response errors.
40
+
41
+ API errors are captured from the JSON response and included in the raised exception message. This includes both HTTP status errors and specific API error messages. For example:
42
+
43
+ ```ruby
44
+ raise StandardError, "Error: #{result['error']['message']} (code: #{result['error']['code']})"
45
+ ```
46
+
47
+ If an HTTP error occurs, the error message will include both the HTTP status and the error message from the API response, if available.
48
+
49
+ ### Models
50
+ The provided `model` option will determine the model use, and contribute to the client's inference of the desired endpoint. Models can be the string `id` listed online at [the GetImg dashboard](https://dashboard.getimg.ai/models) or retrieved using the `GetimgClient.models` method. Equally, you can use symbols instead. For example, `:realistic_vision_v5_1` will translate to `'realistic-vision-v5-1'`
51
+
52
+ ### Base Image Options
53
+ The `base_image` option can be provided, which will automatically set the "image" property for image-to-image, controlnet, face-fix, upscale and inpaint calls, and can be either a file path or a base64 encoded string of the file contents.
54
+
55
+ ### Results
56
+ If all went well and no errors were reported, the response will be equal to the API response received, and thus align with the official documentation. Typically, this will be a hash containing an `image` property with the Base64 encoded image contents, a `seed` property with the generation's seed value, and a `cost` property with the usage cost for the generation.
57
+
58
+ ## Code Samples
59
+
60
+ ### Set API Key
61
+ ```ruby
62
+ GetimgClient.set_api_key('your_api_key')
63
+ ```
64
+
65
+ ### Generate Text-to-Image
66
+ ```ruby
67
+ result = GetimgClient.generate_image("A scenic landscape", model: :stable_diffusion_v1_5, width: 512, height: 512)
68
+ puts result["image"]
69
+ ```
70
+
71
+ ### Generate Image-to-Image
72
+ ```ruby
73
+ base_image = "path/to/base_image.jpg"
74
+ result = GetimgClient.generate_image("Enhance the scene", model: :stable_diffusion_v1_5, base_image:, strength: 0.7)
75
+ puts result["image"]
76
+ ```
77
+
78
+ ### Generate an image using Controlnet
79
+ ```ruby
80
+ base_image = "path/to/base_image.jpg"
81
+ result = GetimgClient.generate_image("Enhance the scene", model: :stable_diffusion_v1_5, base_image:, strength: 0.7, controlnet: canny-1.1)
82
+ puts result["image"]
83
+ ```
84
+
85
+ ### Generate Inpainting
86
+ ```ruby
87
+ base_image = Base64.strict_encode64(File.read("path/to/base_image.jpg"))
88
+ mask_image = Base64.strict_encode64(File.read("path/to/mask_image.jpg"))
89
+ result = GetimgClient.generate_image("Fill the white area with sky", model: :stable_diffusion_v1_5_inpainting, base_image:, mask_image_path: mask_image)
90
+ puts result["image"]
91
+ ```
92
+
93
+ ### Face Fix
94
+ ```ruby
95
+ base_image = Base64.strict_encode64(File.read("path/to/base_image.jpg"))
96
+ result = GetimgClient.generate_image("Fix faces", model: :gfpgan_v1_3, base_image:)
97
+ puts result["image"]
98
+ ```
99
+
100
+ ### Upscale
101
+ ```ruby
102
+ base_image = Base64.strict_encode64(File.read("path/to/base_image.jpg"))
103
+ result = GetimgClient.generate_image("Upscale image", model: :real_esrgan_4x, base_image:, scale: 4)
104
+ puts result["image"]
105
+ ```
106
+
107
+ ### Logging
108
+ Request logging will automatically be enabled, if the **RAILS_ENV** environment variable is set to "development"
109
+
110
+ ### Unit Testing
111
+ The gem includes unit tests using Minitest. Integration tests make real API calls and will incur usage fees. Ensure your API key is set in the environment before running tests.
112
+
113
+ To run the integration tests:
114
+
115
+ ```bash
116
+ GETIMG_API_KEY=your_api_key rake integration
117
+ ```
118
+
119
+ Integration test images are stored in the tmp directory for review.
@@ -0,0 +1,208 @@
1
+ class GetimgClient
2
+ require 'base64'
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+
7
+ # Load the API endpoints from a JSON file
8
+ API_ENDPOINTS = JSON.parse(File.read(File.join(__dir__, 'endpoints.json'))).freeze
9
+
10
+ # Initialize the API key from environment variable
11
+ @api_key = ENV['GETIMG_API_KEY']
12
+
13
+ # Set or override the API key
14
+ def self.set_api_key(api_key)
15
+ @api_key = api_key
16
+ end
17
+
18
+ # Retrieve models from the API
19
+ def self.retrieve_models
20
+ uri = URI(API_ENDPOINTS['models'])
21
+ request = Net::HTTP::Get.new(uri)
22
+ request['Authorization'] = "Bearer #{@api_key}"
23
+ request['accept'] = 'application/json'
24
+
25
+ response = make_get_request(uri, request)
26
+ @models = parse_models_response(response)
27
+ end
28
+
29
+ # Get models, retrieving them if not already fetched
30
+ def self.models
31
+ @models ||= retrieve_models
32
+ end
33
+
34
+ # Generate an image based on the prompt and model
35
+ def self.generate_image(prompt, model:, **options)
36
+ # Validate the prompt
37
+ raise ArgumentError, 'Prompt is required' unless prompt.is_a?(String) && !prompt.strip.empty?
38
+
39
+ # Extract image paths from options
40
+ base_image = options.delete(:base_image)
41
+ mask_image_path = options.delete(:mask_image_path)
42
+ controlnet = options.delete(:controlnet)
43
+
44
+ # Convert model to appropriate ID format
45
+ model_id = model.to_s.gsub('_', '-')
46
+ model_info = models[model_id] || raise(ArgumentError, "Unknown model: #{model}")
47
+
48
+ # Determine the requested pipeline
49
+ requested_pipeline = method_requested(base_image, mask_image_path, controlnet, model_info).to_s.gsub('_', '-')
50
+
51
+ # Check if the model supports the requested pipeline
52
+ unless model_info[:pipelines].include?(requested_pipeline)
53
+ raise ArgumentError, "#{model_info[:name]} does not support #{requested_pipeline}"
54
+ end
55
+
56
+ # Determine the endpoint key based on the model family and pipeline
57
+ endpoint_key = determine_endpoint_key(model_info, requested_pipeline)
58
+ uri = URI(API_ENDPOINTS[endpoint_key.to_s])
59
+
60
+ # Handle image to image, controlnet, instruct, inpaint, face-fix, and upscale pipelines
61
+ if %w[image-to-image controlnet instruct inpaint face-fix upscale].include?(requested_pipeline)
62
+ image_base64 = validate_file_or_base64(base_image)
63
+ options.merge!(image: image_base64)
64
+ options.merge!(controlnet: controlnet) if requested_pipeline == 'controlnet'
65
+ if requested_pipeline == 'inpaint'
66
+ mask_image_base64 = validate_file_or_base64(mask_image_path)
67
+ options.merge!(mask_image: mask_image_base64)
68
+ end
69
+ end
70
+
71
+ # Ensure scale is an integer if present
72
+ options[:scale] = options[:scale].to_i if options[:scale]
73
+
74
+ # Logging request details in development environment
75
+ if ENV['RAILS_ENV'] == 'development'
76
+ puts "Generating image with the following details:"
77
+ puts "Prompt: #{prompt}"
78
+ puts "Model: #{model_id}"
79
+ puts "Requested Pipeline: #{requested_pipeline}"
80
+ puts "Endpoint URI: #{uri}"
81
+ truncated_options = options.transform_values { |v| v.is_a?(String) && v.length > 50 ? "#{v[0, 50]}..." : v }
82
+ puts "Options: #{truncated_options}"
83
+ end
84
+
85
+ # Create and send the request
86
+ request = create_request(uri, prompt, model: model_id, **options)
87
+ response = make_post_request(uri, request)
88
+ handle_response(response)
89
+ end
90
+
91
+ # Retrieve the current account balance
92
+ def self.get_balance
93
+ uri = URI(API_ENDPOINTS['balance'])
94
+ request = Net::HTTP::Get.new(uri)
95
+ request['Authorization'] = "Bearer #{@api_key}"
96
+ request['accept'] = 'application/json'
97
+
98
+ response = make_get_request(uri, request)
99
+ handle_response(response)
100
+ end
101
+
102
+ private
103
+
104
+ # Determine the requested method based on provided image paths, controlnet, and model_info
105
+ def self.method_requested(base_image, mask_image_path, controlnet, model_info)
106
+ return :controlnet if controlnet
107
+ return :inpaint if mask_image_path
108
+ return :instruct if model_info[:pipelines].include?('instruct') # Adjusting for instruct model
109
+ return :face_fix if base_image && !mask_image_path && !controlnet && model_info[:pipelines].include?('face-fix')
110
+ return :upscale if base_image && !mask_image_path && !controlnet && model_info[:pipelines].include?('upscale')
111
+ return :image_to_image if base_image
112
+ :text_to_image
113
+ end
114
+
115
+ # Determine the appropriate endpoint key based on the model family and pipeline
116
+ def self.determine_endpoint_key(model_info, requested_pipeline)
117
+ if model_info[:family] == 'stable-diffusion-xl'
118
+ return :sdxl_image_to_image if requested_pipeline == 'image-to-image'
119
+ return :sdxl_inpaint if requested_pipeline == 'inpaint'
120
+ return :sdxl_text_to_image
121
+ elsif model_info[:family] == 'latent-consistency'
122
+ return :lcm_image_to_image if requested_pipeline == 'image-to-image'
123
+ return :lcm_text_to_image
124
+ else
125
+ return requested_pipeline.gsub('-', '_').to_sym
126
+ end
127
+ end
128
+
129
+ # Create an HTTP POST request with the given parameters
130
+ def self.create_request(uri, prompt, model:, **options)
131
+ request = Net::HTTP::Post.new(uri)
132
+ request['Authorization'] = "Bearer #{@api_key}"
133
+ request['accept'] = 'application/json'
134
+ request['content-type'] = 'application/json'
135
+
136
+ body = {
137
+ prompt: prompt,
138
+ model: model
139
+ }.merge(options)
140
+
141
+ request.body = body.to_json
142
+ request
143
+ end
144
+
145
+ # Make an HTTP POST request
146
+ def self.make_post_request(uri, request)
147
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
148
+ http.request(request)
149
+ end
150
+ end
151
+
152
+ # Make an HTTP GET request
153
+ def self.make_get_request(uri, request)
154
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
155
+ http.request(request)
156
+ end
157
+ end
158
+
159
+ # Handle the API response, raising errors if present
160
+ def self.handle_response(response)
161
+ case response
162
+ when Net::HTTPSuccess
163
+ result = JSON.parse(response.body)
164
+ if result['error']
165
+ raise StandardError, "Error: #{result['error']['message']} (code: #{result['error']['code']})"
166
+ end
167
+ result
168
+ else
169
+ error_message = "HTTP Error: #{response.message} (code: #{response.code})"
170
+ verbose_message = JSON.parse(response.body)['error']['message'] rescue nil
171
+ error_message += " - #{verbose_message}" if verbose_message
172
+ raise StandardError, error_message
173
+ end
174
+ end
175
+
176
+ # Validate if the input is a valid file path or base64 string
177
+ def self.validate_file_or_base64(input)
178
+ if File.exist?(input)
179
+ Base64.strict_encode64(File.read(input))
180
+ elsif input.match?(/\A([A-Za-z0-9+\/]{4})*([A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}==)?\z/)
181
+ input
182
+ else
183
+ raise ArgumentError, 'Invalid file path or base64 string'
184
+ end
185
+ end
186
+
187
+ # Parse the response from the models API to extract model details
188
+ def self.parse_models_response(response)
189
+ models = {}
190
+ case response
191
+ when Net::HTTPSuccess
192
+ JSON.parse(response.body).each do |model|
193
+ models[model['id']] = {
194
+ name: model['name'],
195
+ family: model['family'],
196
+ pipelines: model['pipelines'],
197
+ base_resolution: model['base_resolution'],
198
+ price: model['price'],
199
+ author_url: model['author_url'],
200
+ license_url: model['license_url']
201
+ }
202
+ end
203
+ else
204
+ raise StandardError, "HTTP Error: #{response.message} (code: #{response.code})"
205
+ end
206
+ models
207
+ end
208
+ end
@@ -0,0 +1,68 @@
1
+ require_relative "../test_helper"
2
+
3
+ class GetimgClientIntegrationTest < Minitest::Test
4
+ def setup
5
+ @base_image = Base64.strict_encode64(File.read(File.join(__dir__, "sample_image.jpeg")))
6
+
7
+ @small_image = Base64.strict_encode64(File.read(File.join(__dir__, "small_sample_image.jpeg")))
8
+
9
+ @mask_image = Base64.strict_encode64(File.read(File.join(__dir__, "mask_image.jpeg")))
10
+ end
11
+
12
+ def test_generate_text_to_image
13
+ puts "-- Running test_generate_text_to_image..."
14
+ result = GetimgClient.generate_image("A scenic landscape", model: :stable_diffusion_v1_5, width: 512, height: 512)
15
+ assert result["image"]
16
+ save_image(result["image"], "test_generate_text_to_image")
17
+ end
18
+
19
+ def test_generate_image_to_image
20
+ puts "-- Running test_generate_image_to_image..."
21
+ result = GetimgClient.generate_image("Enhance the scene", model: :stable_diffusion_v1_5, base_image: @base_image)
22
+ assert result["image"]
23
+ save_image(result["image"], "test_generate_image_to_image")
24
+ end
25
+
26
+ def test_generate_controlnet
27
+ puts "-- Running test_generate_controlnet..."
28
+ result = GetimgClient.generate_image("Outline of a landscape", model: :stable_diffusion_v1_5, base_image: @base_image, controlnet: "canny-1.1")
29
+ assert result["image"]
30
+ save_image(result["image"], "test_generate_controlnet")
31
+ end
32
+
33
+ def test_generate_instruct
34
+ puts "-- Running test_generate_instruct..."
35
+ result = GetimgClient.generate_image("Make it night time", model: :instruct_pix2pix, base_image: @base_image)
36
+ assert result["image"]
37
+ save_image(result["image"], "test_generate_instruct")
38
+ end
39
+
40
+ def test_generate_inpaint
41
+ puts "-- Running test_generate_inpaint..."
42
+ result = GetimgClient.generate_image("Fill the white area with sky", model: :stable_diffusion_v1_5_inpainting, base_image: @base_image, mask_image_path: @mask_image)
43
+ assert result["image"]
44
+ save_image(result["image"], "test_generate_inpaint")
45
+ end
46
+
47
+ def test_face_fix
48
+ puts "-- Running test_face_fix..."
49
+ result = GetimgClient.generate_image("Fix faces", model: :gfpgan_v1_3, base_image: @base_image)
50
+ assert result["image"]
51
+ save_image(result["image"], "test_face_fix")
52
+ end
53
+
54
+ def test_upscale
55
+ puts "-- Running test_upscale..."
56
+ result = GetimgClient.generate_image("Upscale image", model: :real_esrgan_4x, base_image: @small_image, scale: 4)
57
+ assert result["image"]
58
+ save_image(result["image"], "test_upscale")
59
+ end
60
+
61
+ private
62
+
63
+ def save_image(base64_image, filename)
64
+ File.open(File.join(__dir__, "../../tmp/#{filename}.jpeg"), "wb") do |file|
65
+ file.write(Base64.decode64(base64_image.split(",").last))
66
+ end
67
+ end
68
+ end
Binary file
Binary file
@@ -0,0 +1,2 @@
1
+ require "minitest/autorun"
2
+ require "getimg_client"
@@ -0,0 +1,13 @@
1
+ require_relative "../test_helper"
2
+
3
+ class GetimgClientTest < Minitest::Test
4
+ def test_retrieve_models
5
+ models = GetimgClient.retrieve_models
6
+ assert models.any?
7
+ end
8
+
9
+ def test_get_balance
10
+ balance = GetimgClient.get_balance
11
+ assert balance["amount"]
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: getimg_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Melvin Sommer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-06-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.11'
27
+ description: A Ruby client for interfacing with the Getimg API.
28
+ email:
29
+ - sommer.melvin@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - lib/getimg_client.rb
36
+ - test/integration/getimg_client_integration_test.rb
37
+ - test/integration/mask_image.jpeg
38
+ - test/integration/sample_image.jpeg
39
+ - test/integration/small_sample_image.jpeg
40
+ - test/test_helper.rb
41
+ - test/unit/getimg_client_test.rb
42
+ homepage: https://gitlab.com/coeusit/getimg_client
43
+ licenses:
44
+ - MIT
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.4.10
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Client for Getimg API
65
+ test_files:
66
+ - test/integration/getimg_client_integration_test.rb
67
+ - test/integration/mask_image.jpeg
68
+ - test/integration/sample_image.jpeg
69
+ - test/integration/small_sample_image.jpeg
70
+ - test/test_helper.rb
71
+ - test/unit/getimg_client_test.rb