Skip to content

Commit 53ca1e0

Browse files
committed
Merge pull request #3 from kaneshin/feature/imageurl
Add feature to parse image URI using http client
2 parents 1bcfc66 + 63b4203 commit 53ca1e0

File tree

9 files changed

+251
-108
lines changed

9 files changed

+251
-108
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ $ go get github.com/kaneshin/pigeon
5555
# Default Detection is LabelDetection.
5656
$ pigeon assets/lenna.jpg
5757
$ pigeon -face gs://bucket_name/lenna.jpg
58+
$ pigeon -label https://httpbin.org/image/jpeg
5859
```
5960

6061
![pigeon-cmd](https://raw.githubusercontent.com/kaneshin/pigeon/master/assets/pigeon-cmd.gif)
@@ -87,14 +88,16 @@ func main() {
8788
// "GOOGLE_APPLICATION_CREDENTIALS" if pass empty string to argument.
8889
// creds := credentials.NewApplicationCredentials("")
8990

90-
client, err := pigeon.New(creds)
91+
config := NewConfig().WithCredentials(creds)
92+
93+
client, err := pigeon.New(config)
9194
if err != nil {
9295
panic(err)
9396
}
9497

9598
// To call multiple image annotation requests.
9699
feature := pigeon.NewFeature(pigeon.LabelDetection)
97-
batch, err := pigeon.NewBatchAnnotateImageRequest([]string{"lenna.jpg"}, feature)
100+
batch, err := client.NewBatchAnnotateImageRequest([]string{"lenna.jpg"}, feature)
98101
if err != nil {
99102
panic(err)
100103
}
@@ -191,7 +194,7 @@ if err != nil {
191194

192195
```go
193196
// To call multiple image annotation requests.
194-
batch, err := pigeon.NewBatchAnnotateImageRequest(list, features()...)
197+
batch, err := client.NewBatchAnnotateImageRequest(list, features()...)
195198
if err != nil {
196199
panic(err)
197200
}

client.go

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package pigeon
22

33
import (
4+
"encoding/base64"
45
"encoding/json"
56
"fmt"
7+
"io/ioutil"
8+
"net/http"
9+
"net/url"
610

711
"golang.org/x/net/context"
812
"golang.org/x/oauth2/google"
@@ -12,15 +16,35 @@ import (
1216
)
1317

1418
type (
15-
// Client is
19+
// A Client provides cloud vision service.
1620
Client struct {
21+
// The context object to use when signing requests.
22+
// Defaults to `context.Background()`.
23+
// context context.Context
24+
25+
// The Config provides service configuration for service clients.
26+
config *Config
27+
28+
// The service object.
1729
service *vision.Service
1830
}
1931
)
2032

2133
// New returns a pointer to a new Client object.
22-
func New(c *credentials.Credentials) (*Client, error) {
23-
creds, err := c.Get()
34+
func New(c *Config) (*Client, error) {
35+
if c == nil {
36+
// Sets a configuration if passed nil value.
37+
c = NewConfig()
38+
}
39+
40+
// TODO: Running on GAE.
41+
42+
if c.Credentials == nil {
43+
// Sets application credentials by defaults.
44+
c.Credentials = credentials.NewApplicationCredentials("")
45+
}
46+
47+
creds, err := c.Credentials.Get()
2448
if err != nil {
2549
return nil, err
2650
}
@@ -38,6 +62,7 @@ func New(c *credentials.Credentials) (*Client, error) {
3862
}
3963

4064
return &Client{
65+
config: c,
4166
service: srv,
4267
}, nil
4368
}
@@ -46,3 +71,101 @@ func New(c *credentials.Credentials) (*Client, error) {
4671
func (c Client) ImagesService() *vision.ImagesService {
4772
return c.service.Images
4873
}
74+
75+
// NewBatchAnnotateImageRequest returns a pointer to a new vision's BatchAnnotateImagesRequest.
76+
func (c Client) NewBatchAnnotateImageRequest(list []string, features ...*vision.Feature) (*vision.BatchAnnotateImagesRequest, error) {
77+
batch := &vision.BatchAnnotateImagesRequest{}
78+
batch.Requests = []*vision.AnnotateImageRequest{}
79+
for _, v := range list {
80+
req, err := c.NewAnnotateImageRequest(v, features...)
81+
if err != nil {
82+
return nil, err
83+
}
84+
batch.Requests = append(batch.Requests, req)
85+
}
86+
return batch, nil
87+
}
88+
89+
// NewAnnotateImageRequest returns a pointer to a new vision's AnnotateImagesRequest.
90+
func (c Client) NewAnnotateImageRequest(v interface{}, features ...*vision.Feature) (*vision.AnnotateImageRequest, error) {
91+
switch v.(type) {
92+
case []byte:
93+
// base64
94+
return NewAnnotateImageContentRequest(v.([]byte), features...)
95+
case string:
96+
u, err := url.Parse(v.(string))
97+
if err != nil {
98+
return nil, err
99+
}
100+
switch u.Scheme {
101+
case "gs":
102+
// GcsImageUri: Google Cloud Storage image URI. It must be in the
103+
// following form:
104+
// "gs://bucket_name/object_name". For more
105+
return NewAnnotateImageSourceRequest(u.String(), features...)
106+
case "http", "https":
107+
httpClient := c.config.HTTPClient
108+
if httpClient == nil {
109+
httpClient = http.DefaultClient
110+
}
111+
resp, err := httpClient.Get(u.String())
112+
if err != nil {
113+
return nil, err
114+
}
115+
defer resp.Body.Close()
116+
if resp.StatusCode >= http.StatusBadRequest {
117+
return nil, http.ErrMissingFile
118+
}
119+
body, err := ioutil.ReadAll(resp.Body)
120+
if err != nil {
121+
return nil, err
122+
}
123+
return c.NewAnnotateImageRequest(body, features...)
124+
}
125+
// filepath
126+
b, err := ioutil.ReadFile(u.String())
127+
if err != nil {
128+
return nil, err
129+
}
130+
return c.NewAnnotateImageRequest(b, features...)
131+
}
132+
return &vision.AnnotateImageRequest{}, nil
133+
}
134+
135+
// NewAnnotateImageContentRequest returns a pointer to a new vision's AnnotateImagesRequest.
136+
func NewAnnotateImageContentRequest(body []byte, features ...*vision.Feature) (*vision.AnnotateImageRequest, error) {
137+
req := &vision.AnnotateImageRequest{
138+
Image: NewAnnotateImageContent(body),
139+
Features: features,
140+
}
141+
return req, nil
142+
}
143+
144+
// NewAnnotateImageSourceRequest returns a pointer to a new vision's AnnotateImagesRequest.
145+
func NewAnnotateImageSourceRequest(source string, features ...*vision.Feature) (*vision.AnnotateImageRequest, error) {
146+
req := &vision.AnnotateImageRequest{
147+
Image: NewAnnotateImageSource(source),
148+
Features: features,
149+
}
150+
return req, nil
151+
}
152+
153+
// NewAnnotateImageContent returns a pointer to a new vision's Image.
154+
// It's contained image content, represented as a stream of bytes.
155+
func NewAnnotateImageContent(body []byte) *vision.Image {
156+
return &vision.Image{
157+
// Content: Image content, represented as a stream of bytes.
158+
Content: base64.StdEncoding.EncodeToString(body),
159+
}
160+
}
161+
162+
// NewAnnotateImageSource returns a pointer to a new vision's Image.
163+
// It's contained external image source (i.e. Google Cloud Storage image
164+
// location).
165+
func NewAnnotateImageSource(source string) *vision.Image {
166+
return &vision.Image{
167+
Source: &vision.ImageSource{
168+
GcsImageUri: source,
169+
},
170+
}
171+
}

client_test.go

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,71 @@ import (
44
"os"
55
"testing"
66

7-
"github.com/kaneshin/pigeon/credentials"
87
"github.com/stretchr/testify/assert"
8+
vision "google.golang.org/api/vision/v1"
99
)
1010

1111
func TestClient(t *testing.T) {
1212
os.Clearenv()
1313

1414
assert := assert.New(t)
1515

16-
creds := credentials.NewApplicationCredentials("")
17-
client, err := New(creds)
16+
conf := NewConfig()
17+
client, err := New(conf)
1818
assert.Nil(client)
1919
assert.Error(err)
2020

2121
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "credentials/example.json")
22-
creds = credentials.NewApplicationCredentials("")
23-
client, err = New(creds)
22+
conf = NewConfig()
23+
client, err = New(conf)
2424
assert.NotNil(client)
2525
assert.NoError(err)
2626
assert.NotNil(client.service)
2727
assert.NotNil(client.ImagesService())
2828
}
29+
30+
func TestNewAnnotateImageRequest(t *testing.T) {
31+
assert := assert.New(t)
32+
33+
var (
34+
req *vision.AnnotateImageRequest
35+
err error
36+
)
37+
const (
38+
gcsImageURI = "gs://bucket/sample.png"
39+
fp = "assets/lenna.jpg"
40+
imageURI = "https://httpbin.org/image/jpeg"
41+
imageURINoExists = "https://httpbin.org/image/jpeg/none"
42+
)
43+
features := NewFeature(LabelDetection)
44+
client, err := New(nil)
45+
assert.NoError(err)
46+
47+
// GCS
48+
req, err = client.NewAnnotateImageRequest(gcsImageURI, features)
49+
assert.NoError(err)
50+
if assert.NotNil(req) {
51+
assert.Equal("", req.Image.Content)
52+
assert.Equal(gcsImageURI, req.Image.Source.GcsImageUri)
53+
}
54+
55+
// Filepath
56+
req, err = client.NewAnnotateImageRequest(fp, features)
57+
assert.NoError(err)
58+
if assert.NotNil(req) {
59+
assert.NotEqual("", req.Image.Content)
60+
assert.Nil(req.Image.Source)
61+
}
62+
63+
// Image URI
64+
req, err = client.NewAnnotateImageRequest(imageURI, features)
65+
assert.NoError(err)
66+
if assert.NotNil(req) {
67+
assert.NotEqual("", req.Image.Content)
68+
assert.Nil(req.Image.Source)
69+
}
70+
71+
req, err = client.NewAnnotateImageRequest(imageURINoExists, features)
72+
assert.Error(err)
73+
assert.Nil(req)
74+
}

config.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package pigeon
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/kaneshin/pigeon/credentials"
7+
)
8+
9+
type (
10+
// A Config provides service configuration for service clients. By default,
11+
// all clients will use the {defaults.DefaultConfig} structure.
12+
Config struct {
13+
// The credentials object to use when signing requests.
14+
// Defaults to application credentials file.
15+
Credentials *credentials.Credentials
16+
17+
// The HTTP client to use when sending requests.
18+
// Defaults to `http.DefaultClient`.
19+
HTTPClient *http.Client
20+
}
21+
)
22+
23+
// NewConfig returns a pointer of new Config objects.
24+
func NewConfig() *Config {
25+
return &Config{}
26+
}
27+
28+
// WithCredentials sets a config Credentials value returning a Config pointer
29+
// for chaining.
30+
func (c *Config) WithCredentials(creds *credentials.Credentials) *Config {
31+
c.Credentials = creds
32+
return c
33+
}
34+
35+
// WithHTTPClient sets a config HTTPClient value returning a Config pointer
36+
// for chaining.
37+
func (c *Config) WithHTTPClient(client *http.Client) *Config {
38+
c.HTTPClient = client
39+
return c
40+
}

config_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package pigeon
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/kaneshin/pigeon/credentials"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestConfig(t *testing.T) {
12+
assert := assert.New(t)
13+
14+
conf := NewConfig()
15+
assert.NotNil(conf)
16+
assert.Nil(conf.Credentials)
17+
assert.Nil(conf.HTTPClient)
18+
19+
creds := credentials.NewApplicationCredentials("")
20+
client := http.DefaultClient
21+
conf.WithCredentials(creds).
22+
WithHTTPClient(client)
23+
assert.NotNil(conf.Credentials)
24+
assert.NotNil(conf.HTTPClient)
25+
}

0 commit comments

Comments
 (0)