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 }