github.com/janelia-flyem/dvid@v1.0.0/datatype/labelblk/labelblk_test.go (about)

     1  package labelblk
     2  
     3  import (
     4  	"bytes"
     5  	"compress/gzip"
     6  	"encoding/binary"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"log"
    11  	"reflect"
    12  	"sync"
    13  	"testing"
    14  
    15  	"github.com/janelia-flyem/dvid/datastore"
    16  	"github.com/janelia-flyem/dvid/dvid"
    17  	"github.com/janelia-flyem/dvid/server"
    18  
    19  	lz4 "github.com/janelia-flyem/go/golz4-updated"
    20  )
    21  
    22  var (
    23  	labelsT datastore.TypeService
    24  	testMu  sync.Mutex
    25  )
    26  
    27  // Sets package-level testRepo and TestVersionID
    28  func initTestRepo() (dvid.UUID, dvid.VersionID) {
    29  	testMu.Lock()
    30  	defer testMu.Unlock()
    31  	if labelsT == nil {
    32  		var err error
    33  		labelsT, err = datastore.TypeServiceByName("labelblk")
    34  		if err != nil {
    35  			log.Fatalf("Can't get labelblk type: %s\n", err)
    36  		}
    37  	}
    38  	return datastore.NewTestRepo()
    39  }
    40  
    41  // Data from which to construct repeatable 3d images where adjacent voxels have different values.
    42  var xdata = []uint64{23, 819229, 757, 100303, 9991}
    43  var ydata = []uint64{66599, 201, 881067, 5488, 0}
    44  var zdata = []uint64{1, 734, 43990122, 42, 319596}
    45  
    46  // Make a 2d slice of bytes with top left corner at (ox,oy,oz) and size (nx,ny)
    47  func makeSlice(offset dvid.Point3d, size dvid.Point2d) []byte {
    48  	numBytes := size[0] * size[1] * 8
    49  	slice := make([]byte, numBytes, numBytes)
    50  	i := 0
    51  	modz := offset[2] % int32(len(zdata))
    52  	for y := int32(0); y < size[1]; y++ {
    53  		sy := y + offset[1]
    54  		mody := sy % int32(len(ydata))
    55  		sx := offset[0]
    56  		for x := int32(0); x < size[0]; x++ {
    57  			modx := sx % int32(len(xdata))
    58  			binary.BigEndian.PutUint64(slice[i:i+8], xdata[modx]+ydata[mody]+zdata[modz])
    59  			i += 8
    60  			sx++
    61  		}
    62  	}
    63  	return slice
    64  }
    65  
    66  // Make a 3d volume of bytes with top left corner at (ox,oy,oz) and size (nx, ny, nz)
    67  func makeVolume(offset, size dvid.Point3d) []byte {
    68  	sliceBytes := size[0] * size[1] * 8
    69  	volumeBytes := sliceBytes * size[2]
    70  	volume := make([]byte, volumeBytes, volumeBytes)
    71  	var i int32
    72  	size2d := dvid.Point2d{size[0], size[1]}
    73  	startZ := offset[2]
    74  	endZ := startZ + size[2]
    75  	for z := startZ; z < endZ; z++ {
    76  		offset[2] = z
    77  		copy(volume[i:i+sliceBytes], makeSlice(offset, size2d))
    78  		i += sliceBytes
    79  	}
    80  	return volume
    81  }
    82  
    83  // Creates a new data instance for labelblk
    84  func newDataInstance(uuid dvid.UUID, t *testing.T, name dvid.InstanceName) *Data {
    85  	config := dvid.NewConfig()
    86  	dataservice, err := datastore.NewData(uuid, labelsT, name, config)
    87  	if err != nil {
    88  		t.Fatalf("Unable to create labelblk instance %q: %v\n", name, err)
    89  	}
    90  	labels, ok := dataservice.(*Data)
    91  	if !ok {
    92  		t.Fatalf("Can't cast labels data service into Data\n")
    93  	}
    94  	return labels
    95  }
    96  
    97  func TestLabelblkDirectAPI(t *testing.T) {
    98  	if err := server.OpenTest(); err != nil {
    99  		t.Fatalf("can't open test server: %v\n", err)
   100  	}
   101  	defer server.CloseTest()
   102  
   103  	uuid, versionID := initTestRepo()
   104  	labels := newDataInstance(uuid, t, "mylabels")
   105  	labelsCtx := datastore.NewVersionedCtx(labels, versionID)
   106  
   107  	// Create a fake block-aligned label volume
   108  	offset := dvid.Point3d{32, 0, 64}
   109  	size := dvid.Point3d{96, 64, 160}
   110  	subvol := dvid.NewSubvolume(offset, size)
   111  	data := makeVolume(offset, size)
   112  
   113  	// Store it into datastore at root
   114  	v, err := labels.NewVoxels(subvol, data)
   115  	if err != nil {
   116  		t.Fatalf("Unable to make new labels Voxels: %v\n", err)
   117  	}
   118  	if err = labels.IngestVoxels(versionID, 1, v, ""); err != nil {
   119  		t.Errorf("Unable to put labels for %s: %v\n", labelsCtx, err)
   120  	}
   121  	if v.NumVoxels() != int64(len(data))/8 {
   122  		t.Errorf("# voxels (%d) after PutVoxels != # original voxels (%d)\n",
   123  			v.NumVoxels(), int64(len(data))/8)
   124  	}
   125  
   126  	// Read the stored image
   127  	v2, err := labels.NewVoxels(subvol, nil)
   128  	if err != nil {
   129  		t.Errorf("Unable to make new labels ExtHandler: %v\n", err)
   130  	}
   131  	if err = labels.GetVoxels(versionID, v2, ""); err != nil {
   132  		t.Errorf("Unable to get voxels for %s: %v\n", labelsCtx, err)
   133  	}
   134  
   135  	// Make sure the retrieved image matches the original
   136  	if v.Stride() != v2.Stride() {
   137  		t.Errorf("Stride in retrieved subvol incorrect\n")
   138  	}
   139  	if v.Interpolable() != v2.Interpolable() {
   140  		t.Errorf("Interpolable bool in retrieved subvol incorrect\n")
   141  	}
   142  	if !reflect.DeepEqual(v.Size(), v2.Size()) {
   143  		t.Errorf("Size in retrieved subvol incorrect: %s vs expected %s\n",
   144  			v2.Size(), v.Size())
   145  	}
   146  	if v.NumVoxels() != v2.NumVoxels() {
   147  		t.Errorf("# voxels in retrieved is different: %d vs expected %d\n",
   148  			v2.NumVoxels(), v.NumVoxels())
   149  	}
   150  	byteData := v2.Data()
   151  
   152  	for i := int64(0); i < v2.NumVoxels()*8; i++ {
   153  		if byteData[i] != data[i] {
   154  			t.Logf("Size of data: %d bytes from GET, %d bytes in PUT\n", len(data), len(data))
   155  			t.Fatalf("GET subvol (%d) != PUT subvol (%d) @ uint64 #%d", byteData[i], data[i], i)
   156  		}
   157  	}
   158  }
   159  func TestLabelblkRepoPersistence(t *testing.T) {
   160  	if err := server.OpenTest(); err != nil {
   161  		t.Fatalf("can't open test server: %v\n", err)
   162  	}
   163  	defer server.CloseTest()
   164  
   165  	uuid, _ := initTestRepo()
   166  
   167  	// Make labels and set various properties
   168  	config := dvid.NewConfig()
   169  	config.Set("BlockSize", "12,13,14")
   170  	config.Set("VoxelSize", "1.1,2.8,11")
   171  	config.Set("VoxelUnits", "microns,millimeters,nanometers")
   172  	dataservice, err := datastore.NewData(uuid, labelsT, "mylabels", config)
   173  	if err != nil {
   174  		t.Errorf("Unable to create labels instance: %v\n", err)
   175  	}
   176  	labels, ok := dataservice.(*Data)
   177  	if !ok {
   178  		t.Errorf("Can't cast labels data service into Data\n")
   179  	}
   180  	oldData := *labels
   181  
   182  	// Restart test datastore and see if datasets are still there.
   183  	if err = datastore.SaveDataByUUID(uuid, labels); err != nil {
   184  		t.Fatalf("Unable to save repo during labels persistence test: %v\n", err)
   185  	}
   186  	datastore.CloseReopenTest()
   187  
   188  	dataservice2, err := datastore.GetDataByUUIDName(uuid, "mylabels")
   189  	if err != nil {
   190  		t.Fatalf("Can't get labels instance from reloaded test db: %v\n", err)
   191  	}
   192  	labels2, ok := dataservice2.(*Data)
   193  	if !ok {
   194  		t.Errorf("Returned new data instance 2 is not imageblk.Data\n")
   195  	}
   196  	if !oldData.Equals(labels2) {
   197  		t.Errorf("Expected %v, got %v\n", oldData, *labels2)
   198  	}
   199  }
   200  
   201  type tuple [4]int32
   202  
   203  var labelsROI = []tuple{
   204  	tuple{3, 3, 2, 4}, tuple{3, 4, 2, 3}, tuple{3, 5, 3, 3},
   205  	tuple{4, 3, 2, 5}, tuple{4, 4, 3, 4}, tuple{4, 5, 2, 4},
   206  	//tuple{5, 2, 3, 4}, tuple{5, 3, 3, 3}, tuple{5, 4, 2, 3}, tuple{5, 5, 2, 2},
   207  }
   208  
   209  func labelsJSON() string {
   210  	b, err := json.Marshal(labelsROI)
   211  	if err != nil {
   212  		return ""
   213  	}
   214  	return string(b)
   215  }
   216  
   217  func inroi(x, y, z int32) bool {
   218  	for _, span := range labelsROI {
   219  		if span[0] != z {
   220  			continue
   221  		}
   222  		if span[1] != y {
   223  			continue
   224  		}
   225  		if span[2] > x || span[3] < x {
   226  			continue
   227  		}
   228  		return true
   229  	}
   230  	return false
   231  }
   232  
   233  // A slice of bytes representing 3d label volume
   234  type testVolume struct {
   235  	data []byte
   236  	size dvid.Point3d
   237  }
   238  
   239  func newTestVolume(nx, ny, nz int32) *testVolume {
   240  	return &testVolume{
   241  		data: make([]byte, nx*ny*nz*8),
   242  		size: dvid.Point3d{nx, ny, nz},
   243  	}
   244  }
   245  
   246  // Add a label to a subvolume.
   247  func (v *testVolume) addSubvol(origin, size dvid.Point3d, label uint64) {
   248  	nx := v.size[0]
   249  	nxy := nx * v.size[1]
   250  	spanBytes := size[0] * 8
   251  	buf := make([]byte, spanBytes)
   252  	for x := int32(0); x < size[0]; x++ {
   253  		binary.LittleEndian.PutUint64(buf[x*8:x*8+8], label)
   254  	}
   255  	for z := origin[2]; z < origin[2]+size[2]; z++ {
   256  		for y := origin[1]; y < origin[1]+size[1]; y++ {
   257  			i := (z*nxy + y*nx + origin[0]) * 8
   258  			copy(v.data[i:i+spanBytes], buf)
   259  		}
   260  	}
   261  }
   262  
   263  // Put label data into given data instance.
   264  func (v *testVolume) put(t *testing.T, uuid dvid.UUID, name string) {
   265  	apiStr := fmt.Sprintf("%snode/%s/%s/raw/0_1_2/%d_%d_%d/0_0_0", server.WebAPIPath,
   266  		uuid, name, v.size[0], v.size[1], v.size[2])
   267  	server.TestHTTP(t, "POST", apiStr, bytes.NewBuffer(v.data))
   268  }
   269  
   270  func (v *testVolume) putMutable(t *testing.T, uuid dvid.UUID, name string) {
   271  	apiStr := fmt.Sprintf("%snode/%s/%s/raw/0_1_2/%d_%d_%d/0_0_0?mutate=true", server.WebAPIPath,
   272  		uuid, name, v.size[0], v.size[1], v.size[2])
   273  	server.TestHTTP(t, "POST", apiStr, bytes.NewBuffer(v.data))
   274  }
   275  
   276  func (v *testVolume) get(t *testing.T, uuid dvid.UUID, name string) {
   277  	apiStr := fmt.Sprintf("%snode/%s/%s/raw/0_1_2/%d_%d_%d/0_0_0", server.WebAPIPath,
   278  		uuid, name, v.size[0], v.size[1], v.size[2])
   279  	v.data = server.TestHTTP(t, "GET", apiStr, nil)
   280  }
   281  
   282  func (v *testVolume) getVoxel(pt dvid.Point3d) uint64 {
   283  	nx := v.size[0]
   284  	nxy := nx * v.size[1]
   285  	i := (pt[2]*nxy + pt[1]*nx + pt[0]) * 8
   286  	return binary.LittleEndian.Uint64(v.data[i : i+8])
   287  }
   288  
   289  func (v *testVolume) verifyLabel(t *testing.T, expected uint64, x, y, z int32) {
   290  	pt := dvid.Point3d{x, y, z}
   291  	label := v.getVoxel(pt)
   292  	if label != expected {
   293  		t.Errorf("Expected label %d at %s for first downres but got %d instead\n", expected, pt, label)
   294  	}
   295  }
   296  
   297  func (v *testVolume) equals(v2 *testVolume) error {
   298  	if !v.size.Equals(v2.size) {
   299  		return fmt.Errorf("volume sizes are not equal")
   300  	}
   301  	if len(v.data) != len(v2.data) {
   302  		return fmt.Errorf("data lengths are not equal")
   303  	}
   304  	for i, value := range v.data {
   305  		if value != v2.data[i] {
   306  			return fmt.Errorf("For element %d, found value %d != %d\n", i, value, v2.data[i])
   307  		}
   308  	}
   309  	return nil
   310  }
   311  
   312  type mergeJSON string
   313  
   314  func (mjson mergeJSON) send(t *testing.T, uuid dvid.UUID, name string) {
   315  	apiStr := fmt.Sprintf("%snode/%s/%s/merge", server.WebAPIPath, uuid, name)
   316  	server.TestHTTP(t, "POST", apiStr, bytes.NewBufferString(string(mjson)))
   317  }
   318  
   319  func TestMultiscale(t *testing.T) {
   320  	if err := server.OpenTest(); err != nil {
   321  		t.Fatalf("can't open test server: %v\n", err)
   322  	}
   323  	defer server.CloseTest()
   324  
   325  	// Create testbed volume and data instances
   326  	uuid, _ := initTestRepo()
   327  	var config dvid.Config
   328  	server.CreateTestInstance(t, uuid, "labelblk", "labels", config)
   329  
   330  	// Add multiscale
   331  	server.CreateTestInstance(t, uuid, "labelblk", "labels_1", config) // 64 x 64 x 64
   332  	server.CreateTestSync(t, uuid, "labels_1", "labels")
   333  	server.CreateTestInstance(t, uuid, "labelblk", "labels_2", config) // 32 x 32 x 32
   334  	server.CreateTestSync(t, uuid, "labels_2", "labels_1")
   335  
   336  	// Create an easily interpreted label volume with a couple of labels.
   337  	volume := newTestVolume(128, 128, 128)
   338  	volume.addSubvol(dvid.Point3d{40, 40, 40}, dvid.Point3d{40, 40, 40}, 1)
   339  	volume.addSubvol(dvid.Point3d{40, 40, 80}, dvid.Point3d{40, 40, 40}, 2)
   340  	volume.addSubvol(dvid.Point3d{80, 40, 40}, dvid.Point3d{40, 40, 40}, 13)
   341  	volume.addSubvol(dvid.Point3d{40, 80, 40}, dvid.Point3d{40, 40, 40}, 209)
   342  	volume.addSubvol(dvid.Point3d{80, 80, 40}, dvid.Point3d{40, 40, 40}, 311)
   343  	volume.put(t, uuid, "labels")
   344  
   345  	// Verify initial ingest for hi-res
   346  	if err := datastore.BlockOnUpdating(uuid, "labels"); err != nil {
   347  		t.Fatalf("Error blocking on update for labels: %v\n", err)
   348  	}
   349  	hires := newTestVolume(128, 128, 128)
   350  	hires.get(t, uuid, "labels")
   351  	hires.verifyLabel(t, 1, 45, 45, 45)
   352  	hires.verifyLabel(t, 2, 50, 50, 100)
   353  	hires.verifyLabel(t, 13, 100, 60, 60)
   354  	hires.verifyLabel(t, 209, 55, 100, 55)
   355  	hires.verifyLabel(t, 311, 81, 81, 41)
   356  
   357  	// Check the first downres: 64^3
   358  	if err := datastore.BlockOnUpdating(uuid, "labels_1"); err != nil {
   359  		t.Fatalf("Error blocking on update for labels_1: %v\n", err)
   360  	}
   361  	downres1 := newTestVolume(64, 64, 64)
   362  	downres1.get(t, uuid, "labels_1")
   363  	downres1.verifyLabel(t, 1, 30, 30, 30)
   364  	downres1.verifyLabel(t, 2, 21, 21, 45)
   365  	downres1.verifyLabel(t, 13, 45, 21, 36)
   366  	downres1.verifyLabel(t, 209, 21, 50, 35)
   367  	downres1.verifyLabel(t, 311, 45, 55, 35)
   368  	expected1 := newTestVolume(64, 64, 64)
   369  	expected1.addSubvol(dvid.Point3d{20, 20, 20}, dvid.Point3d{20, 20, 20}, 1)
   370  	expected1.addSubvol(dvid.Point3d{20, 20, 40}, dvid.Point3d{20, 20, 20}, 2)
   371  	expected1.addSubvol(dvid.Point3d{40, 20, 20}, dvid.Point3d{20, 20, 20}, 13)
   372  	expected1.addSubvol(dvid.Point3d{20, 40, 20}, dvid.Point3d{20, 20, 20}, 209)
   373  	expected1.addSubvol(dvid.Point3d{40, 40, 20}, dvid.Point3d{20, 20, 20}, 311)
   374  	if err := downres1.equals(expected1); err != nil {
   375  		t.Errorf("1st downres 'labels_1' isn't what is expected: %v\n", err)
   376  	}
   377  
   378  	// Check the second downres to voxel: 32^3
   379  	if err := datastore.BlockOnUpdating(uuid, "labels_2"); err != nil {
   380  		t.Fatalf("Error blocking on update for labels_2: %v\n", err)
   381  	}
   382  	expected2 := newTestVolume(32, 32, 32)
   383  	expected2.addSubvol(dvid.Point3d{10, 10, 10}, dvid.Point3d{10, 10, 10}, 1)
   384  	expected2.addSubvol(dvid.Point3d{10, 10, 20}, dvid.Point3d{10, 10, 10}, 2)
   385  	expected2.addSubvol(dvid.Point3d{20, 10, 10}, dvid.Point3d{10, 10, 10}, 13)
   386  	expected2.addSubvol(dvid.Point3d{10, 20, 10}, dvid.Point3d{10, 10, 10}, 209)
   387  	expected2.addSubvol(dvid.Point3d{20, 20, 10}, dvid.Point3d{10, 10, 10}, 311)
   388  	downres2 := newTestVolume(32, 32, 32)
   389  	downres2.get(t, uuid, "labels_2")
   390  	if err := downres2.equals(expected2); err != nil {
   391  		t.Errorf("2nd downres 'labels_2' isn't what is expected: %v\n", err)
   392  	}
   393  }
   394  
   395  type labelVol struct {
   396  	size      dvid.Point3d
   397  	blockSize dvid.Point3d
   398  	offset    dvid.Point3d
   399  	name      string
   400  	data      []byte
   401  
   402  	nx, ny, nz int32
   403  
   404  	startLabel uint64
   405  }
   406  
   407  func (vol *labelVol) makeLabelVolume(t *testing.T, uuid dvid.UUID, startLabel uint64) {
   408  	if vol.startLabel == startLabel && vol.data != nil {
   409  		return
   410  	}
   411  
   412  	vol.startLabel = startLabel
   413  
   414  	vol.nx = vol.size[0] * vol.blockSize[0]
   415  	vol.ny = vol.size[1] * vol.blockSize[1]
   416  	vol.nz = vol.size[2] * vol.blockSize[2]
   417  
   418  	vol.data = make([]byte, vol.numBytes())
   419  	label := startLabel
   420  	var x, y, z, v int32
   421  	for z = 0; z < vol.nz; z++ {
   422  		for y = 0; y < vol.ny; y++ {
   423  			for x = 0; x < vol.nx; x++ {
   424  				label++
   425  				binary.LittleEndian.PutUint64(vol.data[v:v+8], label)
   426  				v += 8
   427  			}
   428  		}
   429  	}
   430  	return
   431  }
   432  
   433  func (vol *labelVol) numBytes() int32 {
   434  	return vol.nx * vol.ny * vol.nz * 8
   435  }
   436  
   437  // Create a new label volume and post it to the test datastore.
   438  // Each voxel in volume has sequential labels in X, Y, then Z order.
   439  func (vol *labelVol) postLabelVolume(t *testing.T, uuid dvid.UUID, compression, roi string, startLabel uint64) {
   440  	vol.makeLabelVolume(t, uuid, startLabel)
   441  
   442  	apiStr := fmt.Sprintf("%snode/%s/%s/raw/0_1_2/%d_%d_%d/%d_%d_%d", server.WebAPIPath,
   443  		uuid, vol.name, vol.nx, vol.ny, vol.nz, vol.offset[0], vol.offset[1], vol.offset[2])
   444  	query := true
   445  
   446  	var data []byte
   447  	var err error
   448  	switch compression {
   449  	case "lz4":
   450  		apiStr += "?compression=lz4"
   451  		compressed := make([]byte, lz4.CompressBound(vol.data))
   452  		var outSize int
   453  		if outSize, err = lz4.Compress(vol.data, compressed); err != nil {
   454  			t.Fatal(err)
   455  		}
   456  		data = compressed[:outSize]
   457  	case "gzip":
   458  		apiStr += "?compression=gzip"
   459  		var buf bytes.Buffer
   460  		gw := gzip.NewWriter(&buf)
   461  		if _, err = gw.Write(vol.data); err != nil {
   462  			t.Fatal(err)
   463  		}
   464  		data = buf.Bytes()
   465  		if err = gw.Close(); err != nil {
   466  			t.Fatal(err)
   467  		}
   468  	default:
   469  		data = vol.data
   470  		query = false
   471  	}
   472  	if roi != "" {
   473  		if query {
   474  			apiStr += "&roi=" + roi
   475  		} else {
   476  			apiStr += "?roi=" + roi
   477  		}
   478  	}
   479  	server.TestHTTP(t, "POST", apiStr, bytes.NewBuffer(data))
   480  }
   481  
   482  func (vol *labelVol) getLabelVolume(t *testing.T, uuid dvid.UUID, compression, roi string) []byte {
   483  	apiStr := fmt.Sprintf("%snode/%s/%s/raw/0_1_2/%d_%d_%d/%d_%d_%d", server.WebAPIPath,
   484  		uuid, vol.name, vol.nx, vol.ny, vol.nz, vol.offset[0], vol.offset[1], vol.offset[2])
   485  	query := true
   486  	switch compression {
   487  	case "lz4":
   488  		apiStr += "?compression=lz4"
   489  	case "gzip":
   490  		apiStr += "?compression=gzip"
   491  	default:
   492  		query = false
   493  	}
   494  	if roi != "" {
   495  		if query {
   496  			apiStr += "&roi=" + roi
   497  		} else {
   498  			apiStr += "?roi=" + roi
   499  		}
   500  	}
   501  	data := server.TestHTTP(t, "GET", apiStr, nil)
   502  	switch compression {
   503  	case "lz4":
   504  		uncompressed := make([]byte, vol.numBytes())
   505  		if err := lz4.Uncompress(data, uncompressed); err != nil {
   506  			t.Fatalf("Unable to uncompress LZ4 data (%s), %d bytes: %v\n", apiStr, len(data), err)
   507  		}
   508  		data = uncompressed
   509  	case "gzip":
   510  		buf := bytes.NewBuffer(data)
   511  		gr, err := gzip.NewReader(buf)
   512  		if err != nil {
   513  			t.Fatalf("Error on gzip new reader: %v\n", err)
   514  		}
   515  		uncompressed, err := ioutil.ReadAll(gr)
   516  		if err != nil {
   517  			t.Fatalf("Error on reading gzip: %v\n", err)
   518  		}
   519  		if err = gr.Close(); err != nil {
   520  			t.Fatalf("Error on closing gzip: %v\n", err)
   521  		}
   522  		data = uncompressed
   523  	default:
   524  	}
   525  	if len(data) != int(vol.numBytes()) {
   526  		t.Errorf("Expected %d uncompressed bytes from 3d labelblk GET.  Got %d instead.", vol.numBytes(), len(data))
   527  	}
   528  	return data
   529  }
   530  
   531  func (vol *labelVol) testGetLabelVolume(t *testing.T, uuid dvid.UUID, compression, roi string) []byte {
   532  	data := vol.getLabelVolume(t, uuid, compression, roi)
   533  
   534  	// run test to make sure it's same volume as we posted.
   535  	label := vol.startLabel
   536  	var x, y, z, v int32
   537  	for z = 0; z < vol.nz; z++ {
   538  		for y = 0; y < vol.ny; y++ {
   539  			for x = 0; x < vol.nx; x++ {
   540  				label++
   541  				got := binary.LittleEndian.Uint64(data[v : v+8])
   542  				if label != got {
   543  					t.Fatalf("Error on 3d GET compression (%q): expected %d, got %d\n", compression, label, got)
   544  					return nil
   545  				}
   546  				v += 8
   547  			}
   548  		}
   549  	}
   550  
   551  	return data
   552  }
   553  
   554  func (vol *labelVol) testBlock(t *testing.T, bx, by, bz int32, data []byte) {
   555  	// Get offset of this block in label volume
   556  	ox := bx*32 - vol.offset[0]
   557  	oy := by*32 - vol.offset[1]
   558  	oz := bz*32 - vol.offset[2]
   559  
   560  	// Get label strides over size of this subvolume
   561  	labelDy := vol.size[0] * vol.blockSize[0]
   562  	labelDz := labelDy * vol.size[1] * vol.blockSize[1]
   563  
   564  	p := 0
   565  	var x, y, z int32
   566  	for z = 0; z < 32; z++ {
   567  		for y = 0; y < 32; y++ {
   568  			label := vol.startLabel + 1 + uint64((z+oz)*labelDz+(y+oy)*labelDy+ox)
   569  			for x = 0; x < 32; x++ {
   570  				got := binary.LittleEndian.Uint64(data[p : p+8])
   571  				if label != got {
   572  					t.Fatalf("Error on block (%d,%d,%d) at voxel (%d,%d,%d): expected %d, got %d\n", bx, by, bz, x, y, z, label, got)
   573  				}
   574  				p += 8
   575  				label++
   576  			}
   577  		}
   578  	}
   579  }
   580  
   581  func (vol *labelVol) testBlocks(t *testing.T, uuid dvid.UUID, compression, roi string) {
   582  	span := 5
   583  	apiStr := fmt.Sprintf("%snode/%s/%s/blocks/%d_%d_%d/%d_%d_%d", server.WebAPIPath,
   584  		uuid, vol.name, 160, 32, 32, vol.offset[0], vol.offset[1], vol.offset[2])
   585  	if compression == "uncompressed" {
   586  		apiStr += "?compression=uncompressed"
   587  	}
   588  	data := server.TestHTTP(t, "GET", apiStr, nil)
   589  	fmt.Printf("Got %d bytes of data\n", len(data))
   590  
   591  	blockBytes := 32 * 32 * 32 * 8
   592  
   593  	// Check if values are what we expect
   594  	bx := vol.offset[0] / 32
   595  	by := vol.offset[1] / 32
   596  	bz := vol.offset[2] / 32
   597  	b := 0
   598  	for i := 0; i < span; i++ {
   599  		// Get block coord + block size
   600  		if b+16 > len(data) {
   601  			t.Fatalf("Only got %d bytes back from block API call, yet next coord in span would put index @ %d\n", len(data), b+16)
   602  		}
   603  		x := int32(binary.LittleEndian.Uint32(data[b : b+4]))
   604  		b += 4
   605  		y := int32(binary.LittleEndian.Uint32(data[b : b+4]))
   606  		b += 4
   607  		z := int32(binary.LittleEndian.Uint32(data[b : b+4]))
   608  		b += 4
   609  		n := int(binary.LittleEndian.Uint32(data[b : b+4]))
   610  		b += 4
   611  		if x != bx || y != by || z != bz {
   612  			t.Errorf("Bad block coordinate: expected (%d,%d,%d), got (%d,%d,%d)\n", bx, by, bz, x, y, z)
   613  		}
   614  
   615  		// Read in the block data as assumed LZ4 and check it.
   616  		var uncompressed []byte
   617  		if compression != "uncompressed" {
   618  			uncompressed = make([]byte, blockBytes)
   619  			if err := lz4.Uncompress(data[b:b+n], uncompressed); err != nil {
   620  				t.Fatalf("Unable to uncompress LZ4 data (%s), %d bytes: %v\n", apiStr, n, err)
   621  			}
   622  		} else {
   623  			uncompressed = data[b : b+n]
   624  		}
   625  		vol.testBlock(t, x, y, z, uncompressed)
   626  		b += n
   627  		bx++
   628  	}
   629  }
   630  
   631  // the label in the test volume should just be the start label + voxel index + 1 when iterating in ZYX order.
   632  // The passed (x,y,z) should be world coordinates, not relative to the volume offset.
   633  func (vol *labelVol) label(x, y, z int32) uint64 {
   634  	if x < vol.offset[0] || x >= vol.offset[0]+vol.size[0]*vol.blockSize[0] {
   635  		return 0
   636  	}
   637  	if y < vol.offset[1] || y >= vol.offset[1]+vol.size[1]*vol.blockSize[1] {
   638  		return 0
   639  	}
   640  	if z < vol.offset[2] || z >= vol.offset[2]+vol.size[2]*vol.blockSize[2] {
   641  		return 0
   642  	}
   643  	x -= vol.offset[0]
   644  	y -= vol.offset[1]
   645  	z -= vol.offset[2]
   646  	nx := vol.size[0] * vol.blockSize[0]
   647  	nxy := nx * vol.size[1] * vol.blockSize[1]
   648  	return vol.startLabel + uint64(z*nxy) + uint64(y*nx) + uint64(x+1)
   649  }
   650  
   651  type sliceTester struct {
   652  	orient string
   653  	width  int32
   654  	height int32
   655  	offset dvid.Point3d // offset of slice
   656  }
   657  
   658  func (s sliceTester) apiStr(uuid dvid.UUID, name string) string {
   659  	return fmt.Sprintf("%snode/%s/%s/raw/%s/%d_%d/%d_%d_%d", server.WebAPIPath,
   660  		uuid, name, s.orient, s.width, s.height, s.offset[0], s.offset[1], s.offset[2])
   661  }
   662  
   663  // make sure the given labels match what would be expected from the test volume.
   664  func (s sliceTester) testLabel(t *testing.T, vol labelVol, img *dvid.Image) {
   665  	data := img.Data()
   666  	var x, y, z int32
   667  	i := 0
   668  	switch s.orient {
   669  	case "xy":
   670  		for y = 0; y < s.height; y++ {
   671  			for x = 0; x < s.width; x++ {
   672  				label := binary.LittleEndian.Uint64(data[i*8 : (i+1)*8])
   673  				i++
   674  				vx := x + s.offset[0]
   675  				vy := y + s.offset[1]
   676  				vz := s.offset[2]
   677  				expected := vol.label(vx, vy, vz)
   678  				if label != expected {
   679  					t.Errorf("Bad label @ (%d,%d,%d): expected %d, got %d\n", vx, vy, vz, expected, label)
   680  					return
   681  				}
   682  			}
   683  		}
   684  		return
   685  	case "xz":
   686  		for z = 0; z < s.height; z++ {
   687  			for x = 0; x < s.width; x++ {
   688  				label := binary.LittleEndian.Uint64(data[i*8 : (i+1)*8])
   689  				i++
   690  				vx := x + s.offset[0]
   691  				vy := s.offset[1]
   692  				vz := z + s.offset[2]
   693  				expected := vol.label(vx, vy, vz)
   694  				if label != expected {
   695  					t.Errorf("Bad label @ (%d,%d,%d): expected %d, got %d\n", vx, vy, vz, expected, label)
   696  					return
   697  				}
   698  			}
   699  		}
   700  		return
   701  	case "yz":
   702  		for z = 0; z < s.height; z++ {
   703  			for y = 0; y < s.width; y++ {
   704  				label := binary.LittleEndian.Uint64(data[i*8 : (i+1)*8])
   705  				i++
   706  				vx := s.offset[0]
   707  				vy := y + s.offset[1]
   708  				vz := z + s.offset[2]
   709  				expected := vol.label(vx, vy, vz)
   710  				if label != expected {
   711  					t.Errorf("Bad label @ (%d,%d,%d): expected %d, got %d\n", vx, vy, vz, expected, label)
   712  					return
   713  				}
   714  			}
   715  		}
   716  		return
   717  	default:
   718  		t.Fatalf("Unknown slice orientation %q\n", s.orient)
   719  	}
   720  }
   721  
   722  func (vol labelVol) testSlices(t *testing.T, uuid dvid.UUID) {
   723  	// Verify XY slice.
   724  	sliceOffset := vol.offset
   725  	sliceOffset[0] += 51
   726  	sliceOffset[1] += 11
   727  	sliceOffset[2] += 23
   728  	slice := sliceTester{"xy", 67, 83, sliceOffset}
   729  	apiStr := slice.apiStr(uuid, vol.name)
   730  	xy := server.TestHTTP(t, "GET", apiStr, nil)
   731  	img, format, err := dvid.ImageFromBytes(xy, EncodeFormat(), false)
   732  	if err != nil {
   733  		t.Fatalf("Error on XY labels GET: %v\n", err)
   734  	}
   735  	if format != "png" {
   736  		t.Errorf("Expected XY labels GET to return %q image, got %q instead.\n", "png", format)
   737  	}
   738  	if img.NumBytes() != 67*83*8 {
   739  		t.Errorf("Expected %d bytes from XY labelblk GET.  Got %d instead.", 160*160*8, img.NumBytes())
   740  	}
   741  	slice.testLabel(t, vol, img)
   742  
   743  	// Verify XZ slice returns what we expect.
   744  	sliceOffset = vol.offset
   745  	sliceOffset[0] += 11
   746  	sliceOffset[1] += 4
   747  	sliceOffset[2] += 3
   748  	slice = sliceTester{"xz", 67, 83, sliceOffset}
   749  	apiStr = slice.apiStr(uuid, vol.name)
   750  	xz := server.TestHTTP(t, "GET", apiStr, nil)
   751  	img, format, err = dvid.ImageFromBytes(xz, EncodeFormat(), false)
   752  	if err != nil {
   753  		t.Fatalf("Error on XZ labels GET: %v\n", err)
   754  	}
   755  	if format != "png" {
   756  		t.Errorf("Expected XZ labels GET to return %q image, got %q instead.\n", "png", format)
   757  	}
   758  	if img.NumBytes() != 67*83*8 {
   759  		t.Errorf("Expected %d bytes from XZ labelblk GET.  Got %d instead.", 67*83*8, img.NumBytes())
   760  	}
   761  	slice.testLabel(t, vol, img)
   762  
   763  	// Verify YZ slice returns what we expect.
   764  	sliceOffset = vol.offset
   765  	sliceOffset[0] += 7
   766  	sliceOffset[1] += 33
   767  	sliceOffset[2] += 33
   768  	slice = sliceTester{"yz", 67, 83, sliceOffset}
   769  	apiStr = slice.apiStr(uuid, vol.name)
   770  	yz := server.TestHTTP(t, "GET", apiStr, nil)
   771  	img, format, err = dvid.ImageFromBytes(yz, EncodeFormat(), false)
   772  	if err != nil {
   773  		t.Fatalf("Error on YZ labels GET: %v\n", err)
   774  	}
   775  	if format != "png" {
   776  		t.Errorf("Expected YZ labels GET to return %q image, got %q instead.\n", "png", format)
   777  	}
   778  	if img.NumBytes() != 67*83*8 {
   779  		t.Errorf("Expected %d bytes from YZ labelblk GET.  Got %d instead.", 67*83*8, img.NumBytes())
   780  	}
   781  	slice.testLabel(t, vol, img)
   782  }
   783  
   784  type labelResp struct {
   785  	Label uint64
   786  }
   787  
   788  func TestLabels(t *testing.T) {
   789  	if err := server.OpenTest(); err != nil {
   790  		t.Fatalf("can't open test server: %v\n", err)
   791  	}
   792  	defer server.CloseTest()
   793  
   794  	uuid, _ := datastore.NewTestRepo()
   795  	if len(uuid) < 5 {
   796  		t.Fatalf("Bad root UUID for new repo: %s\n", uuid)
   797  	}
   798  
   799  	// Create a labelblk instance
   800  	server.CreateTestInstance(t, uuid, "labelblk", "labels", dvid.Config{})
   801  
   802  	vol := labelVol{
   803  		startLabel: 2,
   804  		size:       dvid.Point3d{5, 5, 5}, // in blocks
   805  		blockSize:  dvid.Point3d{32, 32, 32},
   806  		offset:     dvid.Point3d{32, 64, 96},
   807  		name:       "labels",
   808  	}
   809  	vol.postLabelVolume(t, uuid, "", "", 0)
   810  
   811  	// Test the blocks API
   812  	vol.testBlocks(t, uuid, "", "")
   813  	vol.testBlocks(t, uuid, "uncompressed", "")
   814  
   815  	// Test the "label" endpoint.
   816  	apiStr := fmt.Sprintf("%snode/%s/%s/label/100_64_96", server.WebAPIPath, uuid, "labels")
   817  	jsonResp := server.TestHTTP(t, "GET", apiStr, nil)
   818  	var r labelResp
   819  	if err := json.Unmarshal(jsonResp, &r); err != nil {
   820  		t.Errorf("Unable to parse 'label' endpoint response: %s\n", jsonResp)
   821  	}
   822  	if r.Label != 69 {
   823  		t.Errorf("Expected label %d @ (100, 64, 96) got label %d\n", vol.label(100, 64, 96), r.Label)
   824  	}
   825  
   826  	apiStr = fmt.Sprintf("%snode/%s/%s/label/10000_64000_9600121", server.WebAPIPath, uuid, "labels")
   827  	jsonResp = server.TestHTTP(t, "GET", apiStr, nil)
   828  	if err := json.Unmarshal(jsonResp, &r); err != nil {
   829  		t.Errorf("Unable to parse 'label' endpoint response: %s\n", jsonResp)
   830  	}
   831  	if r.Label != 0 {
   832  		t.Errorf("Expected label 0 at random huge point, got label %d\n", r.Label)
   833  	}
   834  
   835  	// Test the "labels" endpoint.
   836  	apiStr = fmt.Sprintf("%snode/%s/%s/labels", server.WebAPIPath, uuid, "labels")
   837  	payload := `[[100,64,96],[78,93,156],[104,65,97]]`
   838  	jsonResp = server.TestHTTP(t, "GET", apiStr, bytes.NewBufferString(payload))
   839  	var labels [3]uint64
   840  	if err := json.Unmarshal(jsonResp, &labels); err != nil {
   841  		t.Errorf("Unable to parse 'labels' endpoint response: %s\n", jsonResp)
   842  	}
   843  	if labels[0] != vol.label(100, 64, 96) {
   844  		t.Errorf("Expected label %d @ (100, 64, 96) got label %d\n", vol.label(100, 64, 96), labels[0])
   845  	}
   846  	if labels[1] != vol.label(78, 93, 156) {
   847  		t.Errorf("Expected label %d @ (78, 93, 156) got label %d\n", vol.label(78, 93, 156), labels[1])
   848  	}
   849  	if labels[2] != vol.label(104, 65, 97) {
   850  		t.Errorf("Expected label %d @ (104, 65, 97) got label %d\n", vol.label(104, 65, 97), labels[2])
   851  	}
   852  
   853  	// Repost the label volume 3 more times with increasing starting values.
   854  	vol.postLabelVolume(t, uuid, "", "", 2100)
   855  	vol.postLabelVolume(t, uuid, "", "", 8176)
   856  	vol.postLabelVolume(t, uuid, "", "", 16623)
   857  
   858  	vol.testSlices(t, uuid)
   859  
   860  	// Try to post last volume concurrently 3x and then check result.
   861  	wg := new(sync.WaitGroup)
   862  	wg.Add(3)
   863  	go func() {
   864  		vol.postLabelVolume(t, uuid, "", "", 16623)
   865  		wg.Done()
   866  	}()
   867  	go func() {
   868  		vol.postLabelVolume(t, uuid, "", "", 16623)
   869  		wg.Done()
   870  	}()
   871  	go func() {
   872  		vol.postLabelVolume(t, uuid, "", "", 16623)
   873  		wg.Done()
   874  	}()
   875  	wg.Wait()
   876  	vol.testGetLabelVolume(t, uuid, "", "")
   877  
   878  	// Try concurrent write of disjoint subvolumes.
   879  	vol2 := labelVol{
   880  		size:      dvid.Point3d{5, 5, 5}, // in blocks
   881  		blockSize: dvid.Point3d{32, 32, 32},
   882  		offset:    dvid.Point3d{192, 64, 96},
   883  		name:      "labels",
   884  	}
   885  	vol3 := labelVol{
   886  		size:      dvid.Point3d{5, 5, 5}, // in blocks
   887  		blockSize: dvid.Point3d{32, 32, 32},
   888  		offset:    dvid.Point3d{192, 224, 96},
   889  		name:      "labels",
   890  	}
   891  	vol4 := labelVol{
   892  		size:      dvid.Point3d{5, 5, 5}, // in blocks
   893  		blockSize: dvid.Point3d{32, 32, 32},
   894  		offset:    dvid.Point3d{32, 224, 96},
   895  		name:      "labels",
   896  	}
   897  
   898  	wg.Add(3)
   899  	go func() {
   900  		vol2.postLabelVolume(t, uuid, "lz4", "", 4000)
   901  		wg.Done()
   902  	}()
   903  	go func() {
   904  		vol3.postLabelVolume(t, uuid, "lz4", "", 8000)
   905  		wg.Done()
   906  	}()
   907  	go func() {
   908  		vol4.postLabelVolume(t, uuid, "lz4", "", 1200)
   909  		wg.Done()
   910  	}()
   911  	wg.Wait()
   912  	vol.testGetLabelVolume(t, uuid, "", "")
   913  	vol2.testGetLabelVolume(t, uuid, "", "")
   914  	vol3.testGetLabelVolume(t, uuid, "", "")
   915  	vol4.testGetLabelVolume(t, uuid, "", "")
   916  
   917  	// Verify various GET 3d volume with compressions and no ROI.
   918  	vol.testGetLabelVolume(t, uuid, "", "")
   919  	vol.testGetLabelVolume(t, uuid, "lz4", "")
   920  	vol.testGetLabelVolume(t, uuid, "gzip", "")
   921  
   922  	// Create a new ROI instance.
   923  	roiName := "myroi"
   924  	server.CreateTestInstance(t, uuid, "roi", roiName, dvid.Config{})
   925  
   926  	// Add ROI data
   927  	apiStr = fmt.Sprintf("%snode/%s/%s/roi", server.WebAPIPath, uuid, roiName)
   928  	server.TestHTTP(t, "POST", apiStr, bytes.NewBufferString(labelsJSON()))
   929  
   930  	// Post updated labels without ROI and make sure it returns those values.
   931  	var labelNoROI uint64 = 20000
   932  	vol.postLabelVolume(t, uuid, "", "", labelNoROI)
   933  	returned := vol.testGetLabelVolume(t, uuid, "", "")
   934  	startLabel := binary.LittleEndian.Uint64(returned[0:8])
   935  	if startLabel != labelNoROI+1 {
   936  		t.Errorf("Expected first voxel to be label %d and got %d instead\n", labelNoROI+1, startLabel)
   937  	}
   938  
   939  	// TODO - Use the ROI to retrieve a 2d xy image.
   940  
   941  	// TODO - Make sure we aren't getting labels back in non-ROI points.
   942  
   943  	// Post again but now with ROI
   944  	var labelWithROI uint64 = 40000
   945  	vol.postLabelVolume(t, uuid, "", roiName, labelWithROI)
   946  
   947  	// Verify ROI masking of POST where anything outside ROI is old labels.
   948  	returned = vol.getLabelVolume(t, uuid, "", "")
   949  	var newlabel uint64 = labelWithROI
   950  	var oldlabel uint64 = labelNoROI
   951  
   952  	nx := vol.size[0] * vol.blockSize[0]
   953  	ny := vol.size[1] * vol.blockSize[1]
   954  	nz := vol.size[2] * vol.blockSize[2]
   955  	var x, y, z, v int32
   956  	for z = 0; z < nz; z++ {
   957  		voxz := z + vol.offset[2]
   958  		blockz := voxz / DefaultBlockSize
   959  		for y = 0; y < ny; y++ {
   960  			voxy := y + vol.offset[1]
   961  			blocky := voxy / DefaultBlockSize
   962  			for x = 0; x < nx; x++ {
   963  				voxx := x + vol.offset[0]
   964  				blockx := voxx / DefaultBlockSize
   965  				oldlabel++
   966  				newlabel++
   967  				got := binary.LittleEndian.Uint64(returned[v : v+8])
   968  				if inroi(blockx, blocky, blockz) {
   969  					if got != newlabel {
   970  						t.Fatalf("Expected %d in ROI, got %d\n", newlabel, got)
   971  					}
   972  				} else {
   973  					if got != oldlabel {
   974  						t.Fatalf("Expected %d outside ROI, got %d\n", oldlabel, got)
   975  					}
   976  				}
   977  				v += 8
   978  			}
   979  		}
   980  	}
   981  
   982  	// Verify that a ROI-enabled GET has zeros everywhere outside ROI.
   983  	returned = vol.getLabelVolume(t, uuid, "", roiName)
   984  	newlabel = labelWithROI
   985  
   986  	x, y, z, v = 0, 0, 0, 0
   987  	for z = 0; z < nz; z++ {
   988  		voxz := z + vol.offset[2]
   989  		blockz := voxz / DefaultBlockSize
   990  		for y = 0; y < ny; y++ {
   991  			voxy := y + vol.offset[1]
   992  			blocky := voxy / DefaultBlockSize
   993  			for x = 0; x < nx; x++ {
   994  				voxx := x + vol.offset[0]
   995  				blockx := voxx / DefaultBlockSize
   996  				oldlabel++
   997  				newlabel++
   998  				got := binary.LittleEndian.Uint64(returned[v : v+8])
   999  				if inroi(blockx, blocky, blockz) {
  1000  					if got != newlabel {
  1001  						t.Fatalf("Expected %d in ROI, got %d\n", newlabel, got)
  1002  					}
  1003  				} else {
  1004  					if got != 0 {
  1005  						t.Fatalf("Expected zero outside ROI, got %d\n", got)
  1006  					}
  1007  				}
  1008  				v += 8
  1009  			}
  1010  		}
  1011  	}
  1012  }