Skip to content

Commit

Permalink
first pass float32blk support before full nD, arbitrary byte/voxel sy…
Browse files Browse the repository at this point in the history
…stem
  • Loading branch information
DocSavage committed Nov 21, 2016
1 parent 6bd5083 commit afa0aa9
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 3 deletions.
29 changes: 29 additions & 0 deletions datatype/imageblk/float32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Data type float32 tailors the image block data type for 32-bit float images. It simply
wraps the voxels package, setting Channels (1) and BytesPerValue(4).
*/

package imageblk

import (
"github.com/janelia-flyem/dvid/datastore"
"github.com/janelia-flyem/dvid/dvid"
)

var float32EncodeFormat dvid.DataValues

func init() {
float32EncodeFormat = dvid.DataValues{
{
T: dvid.T_float32,
Label: "float32",
},
}
interpolable := true
floatimg := NewType(float32EncodeFormat, interpolable)
floatimg.Type.Name = "float32blk"
floatimg.Type.URL = "github.com/janelia-flyem/dvid/datatype/imageblk/float32.go"
floatimg.Type.Version = "0.1"

datastore.Register(&floatimg)
}
226 changes: 226 additions & 0 deletions datatype/imageblk/float32_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package imageblk

import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"reflect"
"testing"

"github.com/janelia-flyem/dvid/datastore"
"github.com/janelia-flyem/dvid/dvid"
"github.com/janelia-flyem/dvid/server"
)

var (
floatimgT datastore.TypeService
)

// A slice of bytes representing a 3d float32 volume
type testVolume struct {
data []byte
offset dvid.Point3d
size dvid.Point3d
}

func newFloatVolume(t *testing.T, offset, size dvid.Point3d) *testVolume {
var buf bytes.Buffer
for i := int64(0); i < size.Prod(); i++ {
if err := binary.Write(&buf, binary.LittleEndian, float32(i)); err != nil {
t.Fatalf("couldn't write to float volume: %v\n", err)
}
}
return &testVolume{
data: buf.Bytes(),
offset: offset,
size: size,
}
}

// Put label data into given data instance.
func (v *testVolume) put(t *testing.T, uuid dvid.UUID, name string) {
apiStr := fmt.Sprintf("%snode/%s/%s/raw/0_1_2/%d_%d_%d/%d_%d_%d?mutate=true", server.WebAPIPath,
uuid, name, v.size[0], v.size[1], v.size[2], v.offset[0], v.offset[1], v.offset[2])
server.TestHTTP(t, "POST", apiStr, bytes.NewBuffer(v.data))
}

func createFloatTestVolume(t *testing.T, uuid dvid.UUID, name string, offset, size dvid.Point3d) *testVolume {
volume := newFloatVolume(t, offset, size)

// Send data over HTTP to populate a data instance
volume.put(t, uuid, name)
return volume
}

func TestFloatInstanceCreation(t *testing.T) {
datastore.OpenTest()
defer datastore.CloseTest()

uuid, _ := datastore.NewTestRepo()

// Create new voxels instance with optional parameters
name := "weights"
metadata := fmt.Sprintf(`{
"typename": "float32blk",
"dataname": %q,
"blocksize": "64,43,28",
"VoxelSize": "13.1, 14.2, 15.3",
"VoxelUnits": "picometers,nanometers,microns"
}`, name)
apiStr := fmt.Sprintf("%srepo/%s/instance", server.WebAPIPath, uuid)
server.TestHTTP(t, "POST", apiStr, bytes.NewBufferString(metadata))

// Get metadata and make sure optional settings have been set.
apiStr = fmt.Sprintf("%snode/%s/%s/info", server.WebAPIPath, uuid, name)
result := server.TestHTTP(t, "GET", apiStr, nil)
var parsed = struct {
Base struct {
TypeName, Name string
}
Extended struct {
BlockSize dvid.Point3d
VoxelSize dvid.NdFloat32
VoxelUnits dvid.NdString
}
}{}
if err := json.Unmarshal(result, &parsed); err != nil {
t.Fatalf("Error parsing JSON response of new instance metadata: %v\n", err)
}
if parsed.Base.Name != name {
t.Errorf("Parsed new instance has unexpected name: %s != %s (expected)\n",
parsed.Base.Name, name)
}
if parsed.Base.TypeName != "float32blk" {
t.Errorf("Parsed new instance has unexpected type name: %s != uint8blk (expected)\n",
parsed.Base.TypeName)
}
if !parsed.Extended.BlockSize.Equals(dvid.Point3d{64, 43, 28}) {
t.Errorf("Bad block size in new uint8blk instance: %s\n", parsed.Extended.BlockSize)
}
if !parsed.Extended.VoxelSize.Equals(dvid.NdFloat32{13.1, 14.2, 15.3}) {
t.Errorf("Bad voxel size in new uint8blk instance: %s\n", parsed.Extended.VoxelSize)
}
if parsed.Extended.VoxelUnits[0] != "picometers" {
t.Errorf("Got %q for X voxel units, not picometers\n", parsed.Extended.VoxelUnits[0])
}
if parsed.Extended.VoxelUnits[1] != "nanometers" {
t.Errorf("Got %q for X voxel units, not picometers\n", parsed.Extended.VoxelUnits[0])
}
if parsed.Extended.VoxelUnits[2] != "microns" {
t.Errorf("Got %q for X voxel units, not picometers\n", parsed.Extended.VoxelUnits[0])
}
}

func TestFloatDirectCalls(t *testing.T) {
datastore.OpenTest()
defer datastore.CloseTest()

uuid, versionID := initTestRepo()

server.CreateTestInstance(t, uuid, "float32blk", "floatimg", dvid.Config{})
dataservice, err := datastore.GetDataByUUIDName(uuid, "floatimg")
if err != nil {
t.Fatal(err)
}
floatimg, ok := dataservice.(*Data)
if !ok {
t.Fatalf("Can't convert dataservice %v into imageblk.Data\n", dataservice)
}
ctx := datastore.NewVersionedCtx(floatimg, versionID)

// Create a block-aligned 8-bit grayscale image
offset := dvid.Point3d{512, 32, 1024}
size := dvid.Point3d{128, 96, 64}
subvol := dvid.NewSubvolume(offset, size)
testvol := createFloatTestVolume(t, uuid, "floatimg", offset, size)
origData := make([]byte, len(testvol.data))
copy(origData, testvol.data)

// Store it into datastore at root
v, err := floatimg.NewVoxels(subvol, testvol.data)
if err != nil {
t.Fatalf("Unable to make new floatimg voxels: %v\n", err)
}
if err = floatimg.IngestVoxels(versionID, 1, v, ""); err != nil {
t.Errorf("Unable to put voxels for %s: %v\n", ctx, err)
}

// Read the stored image
v2, err := floatimg.NewVoxels(subvol, nil)
if err != nil {
t.Errorf("Unable to make new grayscale ExtHandler: %v\n", err)
}
if err = floatimg.GetVoxels(versionID, v2, ""); err != nil {
t.Errorf("Unable to get voxels for %s: %v\n", ctx, err)
}

// Make sure the retrieved image matches the original
if v.Stride() != v2.Stride() {
t.Errorf("Stride in retrieved subvol incorrect\n")
}
if v.Interpolable() != v2.Interpolable() {
t.Errorf("Interpolable bool in retrieved subvol incorrect\n")
}
if !reflect.DeepEqual(v.Size(), v2.Size()) {
t.Errorf("Size in retrieved subvol incorrect: %s vs expected %s\n",
v2.Size(), v.Size())
}
if v.NumVoxels() != v2.NumVoxels() {
t.Errorf("# voxels in retrieved is different: %d vs expected %d\n",
v2.NumVoxels(), v.NumVoxels())
}
if len(v.Data()) != len(v2.Data()) {
t.Errorf("Expected %d bytes in retrieved data, got %d bytes\n", len(v.Data()), len(v2.Data()))
}
received := v2.Data()
//dvid.PrintNonZero("original value", origData)
//dvid.PrintNonZero("returned value", data)
for i := int64(0); i < v2.NumVoxels(); i++ {
if received[i] != origData[i] {
t.Logf("Data returned != data stored for voxel %d\n", i)
t.Logf("Size of data: %d bytes from GET, %d bytes in PUT\n", len(received), len(origData))
t.Fatalf("GET subvol (%d) != PUT subvol (%d) @ index %d", received[i], origData[i], i)
}
}
}

func TestFloat32RepoPersistence(t *testing.T) {
datastore.OpenTest()
defer datastore.CloseTest()

uuid, _ := initTestRepo()

// Make grayscale and set various properties
config := dvid.NewConfig()
config.Set("BlockSize", "12,13,14")
config.Set("VoxelSize", "1.1,2.8,11")
config.Set("VoxelUnits", "microns,millimeters,nanometers")
dataservice, err := datastore.NewData(uuid, floatimgT, "floatimg", config)
if err != nil {
t.Errorf("Unable to create float32 instance: %s\n", err)
}
floatimg, ok := dataservice.(*Data)
if !ok {
t.Errorf("Can't cast float32 data service into Data\n")
}
oldData := *floatimg

// Restart test datastore and see if datasets are still there.
if err = datastore.SaveDataByUUID(uuid, floatimg); err != nil {
t.Fatalf("Unable to save repo during floatimg persistence test: %v\n", err)
}
datastore.CloseReopenTest()

dataservice2, err := datastore.GetDataByUUIDName(uuid, "floatimg")
if err != nil {
t.Fatalf("Can't get floatimg instance from reloaded test db: %v\n", err)
}
floatimg2, ok := dataservice2.(*Data)
if !ok {
t.Errorf("Returned new data instance 2 is not imageblk.Data\n")
}
if !oldData.Equals(floatimg2) {
t.Errorf("Expected %v, got %v\n", oldData, *floatimg2)
}
}
8 changes: 5 additions & 3 deletions datatype/imageblk/imageblk.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ GET <api URL>/node/<UUID>/<data name>/rawkey?x=<block x>&y=<block y>&z=<block z>
GET <api URL>/node/<UUID>/<data name>/isotropic/<dims>/<size>/<offset>[/<format>][?queryopts]
Retrieves either 2d images (PNG by default) or 3d binary data, depending on the dims parameter.
Retrieves either 2d images (PNG by default) or 3d binary data, depending on the dims parameter.
If the underlying data is float32, then the little-endian four byte format is written as RGBA.
The 3d binary data response has "Content-type" set to "application/octet-stream" and is an array of
voxel values in ZYX order (X iterates most rapidly).
Expand Down Expand Up @@ -212,7 +213,8 @@ GET <api URL>/node/<UUID>/<data name>/isotropic/<dims>/<size>/<offset>[/<format
GET <api URL>/node/<UUID>/<data name>/raw/<dims>/<size>/<offset>[/<format>][?queryopts]
Retrieves either 2d images (PNG by default) or 3d binary data, depending on the dims parameter.
Retrieves either 2d images (PNG by default) or 3d binary data, depending on the dims parameter.
If the underlying data is float32, then the little-endian four byte format is written as RGBA.
The 3d binary data response has "Content-type" set to "application/octet-stream" and is an array of
voxel values in ZYX order (X iterates most rapidly).
Expand Down Expand Up @@ -242,7 +244,7 @@ GET <api URL>/node/<UUID>/<data name>/raw/<dims>/<size>/<offset>[/<format>][?qu
available in server implementation.
2D: "png", "jpg" (default: "png")
jpg allows lossy quality setting, e.g., "jpg:80"
nD: uses default "octet-stream".
3D: uses default "octet-stream".
Query-string Options:
Expand Down
4 changes: 4 additions & 0 deletions datatype/imageblk/uint8_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ func initTestRepo() (dvid.UUID, dvid.VersionID) {
if err != nil {
log.Fatalf("Can't get uint8blk type: %s\n", err)
}
floatimgT, err = datastore.TypeServiceByName("float32blk")
if err != nil {
log.Fatalf("Can't get float32blk type: %s\n", err)
}
roiT, err = datastore.TypeServiceByName("roi")
if err != nil {
log.Fatalf("Can't get ROI type: %s\n", err)
Expand Down

0 comments on commit afa0aa9

Please sign in to comment.