Skip to content

Commit 7082235

Browse files
authoredAug 10, 2023
Rsdk 4095: Edit the camera interface to match new get_images proto (viamrobotics#2766)
1 parent 99085be commit 7082235

File tree

11 files changed

+106
-53
lines changed

11 files changed

+106
-53
lines changed
 

‎components/camera/camera.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ type Properties struct {
6363
DistortionParams transform.Distorter
6464
}
6565

66+
// NamedImage is a struct that associates the source from where the image came from to the Image.
67+
type NamedImage struct {
68+
Image image.Image
69+
SourceName string
70+
}
71+
6672
// A Camera is a resource that can capture frames.
6773
type Camera interface {
6874
resource.Resource
@@ -75,7 +81,7 @@ type VideoSource interface {
7581

7682
// Images is used for getting simultaneous images from different sensors,
7783
// along with associated metadata (just timestamp for now). It's not for getting a time series of images from the same sensor.
78-
Images(ctx context.Context) ([]image.Image, time.Time, error)
84+
Images(ctx context.Context) ([]NamedImage, resource.ResponseMetadata, error)
7985
// Stream returns a stream that makes a best effort to return consecutive images
8086
// that may have a MIME type hint dictated in the context via gostream.WithMIMETypeHint.
8187
Stream(ctx context.Context, errHandlers ...gostream.ErrorHandler) (gostream.VideoStream, error)
@@ -105,7 +111,7 @@ type PointCloudSource interface {
105111

106112
// A ImagesSource is a source that can return a list of images with timestamp.
107113
type ImagesSource interface {
108-
Images(ctx context.Context) ([]image.Image, time.Time, error)
114+
Images(ctx context.Context) ([]NamedImage, resource.ResponseMetadata, error)
109115
}
110116

111117
// FromVideoSource creates a Camera resource from a VideoSource.
@@ -242,23 +248,23 @@ func (vs *videoSource) Stream(ctx context.Context, errHandlers ...gostream.Error
242248
// Images is for getting simultaneous images from different sensors
243249
// If the underlying source did not specify an Images function, a default is applied.
244250
// The default returns a list of 1 image from ReadImage, and the current time.
245-
func (vs *videoSource) Images(ctx context.Context) ([]image.Image, time.Time, error) {
251+
func (vs *videoSource) Images(ctx context.Context) ([]NamedImage, resource.ResponseMetadata, error) {
246252
ctx, span := trace.StartSpan(ctx, "camera::videoSource::Images")
247253
defer span.End()
248254
if c, ok := vs.actualSource.(ImagesSource); ok {
249255
return c.Images(ctx)
250256
}
251257
img, release, err := ReadImage(ctx, vs.videoSource)
252258
if err != nil {
253-
return nil, time.Time{}, errors.Wrap(err, "videoSource: call to get Images failed")
259+
return nil, resource.ResponseMetadata{}, errors.Wrap(err, "videoSource: call to get Images failed")
254260
}
255261
defer func() {
256262
if release != nil {
257263
release()
258264
}
259265
}()
260266
ts := time.Now()
261-
return []image.Image{img}, ts, nil
267+
return []NamedImage{{img, ""}}, resource.ResponseMetadata{CapturedAt: ts}, nil
262268
}
263269

264270
// NextPointCloud returns the next PointCloud from the camera, or will error if not supported.

‎components/camera/camera_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,9 @@ func TestCameraWithProjector(t *testing.T) {
247247
images, _, err := cam2.Images(context.Background())
248248
test.That(t, err, test.ShouldBeNil)
249249
test.That(t, len(images), test.ShouldEqual, 1)
250-
test.That(t, images[0], test.ShouldHaveSameTypeAs, &rimage.DepthMap{})
251-
test.That(t, images[0].Bounds().Dx(), test.ShouldEqual, 1280)
252-
test.That(t, images[0].Bounds().Dy(), test.ShouldEqual, 720)
250+
test.That(t, images[0].Image, test.ShouldHaveSameTypeAs, &rimage.DepthMap{})
251+
test.That(t, images[0].Image.Bounds().Dx(), test.ShouldEqual, 1280)
252+
test.That(t, images[0].Image.Bounds().Dy(), test.ShouldEqual, 720)
253253

254254
test.That(t, cam2.Close(context.Background()), test.ShouldBeNil)
255255
}

‎components/camera/client.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"fmt"
77
"image"
88
"sync"
9-
"time"
109

1110
"github.com/edaniels/golog"
1211
"github.com/pkg/errors"
@@ -138,18 +137,18 @@ func (c *client) Stream(
138137
return stream, nil
139138
}
140139

141-
func (c *client) Images(ctx context.Context) ([]image.Image, time.Time, error) {
140+
func (c *client) Images(ctx context.Context) ([]NamedImage, resource.ResponseMetadata, error) {
142141
ctx, span := trace.StartSpan(ctx, "camera::client::Images")
143142
defer span.End()
144143

145144
resp, err := c.client.GetImages(ctx, &pb.GetImagesRequest{
146145
Name: c.name,
147146
})
148147
if err != nil {
149-
return nil, time.Time{}, errors.Wrap(err, "camera client: could not gets images from the camera")
148+
return nil, resource.ResponseMetadata{}, errors.Wrap(err, "camera client: could not gets images from the camera")
150149
}
151150

152-
images := make([]image.Image, 0, len(resp.Images))
151+
images := make([]NamedImage, 0, len(resp.Images))
153152
// keep everything lazy encoded by default, if type is unknown, attempt to decode it
154153
for _, img := range resp.Images {
155154
var rdkImage image.Image
@@ -165,12 +164,12 @@ func (c *client) Images(ctx context.Context) ([]image.Image, time.Time, error) {
165164
case pb.Format_FORMAT_UNSPECIFIED:
166165
rdkImage, _, err = image.Decode(bytes.NewReader(img.Image))
167166
if err != nil {
168-
return nil, time.Time{}, err
167+
return nil, resource.ResponseMetadata{}, err
169168
}
170169
}
171-
images = append(images, rdkImage)
170+
images = append(images, NamedImage{rdkImage, img.SourceName})
172171
}
173-
return images, resp.ResponseMetadata.CapturedAt.AsTime(), nil
172+
return images, resource.ResponseMetadataFromProto(resp.ResponseMetadata), nil
174173
}
175174

176175
func (c *client) NextPointCloud(ctx context.Context) (pointcloud.PointCloud, error) {

‎components/camera/client_test.go

+17-15
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,17 @@ func TestClient(t *testing.T) {
7575
injectCamera.ProjectorFunc = func(ctx context.Context) (transform.Projector, error) {
7676
return projA, nil
7777
}
78-
injectCamera.ImagesFunc = func(ctx context.Context) ([]image.Image, time.Time, error) {
79-
images := []image.Image{}
78+
injectCamera.ImagesFunc = func(ctx context.Context) ([]camera.NamedImage, resource.ResponseMetadata, error) {
79+
images := []camera.NamedImage{}
8080
// one color image
8181
color := rimage.NewImage(40, 50)
82-
images = append(images, color)
82+
images = append(images, camera.NamedImage{color, "color"})
8383
// one depth image
8484
depth := rimage.NewEmptyDepthMap(10, 20)
85-
images = append(images, depth)
85+
images = append(images, camera.NamedImage{depth, "depth"})
8686
// a timestamp of 12345
8787
ts := time.UnixMilli(12345)
88-
return images, ts, nil
88+
return images, resource.ResponseMetadata{ts}, nil
8989
}
9090
injectCamera.StreamFunc = func(ctx context.Context, errHandlers ...gostream.ErrorHandler) (gostream.VideoStream, error) {
9191
return gostream.NewEmbeddedVideoStreamFromReader(gostream.VideoReaderFunc(func(ctx context.Context) (image.Image, func(), error) {
@@ -192,18 +192,20 @@ func TestClient(t *testing.T) {
192192
test.That(t, propsB.SupportsPCD, test.ShouldBeTrue)
193193
test.That(t, propsB.IntrinsicParams, test.ShouldResemble, intrinsics)
194194

195-
images, ts, err := camera1Client.Images(context.Background())
195+
images, meta, err := camera1Client.Images(context.Background())
196196
test.That(t, err, test.ShouldBeNil)
197-
test.That(t, ts, test.ShouldEqual, time.UnixMilli(12345))
197+
test.That(t, meta.CapturedAt, test.ShouldEqual, time.UnixMilli(12345))
198198
test.That(t, len(images), test.ShouldEqual, 2)
199-
test.That(t, images[0].Bounds().Dx(), test.ShouldEqual, 40)
200-
test.That(t, images[0].Bounds().Dy(), test.ShouldEqual, 50)
201-
test.That(t, images[0], test.ShouldHaveSameTypeAs, &rimage.LazyEncodedImage{})
202-
test.That(t, images[0].ColorModel(), test.ShouldHaveSameTypeAs, color.RGBAModel)
203-
test.That(t, images[1].Bounds().Dx(), test.ShouldEqual, 10)
204-
test.That(t, images[1].Bounds().Dy(), test.ShouldEqual, 20)
205-
test.That(t, images[1], test.ShouldHaveSameTypeAs, &rimage.LazyEncodedImage{})
206-
test.That(t, images[1].ColorModel(), test.ShouldHaveSameTypeAs, color.Gray16Model)
199+
test.That(t, images[0].SourceName, test.ShouldEqual, "color")
200+
test.That(t, images[0].Image.Bounds().Dx(), test.ShouldEqual, 40)
201+
test.That(t, images[0].Image.Bounds().Dy(), test.ShouldEqual, 50)
202+
test.That(t, images[0].Image, test.ShouldHaveSameTypeAs, &rimage.LazyEncodedImage{})
203+
test.That(t, images[0].Image.ColorModel(), test.ShouldHaveSameTypeAs, color.RGBAModel)
204+
test.That(t, images[1].SourceName, test.ShouldEqual, "depth")
205+
test.That(t, images[1].Image.Bounds().Dx(), test.ShouldEqual, 10)
206+
test.That(t, images[1].Image.Bounds().Dy(), test.ShouldEqual, 20)
207+
test.That(t, images[1].Image, test.ShouldHaveSameTypeAs, &rimage.LazyEncodedImage{})
208+
test.That(t, images[1].Image.ColorModel(), test.ShouldHaveSameTypeAs, color.Gray16Model)
207209

208210
// Do
209211
resp, err := camera1Client.DoCommand(context.Background(), testutils.TestCommand)

‎components/camera/replaypcd/replaypcd.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"bytes"
66
"compress/gzip"
77
"context"
8-
"image"
98
"sync"
109
"time"
1110

@@ -291,8 +290,8 @@ func addGRPCMetadata(ctx context.Context, timeRequested, timeReceived *timestamp
291290
}
292291

293292
// Images is a part of the camera interface but is not implemented for replay.
294-
func (replay *pcdCamera) Images(ctx context.Context) ([]image.Image, time.Time, error) {
295-
return nil, time.Time{}, errors.New("Images is unimplemented")
293+
func (replay *pcdCamera) Images(ctx context.Context) ([]camera.NamedImage, resource.ResponseMetadata, error) {
294+
return nil, resource.ResponseMetadata{}, errors.New("Images is unimplemented")
296295
}
297296

298297
// Properties is a part of the camera interface but is not implemented for replay.

‎components/camera/server.go

+4-8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
commonpb "go.viam.com/api/common/v1"
1313
pb "go.viam.com/api/component/camera/v1"
1414
"google.golang.org/genproto/googleapis/api/httpbody"
15-
"google.golang.org/protobuf/types/known/timestamppb"
1615

1716
"go.viam.com/rdk/pointcloud"
1817
"go.viam.com/rdk/protoutils"
@@ -106,30 +105,27 @@ func (s *serviceServer) GetImages(
106105
}
107106
// request the images, and then check to see what the underlying type is to determine
108107
// what to encode as. If it's color, just encode as JPEG.
109-
imgs, ts, err := cam.Images(ctx)
108+
imgs, metadata, err := cam.Images(ctx)
110109
if err != nil {
111110
return nil, errors.Wrap(err, "camera server GetImages could not call Images on the camera")
112111
}
113112
imagesMessage := make([]*pb.Image, 0, len(imgs))
114113
for _, img := range imgs {
115-
format, outBytes, err := encodeImageFromUnderlyingType(ctx, img)
114+
format, outBytes, err := encodeImageFromUnderlyingType(ctx, img.Image)
116115
if err != nil {
117116
return nil, errors.Wrap(err, "camera server GetImages could not encode the images")
118117
}
119118
imgMes := &pb.Image{
120-
SourceName: req.Name, // same as the camera name
119+
SourceName: img.SourceName,
121120
Format: format,
122121
Image: outBytes,
123122
}
124123
imagesMessage = append(imagesMessage, imgMes)
125124
}
126125
// right now the only metadata is timestamp
127-
metadata := &commonpb.ResponseMetadata{
128-
CapturedAt: timestamppb.New(ts),
129-
}
130126
resp := &pb.GetImagesResponse{
131127
Images: imagesMessage,
132-
ResponseMetadata: metadata,
128+
ResponseMetadata: metadata.AsProto(),
133129
}
134130

135131
return resp, nil

‎components/camera/server_test.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,17 @@ func TestServer(t *testing.T) {
9090
IntrinsicParams: intrinsics,
9191
}, nil
9292
}
93-
injectCamera.ImagesFunc = func(ctx context.Context) ([]image.Image, time.Time, error) {
94-
images := []image.Image{}
93+
injectCamera.ImagesFunc = func(ctx context.Context) ([]camera.NamedImage, resource.ResponseMetadata, error) {
94+
images := []camera.NamedImage{}
9595
// one color image
9696
color := rimage.NewImage(40, 50)
97-
images = append(images, color)
97+
images = append(images, camera.NamedImage{color, "color"})
9898
// one depth image
9999
depth := rimage.NewEmptyDepthMap(10, 20)
100-
images = append(images, depth)
100+
images = append(images, camera.NamedImage{depth, "depth"})
101101
// a timestamp of 12345
102102
ts := time.UnixMilli(12345)
103-
return images, ts, nil
103+
return images, resource.ResponseMetadata{ts}, nil
104104
}
105105
injectCamera.ProjectorFunc = func(ctx context.Context) (transform.Projector, error) {
106106
return projA, nil
@@ -395,7 +395,9 @@ func TestServer(t *testing.T) {
395395
test.That(t, resp.ResponseMetadata.CapturedAt.AsTime(), test.ShouldEqual, time.UnixMilli(12345))
396396
test.That(t, len(resp.Images), test.ShouldEqual, 2)
397397
test.That(t, resp.Images[0].Format, test.ShouldEqual, pb.Format_FORMAT_JPEG)
398+
test.That(t, resp.Images[0].SourceName, test.ShouldEqual, "color")
398399
test.That(t, resp.Images[1].Format, test.ShouldEqual, pb.Format_FORMAT_RAW_DEPTH)
400+
test.That(t, resp.Images[1].SourceName, test.ShouldEqual, "depth")
399401
})
400402

401403
t.Run("GetProperties", func(t *testing.T) {

‎components/camera/videosource/webcam.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -514,20 +514,20 @@ func (c *monitoredWebcam) Projector(ctx context.Context) (transform.Projector, e
514514
return c.exposedProjector.Projector(ctx)
515515
}
516516

517-
func (c *monitoredWebcam) Images(ctx context.Context) ([]image.Image, time.Time, error) {
517+
func (c *monitoredWebcam) Images(ctx context.Context) ([]camera.NamedImage, resource.ResponseMetadata, error) {
518518
if c, ok := c.underlyingSource.(camera.ImagesSource); ok {
519519
return c.Images(ctx)
520520
}
521521
img, release, err := camera.ReadImage(ctx, c.underlyingSource)
522522
if err != nil {
523-
return nil, time.Time{}, errors.Wrap(err, "monitoredWebcam: call to get Images failed")
523+
return nil, resource.ResponseMetadata{}, errors.Wrap(err, "monitoredWebcam: call to get Images failed")
524524
}
525525
defer func() {
526526
if release != nil {
527527
release()
528528
}
529529
}()
530-
return []image.Image{img}, time.Now(), nil
530+
return []camera.NamedImage{{img, c.Name().Name}}, resource.ResponseMetadata{time.Now()}, nil
531531
}
532532

533533
func (c *monitoredWebcam) Stream(ctx context.Context, errHandlers ...gostream.ErrorHandler) (gostream.VideoStream, error) {

‎resource/response_metadata.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package resource
2+
3+
import (
4+
"time"
5+
6+
commonpb "go.viam.com/api/common/v1"
7+
"google.golang.org/protobuf/types/known/timestamppb"
8+
)
9+
10+
// ResponseMetadata contains extra info associated with a Resource's standard response.
11+
type ResponseMetadata struct {
12+
CapturedAt time.Time
13+
}
14+
15+
// AsProto turns the ResponseMetadata struct into a protobuf message.
16+
func (rm ResponseMetadata) AsProto() *commonpb.ResponseMetadata {
17+
metadata := &commonpb.ResponseMetadata{}
18+
metadata.CapturedAt = timestamppb.New(rm.CapturedAt)
19+
return metadata
20+
}
21+
22+
// ResponseMetadataFromProto turns the protobuf message into a ResponseMetadata struct.
23+
func ResponseMetadataFromProto(proto *commonpb.ResponseMetadata) ResponseMetadata {
24+
metadata := ResponseMetadata{}
25+
metadata.CapturedAt = proto.CapturedAt.AsTime()
26+
return metadata
27+
}

‎resource/response_metadata_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package resource
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
commonpb "go.viam.com/api/common/v1"
8+
"go.viam.com/test"
9+
"google.golang.org/protobuf/types/known/timestamppb"
10+
)
11+
12+
func TestResponseToProto(t *testing.T) {
13+
ts := time.UnixMilli(12345)
14+
metadata := ResponseMetadata{CapturedAt: ts}
15+
proto := metadata.AsProto()
16+
test.That(t, proto.CapturedAt.AsTime(), test.ShouldEqual, ts)
17+
}
18+
19+
func TestResponseFromProto(t *testing.T) {
20+
ts := &timestamppb.Timestamp{Seconds: 12, Nanos: 345000000}
21+
proto := &commonpb.ResponseMetadata{CapturedAt: ts}
22+
metadata := ResponseMetadataFromProto(proto)
23+
test.That(t, metadata.CapturedAt, test.ShouldEqual, time.UnixMilli(12345))
24+
}

‎testutils/inject/camera.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package inject
22

33
import (
44
"context"
5-
"image"
6-
"time"
75

86
"github.com/pkg/errors"
97
"github.com/viamrobotics/gostream"
@@ -19,7 +17,7 @@ type Camera struct {
1917
camera.Camera
2018
name resource.Name
2119
DoFunc func(ctx context.Context, cmd map[string]interface{}) (map[string]interface{}, error)
22-
ImagesFunc func(ctx context.Context) ([]image.Image, time.Time, error)
20+
ImagesFunc func(ctx context.Context) ([]camera.NamedImage, resource.ResponseMetadata, error)
2321
StreamFunc func(
2422
ctx context.Context,
2523
errHandlers ...gostream.ErrorHandler,
@@ -79,7 +77,7 @@ func (c *Camera) Properties(ctx context.Context) (camera.Properties, error) {
7977
}
8078

8179
// Images calls the injected Images or the real version.
82-
func (c *Camera) Images(ctx context.Context) ([]image.Image, time.Time, error) {
80+
func (c *Camera) Images(ctx context.Context) ([]camera.NamedImage, resource.ResponseMetadata, error) {
8381
if c.ImagesFunc == nil {
8482
return c.Camera.Images(ctx)
8583
}

0 commit comments

Comments
 (0)
Please sign in to comment.