From 7481d2f4284e794c2d9a26367eb88902df70a710 Mon Sep 17 00:00:00 2001 From: Tess Avitabile Date: Fri, 14 Oct 2022 21:10:26 +0100 Subject: [PATCH 1/2] [DATA-535] Create an orbslam integration test that runs mono YCbCr images --- .artifact/tree.json | 64 ++++++++++++ rimage/image_processing.go | 48 +++++++++ rimage/image_processing_test.go | 52 +--------- services/slam/builtin/builtin.go | 6 +- services/slam/builtin/builtin_test.go | 87 ++++++++++++++-- services/slam/builtin/orbslam_int_test.go | 120 +++++++++++++++------- 6 files changed, 275 insertions(+), 102 deletions(-) diff --git a/.artifact/tree.json b/.artifact/tree.json index 742793d3e47..b97405a7c70 100644 --- a/.artifact/tree.json +++ b/.artifact/tree.json @@ -50802,6 +50802,70 @@ } } }, + "mock_mono_camera": { + "rgb": { + "0.png": { + "hash": "c4dc7ae86a546684f3c9cbe0e47acf55", + "size": 834386 + }, + "1.png": { + "hash": "c4dc7ae86a546684f3c9cbe0e47acf55", + "size": 834386 + }, + "10.png": { + "hash": "97cb4afc22754e0433303ae8b44d380f", + "size": 813831 + }, + "11.png": { + "hash": "f87b5643a7b58bc4cdf231ab096038b7", + "size": 833993 + }, + "12.png": { + "hash": "f87b5643a7b58bc4cdf231ab096038b7", + "size": 833993 + }, + "13.png": { + "hash": "c40ec042eb926ed5e012c0a505b0453d", + "size": 787645 + }, + "14.png": { + "hash": "87f3db7908d0455ffe3b2a51093abe16", + "size": 824040 + }, + "2.png": { + "hash": "c4dc7ae86a546684f3c9cbe0e47acf55", + "size": 834386 + }, + "3.png": { + "hash": "c4dc7ae86a546684f3c9cbe0e47acf55", + "size": 834386 + }, + "4.png": { + "hash": "f87b5643a7b58bc4cdf231ab096038b7", + "size": 833993 + }, + "5.png": { + "hash": "f87b5643a7b58bc4cdf231ab096038b7", + "size": 833993 + }, + "6.png": { + "hash": "d17c07b754a6c274915a94a2dd978c02", + "size": 819850 + }, + "7.png": { + "hash": "87f3db7908d0455ffe3b2a51093abe16", + "size": 824040 + }, + "8.png": { + "hash": "c40ec042eb926ed5e012c0a505b0453d", + "size": 787645 + }, + "9.png": { + "hash": "97cb4afc22754e0433303ae8b44d380f", + "size": 813831 + } + } + }, "temp_mock_camera": { "color": { "0.png": { diff --git a/rimage/image_processing.go b/rimage/image_processing.go index f23c4ef618f..1c9344ffed3 100644 --- a/rimage/image_processing.go +++ b/rimage/image_processing.go @@ -508,3 +508,51 @@ func EdgeHysteresisFiltering(mag *mat.Dense, low, high float64) (*image.Gray, er } return edges, nil } + +// ImageToYCbCrForTesting converts an image to YCbCr. It is only to be used for testing. +func ImageToYCbCrForTesting(dst *image.YCbCr, src image.Image) { + if dst == nil { + panic("dst can't be nil") + } + + yuvImg, ok := src.(*image.YCbCr) + if ok { + *dst = *yuvImg + return + } + + bounds := src.Bounds() + dy := bounds.Dy() + dx := bounds.Dx() + flat := dy * dx + + if len(dst.Y)+len(dst.Cb)+len(dst.Cr) < 3*flat { + i0 := 1 * flat + i1 := 2 * flat + i2 := 3 * flat + if cap(dst.Y) < i2 { + dst.Y = make([]uint8, i2) + } + dst.Y = dst.Y[:i0] + dst.Cb = dst.Y[i0:i1] + dst.Cr = dst.Y[i1:i2] + } + dst.SubsampleRatio = image.YCbCrSubsampleRatio444 + dst.YStride = dx + dst.CStride = dx + dst.Rect = bounds + + i := 0 + for yi := 0; yi < dy; yi++ { + for xi := 0; xi < dx; xi++ { + // TODO(erh): probably try to get the alpha value with something like + // https://en.wikipedia.org/wiki/Alpha_compositing + r, g, b, _ := src.At(xi, yi).RGBA() + yy, cb, cr := color.RGBToYCbCr(uint8(r/256), uint8(g/256), uint8(b/256)) + dst.Y[i] = yy + dst.Cb[i] = cb + dst.Cr[i] = cr + i++ + } + } +} diff --git a/rimage/image_processing_test.go b/rimage/image_processing_test.go index 83a938eab31..8a1bf7319b1 100644 --- a/rimage/image_processing_test.go +++ b/rimage/image_processing_test.go @@ -3,7 +3,6 @@ package rimage import ( "fmt" "image" - "image/color" "math" "testing" @@ -116,59 +115,12 @@ func BenchmarkConvertImage(b *testing.B) { } } -func imageToYCbCr(dst *image.YCbCr, src image.Image) { - if dst == nil { - panic("dst can't be nil") - } - - yuvImg, ok := src.(*image.YCbCr) - if ok { - *dst = *yuvImg - return - } - - bounds := src.Bounds() - dy := bounds.Dy() - dx := bounds.Dx() - flat := dy * dx - - if len(dst.Y)+len(dst.Cb)+len(dst.Cr) < 3*flat { - i0 := 1 * flat - i1 := 2 * flat - i2 := 3 * flat - if cap(dst.Y) < i2 { - dst.Y = make([]uint8, i2) - } - dst.Y = dst.Y[:i0] - dst.Cb = dst.Y[i0:i1] - dst.Cr = dst.Y[i1:i2] - } - dst.SubsampleRatio = image.YCbCrSubsampleRatio444 - dst.YStride = dx - dst.CStride = dx - dst.Rect = bounds - - i := 0 - for yi := 0; yi < dy; yi++ { - for xi := 0; xi < dx; xi++ { - // TODO(erh): probably try to get the alpha value with something like - // https://en.wikipedia.org/wiki/Alpha_compositing - r, g, b, _ := src.At(xi, yi).RGBA() - yy, cb, cr := color.RGBToYCbCr(uint8(r/256), uint8(g/256), uint8(b/256)) - dst.Y[i] = yy - dst.Cb[i] = cb - dst.Cr[i] = cr - i++ - } - } -} - func TestConvertYCbCr(t *testing.T) { orig, err := readImageFromFile(artifact.MustPath("rimage/canny1.png")) test.That(t, err, test.ShouldBeNil) var yuvImg image.YCbCr - imageToYCbCr(&yuvImg, orig) + ImageToYCbCrForTesting(&yuvImg, orig) err = WriteImageToFile(outDir+"/canny1-ycbcr.png", &yuvImg) test.That(t, err, test.ShouldBeNil) @@ -185,7 +137,7 @@ func BenchmarkConvertImageYCbCr(b *testing.B) { test.That(b, err, test.ShouldBeNil) var yuvImg image.YCbCr - imageToYCbCr(&yuvImg, orig) + ImageToYCbCrForTesting(&yuvImg, orig) b.ResetTimer() diff --git a/services/slam/builtin/builtin.go b/services/slam/builtin/builtin.go index 13ed71c8f71..b76117b1853 100644 --- a/services/slam/builtin/builtin.go +++ b/services/slam/builtin/builtin.go @@ -755,7 +755,7 @@ func (slamSvc *builtIn) StopSLAMProcess() error { return nil } -func (slamSvc *builtIn) getLazyPNGImage(ctx context.Context, cam camera.Camera) ([]byte, func(), error) { +func (slamSvc *builtIn) getPNGImage(ctx context.Context, cam camera.Camera) ([]byte, func(), error) { // We will hint that we want a PNG. // The Camera service server implementation in RDK respects this; others may not. img, release, err := camera.ReadImage( @@ -798,7 +798,7 @@ func (slamSvc *builtIn) getAndSaveDataSparse( return nil, errors.Errorf("expected 1 camera for mono slam, found %v", len(camStreams)) } - image, release, err := slamSvc.getLazyPNGImage(ctx, cams[0]) + image, release, err := slamSvc.getPNGImage(ctx, cams[0]) if err != nil { if err.Error() == opTimeoutErrorMessage { slamSvc.logger.Warnw("Skipping this scan due to error", "error", err) @@ -893,7 +893,7 @@ func (slamSvc *builtIn) getSimultaneousColorAndDepth( goutils.PanicCapturingGo(func() { defer slamSvc.activeBackgroundWorkers.Done() defer wg.Done() - images[iLoop], releaseFuncs[iLoop], errs[iLoop] = slamSvc.getLazyPNGImage(ctx, cams[iLoop]) + images[iLoop], releaseFuncs[iLoop], errs[iLoop] = slamSvc.getPNGImage(ctx, cams[iLoop]) }) } wg.Wait() diff --git a/services/slam/builtin/builtin_test.go b/services/slam/builtin/builtin_test.go index 066ad37723a..cb49d6dd982 100644 --- a/services/slam/builtin/builtin_test.go +++ b/services/slam/builtin/builtin_test.go @@ -4,6 +4,7 @@ package builtin_test import ( + "bytes" "context" "fmt" "image" @@ -41,16 +42,27 @@ import ( ) const ( - validDataRateMS = 200 - numOrbslamImages = 29 + validDataRateMS = 200 ) var ( orbslamIntCameraMutex sync.Mutex orbslamIntCameraReleaseImagesChan chan int = make(chan int, 2) + orbslamIntWebcamReleaseImageChan chan int = make(chan int, 1) orbslamIntSynchronizeCamerasChan chan int = make(chan int) ) +func getNumOrbslamImages(mode slam.Mode) int { + switch mode { + case slam.Mono: + return 15 + case slam.Rgbd: + return 29 + default: + return 0 + } +} + func createFakeSLAMLibraries() { for _, s := range slam.SLAMLibraries { slam.SLAMLibraries["fake_"+s.AlgoName] = slam.LibraryMetadata{ @@ -103,8 +115,8 @@ func setupInjectRobot() *inject.Robot { distortionsA := &transform.BrownConrady{RadialK1: 0.001, RadialK2: 0.00004} projA = intrinsicsA - var projReal transform.Projector - intrinsicsReal := &transform.PinholeCameraIntrinsics{ + var projRealSense transform.Projector + intrinsicsRealSense := &transform.PinholeCameraIntrinsics{ Width: 1280, Height: 720, Fx: 900.538, @@ -112,13 +124,30 @@ func setupInjectRobot() *inject.Robot { Ppx: 648.934, Ppy: 367.736, } - distortionsReal := &transform.BrownConrady{ + distortionsRealSense := &transform.BrownConrady{ RadialK1: 0.158701, RadialK2: -0.485405, RadialK3: 0.435342, TangentialP1: -0.00143327, TangentialP2: -0.000705919} - projReal = intrinsicsReal + projRealSense = intrinsicsRealSense + + var projWebcam transform.Projector + intrinsicsWebcam := &transform.PinholeCameraIntrinsics{ + Width: 640, + Height: 480, + Fx: 939.2693584627577, + Fy: 940.2928257873841, + Ppx: 320.6075282958033, + Ppy: 239.14408757087756, + } + distortionsWebcam := &transform.BrownConrady{ + RadialK1: 0.046535971648456166, + RadialK2: 0.8002516496932317, + RadialK3: -5.408034254951954, + TangentialP1: -8.996658362365533e-06, + TangentialP2: -0.002828504714921335} + projWebcam = intrinsicsWebcam r.ResourceByNameFunc = func(name resource.Name) (interface{}, error) { cam := &inject.Camera{} @@ -249,10 +278,10 @@ func setupInjectRobot() *inject.Robot { return nil, errors.New("camera not lidar") } cam.ProjectorFunc = func(ctx context.Context) (transform.Projector, error) { - return projReal, nil + return projRealSense, nil } cam.PropertiesFunc = func(ctx context.Context) (camera.Properties, error) { - return camera.Properties{IntrinsicParams: intrinsicsReal, DistortionParams: distortionsReal}, nil + return camera.Properties{IntrinsicParams: intrinsicsRealSense, DistortionParams: distortionsRealSense}, nil } var index uint64 cam.StreamFunc = func(ctx context.Context, errHandlers ...gostream.ErrorHandler) (gostream.VideoStream, error) { @@ -265,7 +294,7 @@ func setupInjectRobot() *inject.Robot { select { case <-orbslamIntCameraReleaseImagesChan: i := atomic.AddUint64(&index, 1) - 1 - if i >= numOrbslamImages { + if i >= uint64(getNumOrbslamImages(slam.Rgbd)) { return nil, errors.New("No more orbslam color images") } imgBytes, err := os.ReadFile(artifact.MustPath("slam/mock_camera_short/rgb/" + strconv.FormatUint(i, 10) + ".png")) @@ -304,7 +333,7 @@ func setupInjectRobot() *inject.Robot { select { case <-orbslamIntCameraReleaseImagesChan: i := atomic.AddUint64(&index, 1) - 1 - if i >= numOrbslamImages { + if i >= uint64(getNumOrbslamImages(slam.Rgbd)) { return nil, errors.New("No more orbslam depth images") } imgBytes, err := os.ReadFile(artifact.MustPath("slam/mock_camera_short/depth/" + strconv.FormatUint(i, 10) + ".png")) @@ -322,6 +351,44 @@ func setupInjectRobot() *inject.Robot { } } return cam, nil + case camera.Named("orbslam_int_webcam"): + cam.NextPointCloudFunc = func(ctx context.Context) (pointcloud.PointCloud, error) { + return nil, errors.New("camera not lidar") + } + cam.ProjectorFunc = func(ctx context.Context) (transform.Projector, error) { + return projWebcam, nil + } + cam.PropertiesFunc = func(ctx context.Context) (camera.Properties, error) { + return camera.Properties{IntrinsicParams: intrinsicsWebcam, DistortionParams: distortionsWebcam}, nil + } + var index uint64 + cam.StreamFunc = func(ctx context.Context, errHandlers ...gostream.ErrorHandler) (gostream.VideoStream, error) { + select { + case <-orbslamIntWebcamReleaseImageChan: + i := atomic.AddUint64(&index, 1) - 1 + if i >= uint64(getNumOrbslamImages(slam.Mono)) { + return nil, errors.New("No more orbslam webcam images") + } + imgBytes, err := os.ReadFile(artifact.MustPath("slam/mock_mono_camera/rgb/" + strconv.FormatUint(i, 10) + ".png")) + if err != nil { + return nil, err + } + img, _, err := image.Decode(bytes.NewReader(imgBytes)) + if err != nil { + return nil, err + } + var ycbcrImg image.YCbCr + rimage.ImageToYCbCrForTesting(&ycbcrImg, img) + return gostream.NewEmbeddedVideoStreamFromReader( + gostream.VideoReaderFunc(func(ctx context.Context) (image.Image, func(), error) { + return &ycbcrImg, func() {}, nil + }), + ), nil + default: + return nil, errors.Errorf("Webcam not ready to return image %v", index) + } + } + return cam, nil default: return nil, rdkutils.NewResourceNotFoundError(name) } diff --git a/services/slam/builtin/orbslam_int_test.go b/services/slam/builtin/orbslam_int_test.go index 970fd02b7d2..469be172a50 100644 --- a/services/slam/builtin/orbslam_int_test.go +++ b/services/slam/builtin/orbslam_int_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "os/exec" + "reflect" "strings" "testing" "time" @@ -35,20 +36,28 @@ func createVocabularyFile(name string) error { return err } -// Releases an image pair to be served by the mock camera. The pair is released under a mutex, -// so that they will be consumed in the same call to getSimultaneousColorAndDepth(). -func releaseImages() { - for { - orbslamIntCameraMutex.Lock() - if len(orbslamIntCameraReleaseImagesChan) == cap(orbslamIntCameraReleaseImagesChan) { - orbslamIntCameraMutex.Unlock() - time.Sleep(10 * time.Millisecond) - } else { - orbslamIntCameraReleaseImagesChan <- 1 - orbslamIntCameraReleaseImagesChan <- 1 - orbslamIntCameraMutex.Unlock() - return +// Releases an image or image pair to be served by the mock camera(s). If a pair of images is +// released, it is released under a mutex, so that the images will be consumed in the same call +// to getSimultaneousColorAndDepth(). +func releaseImages(t *testing.T, mode slam.Mode) { + switch mode { + case slam.Mono: + orbslamIntWebcamReleaseImageChan <- 1 + case slam.Rgbd: + for { + orbslamIntCameraMutex.Lock() + if len(orbslamIntCameraReleaseImagesChan) == cap(orbslamIntCameraReleaseImagesChan) { + orbslamIntCameraMutex.Unlock() + time.Sleep(10 * time.Millisecond) + } else { + orbslamIntCameraReleaseImagesChan <- 1 + orbslamIntCameraReleaseImagesChan <- 1 + orbslamIntCameraMutex.Unlock() + return + } } + default: + t.FailNow() } } @@ -59,10 +68,14 @@ func testPositionAndMap(t *testing.T, svc slam.Service) { position, err := svc.Position(context.Background(), "test") test.That(t, err, test.ShouldBeNil) - // Typical values are around (-0.001, 0.001, -0.007) + // Typical values for RGBD are around (-0.001, -0.004, -0.008) + // Typical values for Mono without an existing map are around (0.020, -0.032, -0.053) + // Typical values for Mono with an existing map are around (0.023, -0.036, -0.040) t.Logf("Position point: (%v, %v, %v)", position.Pose().Point().X, position.Pose().Point().Y, position.Pose().Point().Z) - // Typical values are around (0.630, -0.746, 0.216), theta=0.002 + // Typical values for RGBD are around (0.602, -0.772, -0.202), theta=0.002 + // Typical values for Mono without an existing map are around (0.144, 0.980, -0.137), theta=0.104 + // Typical values for Mono with an existing map are around ( 0.092, 0.993, -0.068), theta=0.099 t.Logf("Position orientation: RX: %v, RY: %v, RZ: %v, Theta: %v", position.Pose().Orientation().AxisAngles().RX, position.Pose().Orientation().AxisAngles().RY, @@ -71,12 +84,13 @@ func testPositionAndMap(t *testing.T, svc slam.Service) { actualMIME, _, pointcloud, err := svc.GetMap(context.Background(), "test", "pointcloud/pcd", nil, false) test.That(t, err, test.ShouldBeNil) test.That(t, actualMIME, test.ShouldResemble, "pointcloud/pcd") - // Typical value is 329 + // Typical value for RGBD is 329 + // Values for Mono vary t.Logf("Pointcloud points: %v", pointcloud.Size()) test.That(t, pointcloud.Size(), test.ShouldBeGreaterThan, 0) } -func TestOrbslamIntegration(t *testing.T) { +func integrationTestHelper(t *testing.T, mode slam.Mode) { _, err := exec.LookPath("orb_grpc_server") if err != nil { t.Log("Skipping test because orb_grpc_server binary was not found") @@ -89,12 +103,22 @@ func TestOrbslamIntegration(t *testing.T) { t.Log("Testing online mode") + var sensors []string + switch mode { + case slam.Mono: + sensors = []string{"orbslam_int_webcam"} + case slam.Rgbd: + sensors = []string{"orbslam_int_color_camera", "orbslam_int_depth_camera"} + default: + t.FailNow() + } + attrCfg := &builtin.AttrConfig{ Algorithm: "orbslamv3", - Sensors: []string{"orbslam_int_color_camera", "orbslam_int_depth_camera"}, + Sensors: sensors, ConfigParams: map[string]string{ - "mode": "rgbd", - "orb_n_features": "1000", + "mode": reflect.ValueOf(mode).String(), + "orb_n_features": "1250", "orb_scale_factor": "1.2", "orb_n_levels": "8", "orb_n_ini_th_fast": "20", @@ -107,19 +131,19 @@ func TestOrbslamIntegration(t *testing.T) { MapRateSec: 1, } - // Release a pair of camera images for service validation - releaseImages() + // Release camera image(s) for service validation + releaseImages(t, mode) // Create slam service using a real orbslam binary svc, err := createSLAMService(t, attrCfg, golog.NewTestLogger(t), true, true) test.That(t, err, test.ShouldBeNil) - // Release a pair of camera images, since orbslam looks for the second most recent pair - releaseImages() + // Release camera image(s), since orbslam looks for the second most recent image(s) + releaseImages(t, mode) // Wait for orbslam to finish processing images logReader := svc.(internal.Service).GetSLAMProcessBufferedLogReader() - for i := 0; i < numOrbslamImages-2; i++ { + for i := 0; i < getNumOrbslamImages(mode)-2; i++ { t.Logf("Find log line for image %v", i) - releaseImages() + releaseImages(t, mode) for { line, err := logReader.ReadString('\n') test.That(t, err, test.ShouldBeNil) @@ -137,8 +161,17 @@ func TestOrbslamIntegration(t *testing.T) { // Don't clear out the directory, since we will re-use the config and data for the next run closeOutSLAMService(t, "") - // Delete the last image pair, so that offline mode runs on the same set of images - for _, directoryName := range [2]string{"rgb/", "depth/"} { + // Delete the last image or image pair, so that offline mode runs on the same set of images + var directories []string + switch mode { + case slam.Mono: + directories = []string{"rgb/"} + case slam.Rgbd: + directories = []string{"rgb/", "depth/"} + default: + t.FailNow() + } + for _, directoryName := range directories { files, err := ioutil.ReadDir(name + "/data/" + directoryName) test.That(t, err, test.ShouldBeNil) lastFileName := files[len(files)-1].Name() @@ -155,8 +188,8 @@ func TestOrbslamIntegration(t *testing.T) { Algorithm: "orbslamv3", Sensors: []string{}, ConfigParams: map[string]string{ - "mode": "rgbd", - "orb_n_features": "1000", + "mode": reflect.ValueOf(mode).String(), + "orb_n_features": "1250", "orb_scale_factor": "1.2", "orb_n_levels": "8", "orb_n_ini_th_fast": "20", @@ -207,10 +240,10 @@ func TestOrbslamIntegration(t *testing.T) { attrCfg = &builtin.AttrConfig{ Algorithm: "orbslamv3", - Sensors: []string{"orbslam_int_color_camera", "orbslam_int_depth_camera"}, + Sensors: sensors, ConfigParams: map[string]string{ - "mode": "rgbd", - "orb_n_features": "1000", + "mode": reflect.ValueOf(mode).String(), + "orb_n_features": "1250", "orb_scale_factor": "1.2", "orb_n_levels": "8", "orb_n_ini_th_fast": "20", @@ -221,8 +254,8 @@ func TestOrbslamIntegration(t *testing.T) { MapRateSec: -1, } - // Release a pair of camera images for service validation - releaseImages() + // Release camera image(s) for service validation + releaseImages(t, mode) // Create slam service using a real orbslam binary svc, err = createSLAMService(t, attrCfg, golog.NewTestLogger(t), true, true) test.That(t, err, test.ShouldBeNil) @@ -238,12 +271,12 @@ func TestOrbslamIntegration(t *testing.T) { test.That(t, strings.Contains(line, "Initialization of Atlas from scratch"), test.ShouldBeFalse) } - // Release a pair of camera images, since orbslam looks for the second most recent pair - releaseImages() + // Release camera image(s), since orbslam looks for the second most recent image(s) + releaseImages(t, mode) // Wait for orbslam to finish processing images - for i := 0; i < numOrbslamImages-2; i++ { + for i := 0; i < getNumOrbslamImages(mode)-2; i++ { t.Logf("Find log line for image %v", i) - releaseImages() + releaseImages(t, mode) for { line, err := logReader.ReadString('\n') test.That(t, err, test.ShouldBeNil) @@ -260,4 +293,13 @@ func TestOrbslamIntegration(t *testing.T) { test.That(t, utils.TryClose(context.Background(), svc), test.ShouldBeNil) // Clear out directory closeOutSLAMService(t, name) + +} + +func TestOrbslamIntegrationRGBD(t *testing.T) { + integrationTestHelper(t, slam.Rgbd) +} + +func TestOrbslamIntegrationMono(t *testing.T) { + integrationTestHelper(t, slam.Mono) } From a878c8c5e229670a87ed6d8babc17c406fb01469 Mon Sep 17 00:00:00 2001 From: Tess Avitabile Date: Fri, 14 Oct 2022 21:40:32 +0100 Subject: [PATCH 2/2] remove unused temp_mock_camera --- .artifact/tree.json | 126 -------------------------------------------- 1 file changed, 126 deletions(-) diff --git a/.artifact/tree.json b/.artifact/tree.json index b97405a7c70..ee5591f8cbc 100644 --- a/.artifact/tree.json +++ b/.artifact/tree.json @@ -50865,132 +50865,6 @@ "size": 813831 } } - }, - "temp_mock_camera": { - "color": { - "0.png": { - "hash": "d6a4ad2b5d0a07cb42ce0a968bdd2d1e", - "size": 1074038 - }, - "1.png": { - "hash": "bf162ae919b499fb2a31c1c0651bc7e7", - "size": 1110424 - }, - "10.png": { - "hash": "7ecd6687e8af2f741b74fd6d61e5b94f", - "size": 1262970 - }, - "11.png": { - "hash": "e49a10305cd6444d65a2e259e092dcf3", - "size": 1320711 - }, - "12.png": { - "hash": "ccc96509aecebfcad460f66a8980ea9b", - "size": 1383680 - }, - "13.png": { - "hash": "a54e3cad45855001accb0f3e90cc4901", - "size": 1232388 - }, - "14.png": { - "hash": "2d0fcc43e04cfa64533f066f08286c8c", - "size": 1319859 - }, - "2.png": { - "hash": "561f98f2fdb955118c396071cb768711", - "size": 1113264 - }, - "3.png": { - "hash": "0b1245610799dfc255a39c7b28592839", - "size": 1126246 - }, - "4.png": { - "hash": "f87169c5c85b32e255f59aa50856ff27", - "size": 1126679 - }, - "5.png": { - "hash": "769088a8ebdbb1ce2979b64808ac707d", - "size": 1108197 - }, - "6.png": { - "hash": "fcc66c01f0f1cb5c200d0b3a67d0991e", - "size": 1118615 - }, - "7.png": { - "hash": "f81538daa0836653b935b97c99a60142", - "size": 1094447 - }, - "8.png": { - "hash": "9668c21a11b5abedb1254770dafb959d", - "size": 1246588 - }, - "9.png": { - "hash": "5e6afad5bf9912b1d69ed06d357f0b36", - "size": 1269381 - } - }, - "depth": { - "0.png": { - "hash": "e49b03a3e9fe6a9e41d67e3308a70120", - "size": 245153 - }, - "1.png": { - "hash": "34e0a2ae549be6f507b8f4e27f31f2e2", - "size": 345685 - }, - "10.png": { - "hash": "65517158a4175157ab3636e1ef9ac90d", - "size": 500936 - }, - "11.png": { - "hash": "593e152cea83f14e1c4b4cec78e0b33b", - "size": 528680 - }, - "12.png": { - "hash": "0abc4c2361127eb343572390c8d9f35c", - "size": 528095 - }, - "13.png": { - "hash": "3f28b21a7206a6e6cc109fd2b03d432b", - "size": 526535 - }, - "14.png": { - "hash": "f5142f9dbe12b339f8b4a0b92abe22f9", - "size": 485705 - }, - "2.png": { - "hash": "34e0a2ae549be6f507b8f4e27f31f2e2", - "size": 345685 - }, - "3.png": { - "hash": "2a3b9d6fa3177270c8be3fc48f12b6e9", - "size": 480005 - }, - "4.png": { - "hash": "59765705cd3ff8331b908f5a2f2b7d20", - "size": 479413 - }, - "5.png": { - "hash": "bbb525470e857878207ce030a0d78b26", - "size": 479728 - }, - "6.png": { - "hash": "ed4df21dd6036b0cda823de71dd81c53", - "size": 513825 - }, - "7.png": { - "hash": "b37632c463e4a4a78b3bcd172a215ea5", - "size": 511135 - }, - "8.png": { - "hash": "95683c3b3c6a93282c3d06fabcd149ed", - "size": 503015 - }, - "9.png": { - "hash": "fcac230bb47d1617838e48fe3ca10f56", - "size": 505238 - } - } } }, "transform": {