-
Notifications
You must be signed in to change notification settings - Fork 113
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 2077 write gray16 images to viam depth format #1960
Changes from all commits
3c09325
86875b2
1c3a503
dcabb35
3e95637
21a43a7
1975f42
0426613
69a54c8
e617722
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import ( | |
"bufio" | ||
"compress/gzip" | ||
"encoding/binary" | ||
"image" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
|
@@ -18,7 +19,7 @@ import ( | |
const MagicNumIntVersionX = 6363110499870197078 | ||
|
||
// MagicNumIntViamType is the magic number (as an int) for the custom Viam depth type. | ||
// magic number for ViamCustomType is int64([]byte("DEPTHMAP")). | ||
// magic number for ViamCustomType is uint64([]byte("DEPTHMAP")), Little Endian. | ||
const MagicNumIntViamType = 5782988369567958340 | ||
|
||
func _readNext(r io.Reader) (int64, error) { | ||
|
@@ -61,15 +62,16 @@ func ReadDepthMap(r io.Reader) (*DepthMap, error) { | |
} | ||
switch firstBytes { | ||
case MagicNumIntVersionX: // magic number for VERSIONX | ||
return readDepthMapVersionX(r.(*bufio.Reader)) | ||
return readDepthMapVersionX(r) | ||
case MagicNumIntViamType: // magic number for ViamCustomType is int64([]byte("DEPTHMAP")) | ||
return readDepthMapViam(r.(*bufio.Reader)) | ||
return readDepthMapViam(r) | ||
default: | ||
return readDepthMapRaw(r.(*bufio.Reader), firstBytes) | ||
return readDepthMapRaw(r, firstBytes) | ||
} | ||
} | ||
|
||
func readDepthMapRaw(f *bufio.Reader, firstBytes int64) (*DepthMap, error) { | ||
func readDepthMapRaw(ff io.Reader, firstBytes int64) (*DepthMap, error) { | ||
f := bufio.NewReader(ff) | ||
dm := DepthMap{} | ||
|
||
dm.width = int(firstBytes) | ||
|
@@ -83,26 +85,34 @@ func readDepthMapRaw(f *bufio.Reader, firstBytes int64) (*DepthMap, error) { | |
return setRawDepthMapValues(f, &dm) | ||
} | ||
|
||
func readDepthMapViam(f *bufio.Reader) (*DepthMap, error) { | ||
dm := DepthMap{} | ||
func readDepthMapViam(ff io.Reader) (*DepthMap, error) { | ||
f := bufio.NewReader(ff) | ||
dm := &DepthMap{} | ||
|
||
rawWidth, err := _readNext(f) | ||
if err != nil { | ||
return nil, err | ||
return nil, errors.Wrapf(err, "could not read vnd.viam.dep width") | ||
} | ||
dm.width = int(rawWidth) | ||
rawHeight, err := _readNext(f) | ||
if err != nil { | ||
return nil, err | ||
return nil, errors.Wrapf(err, "could not read vnd.viam.dep height") | ||
} | ||
dm.height = int(rawHeight) | ||
|
||
return setRawDepthMapValues(f, &dm) | ||
// dump the rest of the bytes in a depth slice | ||
datSlice := make([]Depth, dm.height*dm.width) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like a smoother way to do setRawDepthMapValues() except now there's two places with basically the same code chunk. We only use this here and in readDepthMapRaw, so maybe just go ahead and replace the function with this logic? Might mean that the error printout gets slightly less specific though... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like we're just dumping it all out as opposed to reading 8 bytes at a time. Nice. This should also be valid for raw depth maps. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Raw Depth maps are actually treated differently: In the raw depth map function, it is writing the two bytes (uint16) of depth info from the This new Viam function, on the other hand, writes out 2 bytes, and reads 2 bytes. So the space the file takes up is smaller, because it doesn't have 6 extra bytes of 0s for every 2 bytes of depth info. This also allows you to just dump the data from the file into a depth slice, without having to parse 8 bytes at a time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll put a comment in the file pointing out the difference |
||
err = binary.Read(f, binary.LittleEndian, &datSlice) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "could not read vnd.viam.dep data slice") | ||
} | ||
dm.data = datSlice | ||
return dm, nil | ||
} | ||
|
||
func readDepthMapVersionX(r *bufio.Reader) (*DepthMap, error) { | ||
func readDepthMapVersionX(rr io.Reader) (*DepthMap, error) { | ||
dm := DepthMap{} | ||
|
||
r := bufio.NewReader(rr) | ||
// get past garbade | ||
_, err := r.ReadString('\n') | ||
if err != nil { | ||
|
@@ -183,6 +193,7 @@ func readDepthMapVersionX(r *bufio.Reader) (*DepthMap, error) { | |
return &dm, nil | ||
} | ||
|
||
// setRawDepthMapValues read out values 8 bytes at a time, converting the 8 bytes into a 2 byte depth value. | ||
func setRawDepthMapValues(f *bufio.Reader, dm *DepthMap) (*DepthMap, error) { | ||
if dm.width <= 0 || dm.width >= 100000 || dm.height <= 0 || dm.height >= 100000 { | ||
return nil, errors.Errorf("bad width or height for depth map %v %v", dm.width, dm.height) | ||
|
@@ -204,7 +215,7 @@ func setRawDepthMapValues(f *bufio.Reader, dm *DepthMap) (*DepthMap, error) { | |
} | ||
|
||
// WriteRawDepthMapToFile writes the raw depth map to the given file. | ||
func WriteRawDepthMapToFile(dm *DepthMap, fn string) (err error) { | ||
func WriteRawDepthMapToFile(dm image.Image, fn string) (err error) { | ||
//nolint:gosec | ||
f, err := os.Create(fn) | ||
if err != nil { | ||
|
@@ -226,15 +237,15 @@ func WriteRawDepthMapToFile(dm *DepthMap, fn string) (err error) { | |
} | ||
|
||
if strings.HasSuffix(fn, ".vnd.viam.dep") { | ||
_, err := out.Write(DepthMapMagicNumber) | ||
_, err = WriteViamDepthMapTo(dm, out) | ||
if err != nil { | ||
return err | ||
} | ||
} else { | ||
_, err = WriteRawDepthMapTo(dm, out) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
_, err = WriteRawDepthMapTo(dm, out) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if gout != nil { | ||
|
@@ -247,27 +258,37 @@ func WriteRawDepthMapToFile(dm *DepthMap, fn string) (err error) { | |
} | ||
|
||
// WriteRawDepthMapTo writes this depth map to the given writer. | ||
func WriteRawDepthMapTo(dm *DepthMap, out io.Writer) (int64, error) { | ||
// the raw depth map type writes 8 bytes of width, 8 bytes of height, and 8 bytes per pixel. | ||
func WriteRawDepthMapTo(img image.Image, out io.Writer) (int64, error) { | ||
buf := make([]byte, 8) | ||
var totalN int64 | ||
width := img.Bounds().Dx() | ||
height := img.Bounds().Dy() | ||
|
||
binary.LittleEndian.PutUint64(buf, uint64(dm.width)) | ||
binary.LittleEndian.PutUint64(buf, uint64(width)) | ||
n, err := out.Write(buf) | ||
totalN += int64(n) | ||
if err != nil { | ||
return totalN, err | ||
} | ||
|
||
binary.LittleEndian.PutUint64(buf, uint64(dm.height)) | ||
binary.LittleEndian.PutUint64(buf, uint64(height)) | ||
n, err = out.Write(buf) | ||
totalN += int64(n) | ||
if err != nil { | ||
return totalN, err | ||
} | ||
|
||
for x := 0; x < dm.width; x++ { | ||
for y := 0; y < dm.height; y++ { | ||
binary.LittleEndian.PutUint64(buf, uint64(dm.GetDepth(x, y))) | ||
for x := 0; x < width; x++ { | ||
for y := 0; y < height; y++ { | ||
switch dm := img.(type) { | ||
case *DepthMap: | ||
binary.LittleEndian.PutUint64(buf, uint64(dm.GetDepth(x, y))) | ||
case *image.Gray16: | ||
binary.LittleEndian.PutUint64(buf, uint64(dm.Gray16At(x, y).Y)) | ||
default: | ||
return totalN, errors.Errorf("cannot convert image type %T to a raw depth format", dm) | ||
} | ||
n, err = out.Write(buf) | ||
totalN += int64(n) | ||
if err != nil { | ||
|
@@ -278,3 +299,64 @@ func WriteRawDepthMapTo(dm *DepthMap, out io.Writer) (int64, error) { | |
|
||
return totalN, nil | ||
} | ||
|
||
// WriteViamDepthMapTo writes depth map or gray16 image to the given writer as vnd.viam.dep bytes. | ||
// the Viam custom depth type writes 8 bytes of "magic number", 8 bytes of width, 8 bytes of height, and 2 bytes per pixel. | ||
func WriteViamDepthMapTo(img image.Image, out io.Writer) (int64, error) { | ||
if lazy, ok := img.(*LazyEncodedImage); ok { | ||
lazy.decode() | ||
if lazy.decodeErr != nil { | ||
return 0, errors.Errorf("could not decode LazyEncodedImage to a depth image: %v", lazy.decodeErr) | ||
} | ||
img = lazy.decodedImage | ||
} | ||
buf := make([]byte, 8) | ||
var totalN int64 | ||
width := img.Bounds().Dx() | ||
height := img.Bounds().Dy() | ||
|
||
binary.LittleEndian.PutUint64(buf, uint64(MagicNumIntViamType)) | ||
n, err := out.Write(buf) | ||
totalN += int64(n) | ||
if err != nil { | ||
return totalN, err | ||
} | ||
binary.LittleEndian.PutUint64(buf, uint64(width)) | ||
n, err = out.Write(buf) | ||
totalN += int64(n) | ||
if err != nil { | ||
return totalN, err | ||
} | ||
|
||
binary.LittleEndian.PutUint64(buf, uint64(height)) | ||
n, err = out.Write(buf) | ||
totalN += int64(n) | ||
if err != nil { | ||
return totalN, err | ||
} | ||
switch dm := img.(type) { | ||
case *DepthMap: | ||
err = binary.Write(out, binary.LittleEndian, dm.data) // uint16 data | ||
if err != nil { | ||
return totalN, err | ||
} | ||
totalN += int64(len(dm.data) * 2) | ||
case *image.Gray16: | ||
grayBuf := make([]byte, 2) | ||
for y := 0; y < height; y++ { | ||
for x := 0; x < width; x++ { | ||
i := dm.PixOffset(x, y) | ||
z := uint16(dm.Pix[i+0])<<8 | uint16(dm.Pix[i+1]) | ||
binary.LittleEndian.PutUint16(grayBuf, z) | ||
n, err = out.Write(grayBuf) | ||
totalN += int64(n) | ||
if err != nil { | ||
return totalN, err | ||
} | ||
} | ||
} | ||
default: | ||
return totalN, errors.Errorf("cannot convert image type %T to image/vnd.viam.dep depth format", dm) | ||
} | ||
return totalN, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -333,6 +333,44 @@ func TestDepthColorModel(t *testing.T) { | |
test.That(t, convGray.(color.Gray16).Y, test.ShouldEqual, 6168) | ||
} | ||
|
||
func TestViamDepthMap(t *testing.T) { | ||
// create various types of depth representations | ||
width := 10 | ||
height := 20 | ||
dm := NewEmptyDepthMap(width, height) | ||
g16 := image.NewGray16(image.Rect(0, 0, width, height)) | ||
g8 := image.NewGray(image.Rect(0, 0, width, height)) | ||
for x := 0; x < width; x++ { | ||
for y := 0; y < height; y++ { | ||
dm.Set(x, y, Depth(x*y)) | ||
g16.SetGray16(x, y, color.Gray16{uint16(x * y)}) | ||
g8.SetGray(x, y, color.Gray{uint8(x * y)}) | ||
} | ||
} | ||
// write dm to a viam type buffer | ||
buf := &bytes.Buffer{} | ||
byt, err := WriteViamDepthMapTo(dm, buf) | ||
test.That(t, err, test.ShouldBeNil) | ||
test.That(t, byt, test.ShouldEqual, (3*8 + 2*width*height)) // 3 bytes for header, 2 bytes per pixel | ||
// write gray16 to a viam type buffer | ||
buf16 := &bytes.Buffer{} | ||
byt16, err := WriteViamDepthMapTo(g16, buf16) | ||
test.That(t, err, test.ShouldBeNil) | ||
test.That(t, byt16, test.ShouldEqual, (3*8 + 2*width*height)) // 3 bytes for header, 2 bytes per pixel | ||
// gray should fail | ||
buf8 := &bytes.Buffer{} | ||
_, err = WriteViamDepthMapTo(g8, buf8) | ||
test.That(t, err, test.ShouldNotBeNil) | ||
test.That(t, err.Error(), test.ShouldContainSubstring, "cannot convert image type") | ||
// read from a viam type buffers and compare to original | ||
dm2, err := ReadDepthMap(buf) | ||
test.That(t, err, test.ShouldBeNil) | ||
test.That(t, dm2, test.ShouldResemble, dm) | ||
dm3, err := ReadDepthMap(buf16) | ||
test.That(t, err, test.ShouldBeNil) | ||
test.That(t, dm3, test.ShouldResemble, dm) | ||
} | ||
|
||
func TestDepthMapEncoding(t *testing.T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we lost the "make sure you can read this from a file with the right extension" test and we might wanna keep it. The depth map in artifact/rimage/fakeDM.vnd.viam.dep is just like the one created below from scratch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This PR changes the way But yes, I will create a new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Like, here is the byte writeout for artifact/rimage/fakeDM.vnd.viam.dep, where each line is 8 bytes, and the first 3 lines are the DEPTHMAP header, width and height respectively. And then after that, each 8 bytes is one pixel There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And here is the new vnd.viam.dep format, which is more compact, which essentially holds 4 pixels worth of data on each line
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replaced the fakeDM file in artifact to new more compressed format. |
||
m, err := NewDepthMapFromFile(context.Background(), artifact.MustPath("rimage/fakeDM.vnd.viam.dep")) | ||
test.That(t, err, test.ShouldBeNil) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is already a great change just from synchronizing the function signatures to all input a io.Reader. Love this.