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-9132] Add (Get)Image to the camera interface #4487

Merged
merged 63 commits into from
Dec 3, 2024

Conversation

hexbabe
Copy link
Member

@hexbabe hexbabe commented Oct 24, 2024

Overview

https://viam.atlassian.net/browse/RSDK-9132

This PR does not remove Stream from the Camera interface or any gostream logic. It just wraps it all in better English that corresponds with the gRPC method GetImage. Mostly just adding syntactic sugar over stream.Next/ReadImage using a new Image method (for existing builtins that use gostream), and modifying the camera server and client appropriately to utilize Image.

My hopes are that this encourages people to never be tempted to use Stream in modules or even VideoReaderFunc. We will eventually removeStream from the base Camera interface

It will also provide a significant performance boost to the ReadImage data collector since we no longer have to transcode the old image.Image output into bytes anymore in collector.go.

Summary of changes

  • Add Image to camera interface
  • Use Image in camera/client.go, camera/server.go and data collector
  • Use Image in builtins (webcam, replaypcd, ffmpeg, fake etc.) instead of Stream wrappers in preparation for removing Stream entirely from the camera interface

Note

Will this break cli module generator? No

Manual Tests

Did this list of manual tests already twice at different points in development, but should re-test after these code reviews.

  • viamrtsp h264 with and without passthrough
  • Webcam
  • Webcam hot swap (reconfigure/rebuild due to switching sources)
  • Transform pipeline on webcam (rotate and crop including new crop features)
  • ffmpeg behavior with video0 is on par with the main branch (broken?? yes made ticket to track)
  • ffmpeg works for a fake mediamtx+ffmpeg rtsp stream though! No hang and stream looks great
  • Fake camera looks good with and without rtp passthrough toggled
  • OAK-FFC-3P

Also built the viamrtsp module with local replacement off this branch and the stream is working for both rtp passthrough true and false, indicating that videosourcewrapped modules shouldn't be affected by this change.

Profile for performance improvement in data collection

If the PDFs are insanely blurry, try downloading and opening with system viewer

Link

The big thing that Nick noted was alarming was the staggering memory used to alloc image NewYCbCr. This is no longer the case on this feature branch.

Config used for pprof

{
  "components": [
    {
      "name": "nickcam-big-green-1",
      "namespace": "rdk",
      "type": "camera",
      "model": "ncs:camera:nickcam",
      "attributes": {
        "big": true,
        "color": "green",
        "image_type": "jpeg"
      },
      "service_configs": [
        {
          "type": "data_manager",
          "attributes": {
            "capture_methods": [
              {
                "method": "ReadImage",
                "capture_frequency_hz": 60,
                "additional_params": {},
                "capture_queue_size": 0
              }
            ]
          }
        }
      ]
    },
    {
      "name": "nickcam-big-green-2",
      "namespace": "rdk",
      "type": "camera",
      "model": "ncs:camera:nickcam",
      "attributes": {
        "color": "green",
        "image_type": "jpeg",
        "big": true
      },
      "service_configs": [
        {
          "type": "data_manager",
          "attributes": {
            "capture_methods": [
              {
                "method": "ReadImage",
                "capture_frequency_hz": 60,
                "additional_params": {},
                "capture_queue_size": 0
              }
            ]
          }
        }
      ]
    },
    {
      "name": "nickcam-big-green-3",
      "namespace": "rdk",
      "type": "camera",
      "model": "ncs:camera:nickcam",
      "attributes": {
        "image_type": "jpeg",
        "big": true,
        "color": "green"
      },
      "service_configs": [
        {
          "type": "data_manager",
          "attributes": {
            "capture_methods": [
              {
                "method": "ReadImage",
                "capture_frequency_hz": 60,
                "additional_params": {},
                "capture_queue_size": 0
              }
            ]
          }
        }
      ]
    }
  ],
  "services": [
    {
      "name": "data_manager-1",
      "namespace": "rdk",
      "type": "data_manager",
      "attributes": {
        "sync_disabled": true,
        "capture_disabled": false
      }
    }
  ],
  "modules": [
    {
      "type": "registry",
      "name": "ncs_nickcam",
      "module_id": "ncs:nickcam",
      "version": "latest"
    }
  ]
}

Comparative performance from an operator's perspective

Config used

{
  "components": [
    {
      "name": "rtsp-1",
      "namespace": "rdk",
      "type": "camera",
      "model": "viam:viamrtsp:rtsp",
      "attributes": {
        "rtsp_address": "rtsp://10.1.12.67:554"
      },
      "service_configs": [
        {
          "type": "data_manager",
          "attributes": {
            "capture_methods": [
              {
                "method": "ReadImage",
                "capture_frequency_hz": 60,
                "additional_params": {},
                "capture_queue_size": 0
              }
            ]
          }
        }
      ]
    },
    {
      "name": "rtsp-2",
      "namespace": "rdk",
      "type": "camera",
      "model": "viam:viamrtsp:rtsp",
      "attributes": {
        "rtsp_address": "rtsp://10.1.12.67:554"
      },
      "service_configs": [
        {
          "type": "data_manager",
          "attributes": {
            "capture_methods": [
              {
                "method": "ReadImage",
                "capture_frequency_hz": 60,
                "additional_params": {},
                "capture_queue_size": 0
              }
            ]
          }
        }
      ]
    },
    {
      "name": "rtsp-3",
      "namespace": "rdk",
      "type": "camera",
      "model": "viam:viamrtsp:rtsp",
      "attributes": {
        "rtsp_address": "rtsp://10.1.12.67:554"
      },
      "service_configs": [
        {
          "type": "data_manager",
          "attributes": {
            "capture_methods": [
              {
                "method": "ReadImage",
                "capture_frequency_hz": 60,
                "additional_params": {},
                "capture_queue_size": 0
              }
            ]
          }
        }
      ]
    },
    {
      "name": "rtsp-4",
      "namespace": "rdk",
      "type": "camera",
      "model": "viam:viamrtsp:rtsp",
      "attributes": {
        "rtsp_address": "rtsp://10.1.12.67:554"
      },
      "service_configs": [
        {
          "type": "data_manager",
          "attributes": {
            "capture_methods": [
              {
                "method": "ReadImage",
                "capture_frequency_hz": 60,
                "additional_params": {},
                "capture_queue_size": 0
              }
            ]
          }
        }
      ]
    },
    {
      "name": "rtsp-5",
      "namespace": "rdk",
      "type": "camera",
      "model": "viam:viamrtsp:rtsp",
      "attributes": {
        "rtsp_address": "rtsp://10.1.12.67:554"
      },
      "service_configs": [
        {
          "type": "data_manager",
          "attributes": {
            "capture_methods": [
              {
                "method": "ReadImage",
                "capture_frequency_hz": 60,
                "additional_params": {},
                "capture_queue_size": 0
              }
            ]
          }
        }
      ]
    }
  ],
  "services": [
    {
      "name": "data_manager-1",
      "namespace": "rdk",
      "type": "data_manager",
      "attributes": {
        "sync_disabled": true,
        "capture_disabled": false
      }
    }
  ],
  "modules": [
    {
      "type": "registry",
      "name": "viam_viamrtsp",
      "module_id": "viam:viamrtsp",
      "version": "0.2.0-rc4",
      "disabled": false
    }
  ]
}

v0.50.0 branch (RPI4):

Screenshot 2024-11-15 at 3 29 37 PM

Feature branch (RPI4):

Screenshot 2024-11-15 at 3 30 21 PM

v0.50.0 (Orin Nano):

Screenshot 2024-11-15 at 3 17 36 PM

Feature branch (Orin Nano):

Screenshot 2024-11-15 at 3 16 10 PM

RPI4 memory improvement: I noticed that off the feature branch, memory stabilized around 1.9gb, while on the main branch, memory stabilized around 2.5gb

RPI4 CPU improvement: I learned how to filter for processes in this comparison. It's quite clear that the CPU usage is lower as all figures are lower across the board for the feature branch.

Orin Nano CPU improvement: Though the core usage was about the same between main and feature, I noticed that on the latest release, the viam-server processes were always taking the most CPU. Off the feature branch, viamrtsp were always on top.

I did not notice memory improvements on the Orin Nano. May be missing something. Operator skill issue perhaps

Copy link
Contributor

Warning your change may break code samples. If your change modifies any of the following functions please contact @viamrobotics/fleet-management. Thanks!

component function
base IsMoving
board GPIOPinByName
camera Properties
encoder Properties
motor IsMoving
sensor Readings
servo Position
arm EndPosition
audio MediaProperties
gantry Lengths
gripper IsMoving
input_controller Controls
movement_sensor LinearAcceleration
power_sensor Power
pose_tracker Poses
motion GetPose
vision GetProperties

@viambot viambot added the safe to test This pull request is marked safe to test from a trusted zone label Oct 24, 2024
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Oct 25, 2024
@hexbabe hexbabe marked this pull request as ready for review October 25, 2024 18:56
@hexbabe hexbabe requested review from randhid and seanavery October 25, 2024 18:58
@hexbabe
Copy link
Member Author

hexbabe commented Oct 25, 2024

I'm gonna be out next week but this is ready for a first pass review. Also lmk if we should add Nick or/(and?) Dan to this review. Perhaps for future reviews that are a bit more invasive?

Copy link
Member

@randhid randhid left a comment

Choose a reason for hiding this comment

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

Since we're breaking go modules by adding another method to the interface, we should make that method follow 1:1 the inputs and returns of the proto api.

This will mean bookkeeping the release of each image stream,should you choose to use ReadImage. This will definitely go away once we get the image from the underlying comms connection like we do in the intel realsense and oak-d camera, I don't know how intense that is.

If changing the returns from GetImage to just be 1:1 the API type cause more problems in changing code outside of camera, we can focus on replacements in the camera packages, and let datamanger and vision packages use Stream and Next for this pr, but I think you're nearly there.

@@ -228,6 +228,10 @@ func (c *client) Stream(
return stream, nil
}

func (c *client) GetImage(ctx context.Context) (image.Image, func(), error) {
return c.Read(ctx)
Copy link
Member

Choose a reason for hiding this comment

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

Could we Read and handle release so we do not need to include release func in returns? (perhaps that would make sense in the videosourcewrapper layer).
As mentioned, this would be a larger departure from Stream.Next which assuming will cause major problems.

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 gonna try to get rid of release in camera components and see what happens

Copy link
Member Author

@hexbabe hexbabe Nov 4, 2024

Choose a reason for hiding this comment

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

Looks like we just need to move the rimage.EncodeImage step one level in i.e. outBytes should come out of the Image call instead of being handled in the data collector or camera server/client. Currently release is called in the collector/server/client, so we should just move it into the Image implementation. Same logic, just handled more nested in our abstraction.

I guess future module writers should get used to using rimage to encode their output i.e. read from the source bytes and output a newly constructed []byte result? Does that sound okay to everyone?

Copy link
Member

Choose a reason for hiding this comment

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

Looks like we just need to move the rimage.EncodeImage step one level in i.e. outBytes should come out of the Image call instead of being handled in the data collector or camera server/client.

In the client, I think that makes sense to return rimage to match the python sdk functionality -- avoid unnecessary decodes if you just want bytes.

I guess future module writers should get used to using rimage to encode their output i.e. read from the source bytes and output a newly constructed []byte result? Does that sound okay to everyone?

Would this be similar to viamimage in the python sdk, i think that works pretty well.

Looks like the server is already handling release and encoding for the caller.
Are you also suggesting removing encoding step in server GetImage?

Copy link
Member Author

@hexbabe hexbabe Nov 5, 2024

Choose a reason for hiding this comment

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

This is my WIP in server.go

	switch castedCam := cam.(type) {
	case ReadImager:
		// RSDK-8663: If available, call a method that reads exactly one image. The default
		// `ReadImage` implementation will otherwise create a gostream `Stream`, call `Next` and
		// `Close` the stream. However, between `Next` and `Close`, the stream may have pulled a
		// second image from the underlying camera. This is particularly noticeable on camera
		// clients. Where a second `GetImage` request can be processed/returned over the
		// network. Just to be discarded.
		// RSDK-9132(sean yu): In addition to what Dan said above, ReadImager is important
		// for camera components that rely on the `release` functionality provided by gostream's `Read`
		// such as viamrtsp.
		// (check that this comment is 100% true before code review then delete this paranthetical statement)
		img, release, err := castedCam.Read(ctx)
		if err != nil {
			return nil, err
		}
		defer func() {
			if release != nil {
				release()
			}
		}()
	
		actualMIME, _ := utils.CheckLazyMIMEType(req.MimeType)
		resp.MimeType = actualMIME
		outBytes, err := rimage.EncodeImage(ctx, img, req.MimeType)
		if err != nil {
			return nil, err
		}
		resp.Image = outBytes
	default:
		imgBytes, mimeType, err := cam.Image(ctx, req.MimeType, ext)
		if err != nil {
			return nil, err
		}
		actualMIME, _ := utils.CheckLazyMIMEType(mimeType)
		resp.MimeType = actualMIME
		resp.Image = imgBytes
	}

So I think yes, in the default case we don't encode anymore since the return type is now bytes

Copy link
Member

Choose a reason for hiding this comment

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

Ok cool I like that.

I am little confused on why we still need the ReadImager path here. Shouldnt the camera interface now always have Image defined so we can just use that?

Copy link
Member Author

@hexbabe hexbabe Nov 5, 2024

Choose a reason for hiding this comment

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

My thinking here is since viamrtsp uses the VideoReaderFunc and Read's release functionality to keep track of pool frames in the frame allocation optimization flow, for the server.go that serves viamrtsp, we need to be able to call release()

I think we could refactor viamrtsp though to just copy out the bytes on return early and use the new .Image pathway... I'm down to remove ReadImager entirely as long as we make it a high priority to refactor viamrtsp to use .Image and []byte

Copy link
Member

@seanavery seanavery Nov 5, 2024

Choose a reason for hiding this comment

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

I see, interesting. I think it makes sense to leave in for now for viamrtsp compatibility.

I think we could refactor viamrtsp though to just copy out the bytes on return early and use the new .Image pathway... I'm down to remove ReadImager entirely as long as we make it a high priority to refactor viamrtsp to use .Image and []byte

Yep the whole point to having a memory manager was the issue with passing in a pointer to the avframe in VideoReaderFunc return causing segfaults with the encoding in the server.

Since we are removing encoding from the server and relying on the user to handle this, we should eventually be able to use Image pathway and simplify things quite a bit : )

Copy link
Member

Choose a reason for hiding this comment

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

All our go cameras will need a refactor for the full fix of this interface, I do not anticipate that we will have the best version of this code and go modules after one pr. Eventually we will get rid of Stream and Next completely. The problem is those videosourcewrapper helpers are all over the place in go modules since they're exported convenience functions.

Always export code conscientiously. But we're breaking this interface anyway, so we'll deal with the following breaks.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay 👍 will remove ReadImager in this PR

Copy link
Member

@seanavery seanavery left a comment

Choose a reason for hiding this comment

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

I am wondering if it makes sense to do a full removal of Read from client & server, ReadImage, and ReadImager here instead of just aliasing in those cases.

@randhid
Copy link
Member

randhid commented Nov 1, 2024

I am wondering if it makes sense to do a full removal of Read from client & server, ReadImage, and ReadImager here instead of just aliasing in those cases.

Yes.

@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Nov 4, 2024
Copy link
Member

@seanavery seanavery left a comment

Choose a reason for hiding this comment

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

Should we replace the cemeraservice GetImage call in RenderFrame with Image?

func (s *serviceServer) RenderFrame(
	ctx context.Context,
	req *pb.RenderFrameRequest,
) (*httpbody.HttpBody, error) {
	ctx, span := trace.StartSpan(ctx, "camera::server::RenderFrame")
	defer span.End()
	if req.MimeType == "" {
		req.MimeType = utils.MimeTypeJPEG // default rendering
	}
	resp, err := s.GetImage(ctx, (*pb.GetImageRequest)(req))

@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Nov 6, 2024
@hexbabe
Copy link
Member Author

hexbabe commented Nov 6, 2024

Should we replace the cemeraservice GetImage call in RenderFrame with Image?

func (s *serviceServer) RenderFrame(
	ctx context.Context,
	req *pb.RenderFrameRequest,
) (*httpbody.HttpBody, error) {
	ctx, span := trace.StartSpan(ctx, "camera::server::RenderFrame")
	defer span.End()
	if req.MimeType == "" {
		req.MimeType = utils.MimeTypeJPEG // default rendering
	}
	resp, err := s.GetImage(ctx, (*pb.GetImageRequest)(req))

serviceServers use the gRPC named version, so GetImage is right

@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Nov 6, 2024
@viambot viambot removed the safe to test This pull request is marked safe to test from a trusted zone label Nov 6, 2024
@viambot viambot added the safe to test This pull request is marked safe to test from a trusted zone label Dec 2, 2024
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Dec 2, 2024
Copy link
Member

@nicksanford nicksanford left a comment

Choose a reason for hiding this comment

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

LGTM Great work Sean!

I also confirmed that filtered camera still works as expected.

@nicksanford
Copy link
Member

LGTM Great work Sean!

I also confirmed that filtered camera still works as expected.

Also, the end to end of syncing camera.ReadImage (for modules).

I did not test the transform camera.

@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Dec 3, 2024
@hexbabe
Copy link
Member Author

hexbabe commented Dec 3, 2024

Cool. I'm gonna run it through my own manual test gauntlet and then merge before EOD

@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Dec 3, 2024
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Dec 3, 2024
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Dec 3, 2024
@viambot viambot added safe to test This pull request is marked safe to test from a trusted zone and removed safe to test This pull request is marked safe to test from a trusted zone labels Dec 3, 2024
@hexbabe hexbabe merged commit 3d04939 into viamrobotics:main Dec 3, 2024
16 checks passed
@hexbabe hexbabe deleted the RSDK-9132 branch December 3, 2024 17:40
@hexbabe hexbabe restored the RSDK-9132 branch December 3, 2024 17:40
@hexbabe hexbabe deleted the RSDK-9132 branch December 3, 2024 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
safe to test This pull request is marked safe to test from a trusted zone
Projects
None yet
Development

Successfully merging this pull request may close these issues.