Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RSDK-9660] - fix-camera-image-returning-no-mime-type #4674

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion components/camera/camera.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"go.viam.com/rdk/rimage"
"go.viam.com/rdk/rimage/transform"
"go.viam.com/rdk/robot"
"go.viam.com/rdk/utils"
)

func init() {
Expand Down Expand Up @@ -166,7 +167,7 @@ func DecodeImageFromCamera(ctx context.Context, mimeType string, extra map[strin
if len(resBytes) == 0 {
return nil, errors.New("received empty bytes from camera")
}
img, err := rimage.DecodeImage(ctx, resBytes, resMetadata.MimeType)
img, err := rimage.DecodeImage(ctx, resBytes, utils.WithLazyMIMEType(resMetadata.MimeType))
if err != nil {
return nil, fmt.Errorf("could not decode into image.Image: %w", err)
}
Expand Down
9 changes: 6 additions & 3 deletions components/camera/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,14 @@ func (c *client) Image(ctx context.Context, mimeType string, extra map[string]in

if expectedType != "" && resp.MimeType != expectedType {
c.logger.CDebugw(ctx, "got different MIME type than what was asked for", "sent", expectedType, "received", resp.MimeType)
} else {
resp.MimeType = mimeType
if resp.MimeType == "" {
// if the user expected a mime_type and the successful response didn't have a mime type, assume the
// response's mime_type was what the user requested
resp.MimeType = mimeType
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes it so that we only override the camera.GetImageResponse.MimeType if it is empty string and the caller specified an expected mime type.

}
}

return resp.Image, ImageMetadata{MimeType: utils.WithLazyMIMEType(resp.MimeType)}, nil
return resp.Image, ImageMetadata{MimeType: resp.MimeType}, nil
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm moving this utils.WithLazyMIMEType call here: https://github.com/viamrobotics/rdk/pull/4674/files#diff-4eab9b93978c09650defc23c4274a4c77d87cfdea7775d86dcf8c838ec207a01R170 as that is the only caller of Image in viam-server and the reason why we were adding the lazy mime type.

}

func (c *client) Images(ctx context.Context) ([]NamedImage, resource.ResponseMetadata, error) {
Expand Down
27 changes: 26 additions & 1 deletion components/camera/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"image"
"image/color"
"image/jpeg"
"image/png"
"net"
"testing"
Expand Down Expand Up @@ -428,7 +429,14 @@ func TestClientLazyImage(t *testing.T) {
test.That(t, png.Encode(&imgBuf, img), test.ShouldBeNil)
imgPng, err := png.Decode(bytes.NewReader(imgBuf.Bytes()))
test.That(t, err, test.ShouldBeNil)
var jpegBuf bytes.Buffer
test.That(t, jpeg.Encode(&jpegBuf, img, &jpeg.Options{Quality: 100}), test.ShouldBeNil)
imgJpeg, err := jpeg.Decode(bytes.NewBuffer(jpegBuf.Bytes()))
test.That(t, err, test.ShouldBeNil)

injectCamera.PropertiesFunc = func(ctx context.Context) (camera.Properties, error) {
return camera.Properties{}, nil
}
injectCamera.ImageFunc = func(ctx context.Context, mimeType string, extra map[string]interface{}) ([]byte, camera.ImageMetadata, error) {
if mimeType == "" {
mimeType = rutils.MimeTypeRawRGBA
Expand Down Expand Up @@ -470,7 +478,6 @@ func TestClientLazyImage(t *testing.T) {
frameLazy := frame.(*rimage.LazyEncodedImage)
test.That(t, frameLazy.RawData(), test.ShouldResemble, imgBuf.Bytes())

ctx = context.Background()
frame, err = camera.DecodeImageFromCamera(ctx, rutils.WithLazyMIMEType(rutils.MimeTypePNG), nil, camera1Client)
test.That(t, err, test.ShouldBeNil)
test.That(t, frame, test.ShouldHaveSameTypeAs, &rimage.LazyEncodedImage{})
Expand All @@ -482,6 +489,24 @@ func TestClientLazyImage(t *testing.T) {
test.That(t, err, test.ShouldBeNil)
test.That(t, compVal, test.ShouldEqual, 0) // exact copy, no color conversion

// when DecodeImageFromCamera is called without a mime type, defaults to JPEG
var called bool
injectCamera.ImageFunc = func(ctx context.Context, mimeType string, extra map[string]interface{}) ([]byte, camera.ImageMetadata, error) {
called = true
test.That(t, mimeType, test.ShouldResemble, rutils.WithLazyMIMEType(rutils.MimeTypeJPEG))
resBytes, err := rimage.EncodeImage(ctx, imgPng, mimeType)
test.That(t, err, test.ShouldBeNil)
return resBytes, camera.ImageMetadata{MimeType: mimeType}, nil
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to non lazy

}
frame, err = camera.DecodeImageFromCamera(ctx, "", nil, camera1Client)
test.That(t, err, test.ShouldBeNil)
test.That(t, frame, test.ShouldHaveSameTypeAs, &rimage.LazyEncodedImage{})
frameLazy = frame.(*rimage.LazyEncodedImage)
test.That(t, frameLazy.MIMEType(), test.ShouldEqual, rutils.MimeTypeJPEG)
compVal, _, err = rimage.CompareImages(imgJpeg, frame)
test.That(t, err, test.ShouldBeNil)
test.That(t, compVal, test.ShouldEqual, 0) // exact copy, no color conversion
test.That(t, called, test.ShouldBeTrue)
test.That(t, conn.Close(), test.ShouldBeNil)
}

Expand Down
8 changes: 8 additions & 0 deletions rimage/lazy_encoded.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import (
"context"
"image"
"image/color"
"net/http"
"sync"

"go.viam.com/rdk/logging"
)

// LazyEncodedImage defers the decoding of an image until necessary.
Expand All @@ -29,6 +32,11 @@ type LazyEncodedImage struct {
// away with reading all metadata from the header of the image bytes.
// NOTE: Usage of an image that would fail to decode causes a lazy panic.
func NewLazyEncodedImage(imgBytes []byte, mimeType string) image.Image {
if mimeType == "" {
logging.Global().Warn("NewLazyEncodedImage called without a mime_type. " +
"Sniffing bytes to detect mime_type. Specify mime_type to reduce CPU utilization")
mimeType = http.DetectContentType(imgBytes)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will attempt to detect the mime type of the image bytes if mimeType is empty.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a cool function from that library - nice to know it's in there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this method can return 3 mime types that would be interesting to us:

  • image/*
  • application/octet-stream
  • something else

It might be worth logging if this code path is getting application/octet-stream
Definitely worth logging if it's "something else"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great suggestion, done

}
return &LazyEncodedImage{
imgBytes: imgBytes,
mimeType: mimeType,
Expand Down
33 changes: 30 additions & 3 deletions rimage/lazy_encoded_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rimage
import (
"bytes"
"image"
"image/jpeg"
"image/png"
"testing"

Expand All @@ -15,10 +16,14 @@ func TestLazyEncodedImage(t *testing.T) {
img := image.NewNRGBA(image.Rect(0, 0, 4, 8))
img.Set(3, 3, Red)

var buf bytes.Buffer
test.That(t, png.Encode(&buf, img), test.ShouldBeNil)
var pngBuf bytes.Buffer
test.That(t, png.Encode(&pngBuf, img), test.ShouldBeNil)
var jpegBuf bytes.Buffer
test.That(t, jpeg.Encode(&jpegBuf, img, &jpeg.Options{Quality: 100}), test.ShouldBeNil)
jpegImg, err := jpeg.Decode(bytes.NewBuffer(jpegBuf.Bytes()))
test.That(t, err, test.ShouldBeNil)

imgLazy := NewLazyEncodedImage(buf.Bytes(), utils.MimeTypePNG)
imgLazy := NewLazyEncodedImage(pngBuf.Bytes(), utils.MimeTypePNG)

test.That(t, imgLazy.(*LazyEncodedImage).MIMEType(), test.ShouldEqual, utils.MimeTypePNG)
test.That(t, NewColorFromColor(imgLazy.At(0, 0)), test.ShouldEqual, Black)
Expand Down Expand Up @@ -46,4 +51,26 @@ func TestLazyEncodedImage(t *testing.T) {
test.That(t, func() { imgLazy.ColorModel() }, test.ShouldPanic)
test.That(t, func() { NewColorFromColor(imgLazy.At(0, 0)) }, test.ShouldPanic)
test.That(t, func() { NewColorFromColor(imgLazy.At(4, 4)) }, test.ShouldPanic)

// png without a mime type
imgLazy = NewLazyEncodedImage(pngBuf.Bytes(), "")
test.That(t, imgLazy.(*LazyEncodedImage).MIMEType(), test.ShouldEqual, utils.MimeTypePNG)
test.That(t, NewColorFromColor(imgLazy.At(0, 0)), test.ShouldEqual, Black)
test.That(t, NewColorFromColor(imgLazy.At(3, 3)), test.ShouldEqual, Red)
test.That(t, imgLazy.Bounds(), test.ShouldResemble, img.Bounds())
test.That(t, imgLazy.ColorModel(), test.ShouldResemble, img.ColorModel())

img2, err = png.Decode(bytes.NewBuffer(imgLazy.(*LazyEncodedImage).RawData()))
test.That(t, err, test.ShouldBeNil)
test.That(t, img2, test.ShouldResemble, img)

// jpeg without a mime type
imgLazy = NewLazyEncodedImage(jpegBuf.Bytes(), "")
test.That(t, imgLazy.(*LazyEncodedImage).MIMEType(), test.ShouldEqual, utils.MimeTypeJPEG)
test.That(t, imgLazy.Bounds(), test.ShouldResemble, jpegImg.Bounds())
test.That(t, imgLazy.ColorModel(), test.ShouldResemble, jpegImg.ColorModel())

img2, err = jpeg.Decode(bytes.NewBuffer(imgLazy.(*LazyEncodedImage).RawData()))
test.That(t, err, test.ShouldBeNil)
test.That(t, img2, test.ShouldResemble, jpegImg)
}
Loading