From c700e677355bf64f225f94da481e7088f3efa962 Mon Sep 17 00:00:00 2001 From: Yucheng Zhu Date: Fri, 20 Sep 2019 13:58:14 -0400 Subject: [PATCH 1/2] Add support for read and create hdf5 true color image dataset --- h5g_group.go | 18 +++++++++++ h5g_group_test.go | 51 ++++++++++++++++++++++++++++++ h5i_image.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 h5i_image.go diff --git a/h5g_group.go b/h5g_group.go index d3503ea..3682c2d 100644 --- a/h5g_group.go +++ b/h5g_group.go @@ -11,7 +11,9 @@ package hdf5 import "C" import ( + "errors" "fmt" + "image" "unsafe" ) @@ -169,3 +171,19 @@ func (g *CommonFG) LinkExists(name string) bool { defer C.free(unsafe.Pointer(c_name)) return C.H5Lexists(g.id, c_name, 0) > 0 } + +// CreateTureImage create a image set with given name under a CommonFG +func (g *CommonFG) CreateTrueImage(name string, img image.Image) error { + if g.LinkExists(name) { + return errors.New("name already exist") + } + return newImage(g.id, name, img) +} + +// ReadTrueImage read a image dataset into a go Image +func (g *CommonFG) ReadTrueImage(name string) (image.Image, error) { + if !g.LinkExists(name) { + return nil, errors.New("name doesn't exist") + } + return getImage(g.id, name) +} diff --git a/h5g_group_test.go b/h5g_group_test.go index 3fd5197..16f317c 100644 --- a/h5g_group_test.go +++ b/h5g_group_test.go @@ -5,6 +5,9 @@ package hdf5 import ( + "image" + "image/color" + "image/jpeg" "os" "testing" ) @@ -128,3 +131,51 @@ func TestGroup(t *testing.T) { } } + +func TestImage(t *testing.T) { + f, err := CreateFile(fname, F_ACC_TRUNC) + if err != nil { + t.Fatalf("CreateFile failed: %s", err) + } + defer os.Remove(fname) + defer f.Close() + img := image.NewRGBA(image.Rect(0, 0, 1000, 500)) + for y := 200; y < 300; y++ { + for x := 400; x < 600; x++ { + img.Set(x, y, color.RGBA{255, 0, 255, 255}) + } + } + if err != nil { + t.Fatalf("image decoding failed: %s", err) + } + g1, err := f.CreateGroup("foo") + if err != nil { + t.Fatalf("couldn't create group: %s", err) + } + defer g1.Close() + err = g1.CreateTrueImage("image", img) + if err != nil { + t.Fatalf("image saving failed: %s", err) + } + imgRead, err := g1.ReadTrueImage("image") + if err != nil { + t.Fatalf("image reading failed: %s", err) + } + widthGot := imgRead.Bounds().Max.X + heightGot := imgRead.Bounds().Max.Y + if widthGot != 1000 || heightGot != 500 { + t.Fatalf("image dimension miss match: Got %d * %d, suppose to be 1000x500", widthGot, heightGot) + } + + imgfile, err := os.Create("img.jpg") + if err != nil { + t.Fatalf("image file creation failed: %s", err) + } + defer os.Remove("img.jpg") + defer imgfile.Close() + + err = jpeg.Encode(imgfile, imgRead, nil) + if err != nil { + t.Fatalf(" jpeg image saving err: %s", err) + } +} diff --git a/h5i_image.go b/h5i_image.go new file mode 100644 index 0000000..777fc91 --- /dev/null +++ b/h5i_image.go @@ -0,0 +1,79 @@ +// license that can be found in the LICENSE file. + +package hdf5 + +// #include "hdf5.h" +// #include "hdf5_hl.h" +// #include +// #include +import "C" + +import ( + "errors" + "image" + "image/color" + "unsafe" +) + +// newImage takes a image object and convert it to a hdf5 format and write to the id node +func newImage(id C.hid_t, name string, img image.Image) error { + if img == nil { + return errors.New("nil image!") + } + width := img.Bounds().Max.X + height := img.Bounds().Max.Y + var c_width, c_height C.hsize_t + c_name := C.CString(name) + defer C.free(unsafe.Pointer(c_name)) + var gbuf []uint8 + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + r, g, b, _ := img.At(x, y).RGBA() + gbuf = append(gbuf, uint8(r>>8)) + gbuf = append(gbuf, uint8(g>>8)) + gbuf = append(gbuf, uint8(b>>8)) + } + } + c_width = C.hsize_t(width) + c_height = C.hsize_t(height) + c_image := (*C.uchar)(unsafe.Pointer(&gbuf[0])) + + status := C.H5IMmake_image_24bit(id, c_name, c_width, c_height, C.CString("INTERLACE_PIXEL"), c_image) + if status < 0 { + return errors.New("Failed to create HDF5 true color image") + } + return nil +} + +// +func getImage(id C.hid_t, name string) (image.Image, error) { + //TODO Should handle interlace and npal better, yet these two are not needed for simple image read and write + var width, height, planes C.hsize_t + var npals C.hssize_t + var interlace C.char + c_name := C.CString(name) + defer C.free(unsafe.Pointer(c_name)) + rc := C.H5IMget_image_info(id, c_name, &width, &height, &planes, &interlace, &npals) + err := h5err(rc) + if err != nil { + return nil, err + } + gbuf := make([]uint8, width*height*planes) + c_image := (*C.uchar)(unsafe.Pointer(&gbuf[0])) + rc = C.H5IMread_image(id, c_name, c_image) + + err = h5err(rc) + if err != nil { + return nil, err + } + + g_width := int(width) + g_height := int(height) + img := image.NewRGBA(image.Rect(0, 0, g_width, g_height)) + for y := 0; y < g_height; y++ { + for x := 0; x < g_width; x++ { + img.Set(x, y, color.RGBA{gbuf[y*g_width*3+x*3], gbuf[y*g_width*3+x*3+1], gbuf[y*g_width*3+x*3+2], 255}) + } + } + return img, err +} From 2aa53543eaf260f29c5ff8f48d72f21ce6ebe00e Mon Sep 17 00:00:00 2001 From: Yucheng Zhu Date: Sat, 21 Sep 2019 10:26:26 -0400 Subject: [PATCH 2/2] change the image buffer in h5i_image.go from gbuf []uint8 to buf []byte, some comments and error message update --- h5g_group.go | 12 ++++++------ h5g_group_test.go | 26 +++++++++++++------------- h5i_image.go | 15 +++++---------- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/h5g_group.go b/h5g_group.go index 3682c2d..10ea541 100644 --- a/h5g_group.go +++ b/h5g_group.go @@ -172,18 +172,18 @@ func (g *CommonFG) LinkExists(name string) bool { return C.H5Lexists(g.id, c_name, 0) > 0 } -// CreateTureImage create a image set with given name under a CommonFG -func (g *CommonFG) CreateTrueImage(name string, img image.Image) error { +// CreateImage create a image(RGB only) set with given name in the receiver. +func (g *CommonFG) CreateImage(name string, img image.Image) error { if g.LinkExists(name) { - return errors.New("name already exist") + return errors.New("hdf5: name already exist") } return newImage(g.id, name, img) } -// ReadTrueImage read a image dataset into a go Image -func (g *CommonFG) ReadTrueImage(name string) (image.Image, error) { +// ReadImage read a image dataset, returning it as an image.Image. +func (g *CommonFG) ReadImage(name string) (image.Image, error) { if !g.LinkExists(name) { - return nil, errors.New("name doesn't exist") + return nil, errors.New("hdf5: name doesn't exist") } return getImage(g.id, name) } diff --git a/h5g_group_test.go b/h5g_group_test.go index 16f317c..12a9604 100644 --- a/h5g_group_test.go +++ b/h5g_group_test.go @@ -135,7 +135,7 @@ func TestGroup(t *testing.T) { func TestImage(t *testing.T) { f, err := CreateFile(fname, F_ACC_TRUNC) if err != nil { - t.Fatalf("CreateFile failed: %s", err) + t.Fatalf("CreateFile failed: %v", err) } defer os.Remove(fname) defer f.Close() @@ -146,36 +146,36 @@ func TestImage(t *testing.T) { } } if err != nil { - t.Fatalf("image decoding failed: %s", err) + t.Fatalf("image decoding failed: %v", err) } g1, err := f.CreateGroup("foo") if err != nil { - t.Fatalf("couldn't create group: %s", err) + t.Fatalf("couldn't create group: %v", err) } defer g1.Close() - err = g1.CreateTrueImage("image", img) + err = g1.CreateImage("image", img) if err != nil { - t.Fatalf("image saving failed: %s", err) + t.Fatalf("image saving failed: %v", err) } - imgRead, err := g1.ReadTrueImage("image") + imgRead, err := g1.ReadImage("image") if err != nil { - t.Fatalf("image reading failed: %s", err) + t.Fatalf("image reading failed: %v", err) } - widthGot := imgRead.Bounds().Max.X - heightGot := imgRead.Bounds().Max.Y - if widthGot != 1000 || heightGot != 500 { - t.Fatalf("image dimension miss match: Got %d * %d, suppose to be 1000x500", widthGot, heightGot) + gotWidth := imgRead.Bounds().Max.X + gotHeight := imgRead.Bounds().Max.Y + if gotWidth != 1000 || gotHeight != 500 { + t.Errorf("image dimension mismatch: got %dx%d, want:1000x500", gotWidth, gotHeight) } imgfile, err := os.Create("img.jpg") if err != nil { - t.Fatalf("image file creation failed: %s", err) + t.Fatalf("image file creation failed: %v", err) } defer os.Remove("img.jpg") defer imgfile.Close() err = jpeg.Encode(imgfile, imgRead, nil) if err != nil { - t.Fatalf(" jpeg image saving err: %s", err) + t.Errorf("unexpected error saving image: %v", err) } } diff --git a/h5i_image.go b/h5i_image.go index 777fc91..e797bdb 100644 --- a/h5i_image.go +++ b/h5i_image.go @@ -17,37 +17,32 @@ import ( // newImage takes a image object and convert it to a hdf5 format and write to the id node func newImage(id C.hid_t, name string, img image.Image) error { - if img == nil { - return errors.New("nil image!") - } width := img.Bounds().Max.X height := img.Bounds().Max.Y var c_width, c_height C.hsize_t c_name := C.CString(name) defer C.free(unsafe.Pointer(c_name)) - var gbuf []uint8 + var buf []byte for y := 0; y < height; y++ { for x := 0; x < width; x++ { r, g, b, _ := img.At(x, y).RGBA() - gbuf = append(gbuf, uint8(r>>8)) - gbuf = append(gbuf, uint8(g>>8)) - gbuf = append(gbuf, uint8(b>>8)) + buf = append(buf, byte(r>>8), byte(g>>8), byte(b>>8)) } } c_width = C.hsize_t(width) c_height = C.hsize_t(height) - c_image := (*C.uchar)(unsafe.Pointer(&gbuf[0])) + c_image := (*C.uchar)(unsafe.Pointer(&buf[0])) status := C.H5IMmake_image_24bit(id, c_name, c_width, c_height, C.CString("INTERLACE_PIXEL"), c_image) if status < 0 { - return errors.New("Failed to create HDF5 true color image") + return errors.New("hdf5: failed to create true color image") } return nil } // func getImage(id C.hid_t, name string) (image.Image, error) { - //TODO Should handle interlace and npal better, yet these two are not needed for simple image read and write + //TODO(zyc-sudo) Should handle interlace and npal better, yet these two are not needed for simple image read and write. var width, height, planes C.hsize_t var npals C.hssize_t var interlace C.char