Skip to content

Commit

Permalink
Merge pull request #3 from kaneshin/feature/imageurl
Browse files Browse the repository at this point in the history
Add feature to parse image URI using http client
  • Loading branch information
kaneshin committed Mar 5, 2016
2 parents 1bcfc66 + 63b4203 commit 53ca1e0
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 108 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ $ go get github.com/kaneshin/pigeon
# Default Detection is LabelDetection.
$ pigeon assets/lenna.jpg
$ pigeon -face gs://bucket_name/lenna.jpg
$ pigeon -label https://httpbin.org/image/jpeg
```

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

client, err := pigeon.New(creds)
config := NewConfig().WithCredentials(creds)

client, err := pigeon.New(config)
if err != nil {
panic(err)
}

// To call multiple image annotation requests.
feature := pigeon.NewFeature(pigeon.LabelDetection)
batch, err := pigeon.NewBatchAnnotateImageRequest([]string{"lenna.jpg"}, feature)
batch, err := client.NewBatchAnnotateImageRequest([]string{"lenna.jpg"}, feature)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -191,7 +194,7 @@ if err != nil {

```go
// To call multiple image annotation requests.
batch, err := pigeon.NewBatchAnnotateImageRequest(list, features()...)
batch, err := client.NewBatchAnnotateImageRequest(list, features()...)
if err != nil {
panic(err)
}
Expand Down
129 changes: 126 additions & 3 deletions client.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package pigeon

import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"

"golang.org/x/net/context"
"golang.org/x/oauth2/google"
Expand All @@ -12,15 +16,35 @@ import (
)

type (
// Client is
// A Client provides cloud vision service.
Client struct {
// The context object to use when signing requests.
// Defaults to `context.Background()`.
// context context.Context

// The Config provides service configuration for service clients.
config *Config

// The service object.
service *vision.Service
}
)

// New returns a pointer to a new Client object.
func New(c *credentials.Credentials) (*Client, error) {
creds, err := c.Get()
func New(c *Config) (*Client, error) {
if c == nil {
// Sets a configuration if passed nil value.
c = NewConfig()
}

// TODO: Running on GAE.

if c.Credentials == nil {
// Sets application credentials by defaults.
c.Credentials = credentials.NewApplicationCredentials("")
}

creds, err := c.Credentials.Get()
if err != nil {
return nil, err
}
Expand All @@ -38,6 +62,7 @@ func New(c *credentials.Credentials) (*Client, error) {
}

return &Client{
config: c,
service: srv,
}, nil
}
Expand All @@ -46,3 +71,101 @@ func New(c *credentials.Credentials) (*Client, error) {
func (c Client) ImagesService() *vision.ImagesService {
return c.service.Images
}

// NewBatchAnnotateImageRequest returns a pointer to a new vision's BatchAnnotateImagesRequest.
func (c Client) NewBatchAnnotateImageRequest(list []string, features ...*vision.Feature) (*vision.BatchAnnotateImagesRequest, error) {
batch := &vision.BatchAnnotateImagesRequest{}
batch.Requests = []*vision.AnnotateImageRequest{}
for _, v := range list {
req, err := c.NewAnnotateImageRequest(v, features...)
if err != nil {
return nil, err
}
batch.Requests = append(batch.Requests, req)
}
return batch, nil
}

// NewAnnotateImageRequest returns a pointer to a new vision's AnnotateImagesRequest.
func (c Client) NewAnnotateImageRequest(v interface{}, features ...*vision.Feature) (*vision.AnnotateImageRequest, error) {
switch v.(type) {
case []byte:
// base64
return NewAnnotateImageContentRequest(v.([]byte), features...)
case string:
u, err := url.Parse(v.(string))
if err != nil {
return nil, err
}
switch u.Scheme {
case "gs":
// GcsImageUri: Google Cloud Storage image URI. It must be in the
// following form:
// "gs://bucket_name/object_name". For more
return NewAnnotateImageSourceRequest(u.String(), features...)
case "http", "https":
httpClient := c.config.HTTPClient
if httpClient == nil {
httpClient = http.DefaultClient
}
resp, err := httpClient.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
return nil, http.ErrMissingFile
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return c.NewAnnotateImageRequest(body, features...)
}
// filepath
b, err := ioutil.ReadFile(u.String())
if err != nil {
return nil, err
}
return c.NewAnnotateImageRequest(b, features...)
}
return &vision.AnnotateImageRequest{}, nil
}

// NewAnnotateImageContentRequest returns a pointer to a new vision's AnnotateImagesRequest.
func NewAnnotateImageContentRequest(body []byte, features ...*vision.Feature) (*vision.AnnotateImageRequest, error) {
req := &vision.AnnotateImageRequest{
Image: NewAnnotateImageContent(body),
Features: features,
}
return req, nil
}

// NewAnnotateImageSourceRequest returns a pointer to a new vision's AnnotateImagesRequest.
func NewAnnotateImageSourceRequest(source string, features ...*vision.Feature) (*vision.AnnotateImageRequest, error) {
req := &vision.AnnotateImageRequest{
Image: NewAnnotateImageSource(source),
Features: features,
}
return req, nil
}

// NewAnnotateImageContent returns a pointer to a new vision's Image.
// It's contained image content, represented as a stream of bytes.
func NewAnnotateImageContent(body []byte) *vision.Image {
return &vision.Image{
// Content: Image content, represented as a stream of bytes.
Content: base64.StdEncoding.EncodeToString(body),
}
}

// NewAnnotateImageSource returns a pointer to a new vision's Image.
// It's contained external image source (i.e. Google Cloud Storage image
// location).
func NewAnnotateImageSource(source string) *vision.Image {
return &vision.Image{
Source: &vision.ImageSource{
GcsImageUri: source,
},
}
}
56 changes: 51 additions & 5 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,71 @@ import (
"os"
"testing"

"github.com/kaneshin/pigeon/credentials"
"github.com/stretchr/testify/assert"
vision "google.golang.org/api/vision/v1"
)

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

assert := assert.New(t)

creds := credentials.NewApplicationCredentials("")
client, err := New(creds)
conf := NewConfig()
client, err := New(conf)
assert.Nil(client)
assert.Error(err)

os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "credentials/example.json")
creds = credentials.NewApplicationCredentials("")
client, err = New(creds)
conf = NewConfig()
client, err = New(conf)
assert.NotNil(client)
assert.NoError(err)
assert.NotNil(client.service)
assert.NotNil(client.ImagesService())
}

func TestNewAnnotateImageRequest(t *testing.T) {
assert := assert.New(t)

var (
req *vision.AnnotateImageRequest
err error
)
const (
gcsImageURI = "gs://bucket/sample.png"
fp = "assets/lenna.jpg"
imageURI = "https://httpbin.org/image/jpeg"
imageURINoExists = "https://httpbin.org/image/jpeg/none"
)
features := NewFeature(LabelDetection)
client, err := New(nil)
assert.NoError(err)

// GCS
req, err = client.NewAnnotateImageRequest(gcsImageURI, features)
assert.NoError(err)
if assert.NotNil(req) {
assert.Equal("", req.Image.Content)
assert.Equal(gcsImageURI, req.Image.Source.GcsImageUri)
}

// Filepath
req, err = client.NewAnnotateImageRequest(fp, features)
assert.NoError(err)
if assert.NotNil(req) {
assert.NotEqual("", req.Image.Content)
assert.Nil(req.Image.Source)
}

// Image URI
req, err = client.NewAnnotateImageRequest(imageURI, features)
assert.NoError(err)
if assert.NotNil(req) {
assert.NotEqual("", req.Image.Content)
assert.Nil(req.Image.Source)
}

req, err = client.NewAnnotateImageRequest(imageURINoExists, features)
assert.Error(err)
assert.Nil(req)
}
40 changes: 40 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package pigeon

import (
"net/http"

"github.com/kaneshin/pigeon/credentials"
)

type (
// A Config provides service configuration for service clients. By default,
// all clients will use the {defaults.DefaultConfig} structure.
Config struct {
// The credentials object to use when signing requests.
// Defaults to application credentials file.
Credentials *credentials.Credentials

// The HTTP client to use when sending requests.
// Defaults to `http.DefaultClient`.
HTTPClient *http.Client
}
)

// NewConfig returns a pointer of new Config objects.
func NewConfig() *Config {
return &Config{}
}

// WithCredentials sets a config Credentials value returning a Config pointer
// for chaining.
func (c *Config) WithCredentials(creds *credentials.Credentials) *Config {
c.Credentials = creds
return c
}

// WithHTTPClient sets a config HTTPClient value returning a Config pointer
// for chaining.
func (c *Config) WithHTTPClient(client *http.Client) *Config {
c.HTTPClient = client
return c
}
25 changes: 25 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package pigeon

import (
"net/http"
"testing"

"github.com/kaneshin/pigeon/credentials"
"github.com/stretchr/testify/assert"
)

func TestConfig(t *testing.T) {
assert := assert.New(t)

conf := NewConfig()
assert.NotNil(conf)
assert.Nil(conf.Credentials)
assert.Nil(conf.HTTPClient)

creds := credentials.NewApplicationCredentials("")
client := http.DefaultClient
conf.WithCredentials(creds).
WithHTTPClient(client)
assert.NotNil(conf.Credentials)
assert.NotNil(conf.HTTPClient)
}
Loading

0 comments on commit 53ca1e0

Please sign in to comment.