github.com/janelia-flyem/dvid@v1.0.0/datatype/labelmap/blocks.go (about) 1 package labelmap 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "encoding/binary" 7 "fmt" 8 "io/ioutil" 9 "net/http" 10 "strconv" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/janelia-flyem/dvid/datastore" 16 "github.com/janelia-flyem/dvid/datatype/common/labels" 17 "github.com/janelia-flyem/dvid/dvid" 18 "github.com/janelia-flyem/dvid/storage" 19 lz4 "github.com/janelia-flyem/go/golz4-updated" 20 ) 21 22 func writeBlock(w http.ResponseWriter, bcoord dvid.ChunkPoint3d, out []byte) error { 23 if err := binary.Write(w, binary.LittleEndian, bcoord[0]); err != nil { 24 return err 25 } 26 if err := binary.Write(w, binary.LittleEndian, bcoord[1]); err != nil { 27 return err 28 } 29 if err := binary.Write(w, binary.LittleEndian, bcoord[2]); err != nil { 30 return err 31 } 32 if err := binary.Write(w, binary.LittleEndian, uint32(len(out))); err != nil { 33 return err 34 } 35 if written, err := w.Write(out); err != nil || written != int(len(out)) { 36 if err != nil { 37 dvid.Errorf("error writing value: %v\n", err) 38 return err 39 } 40 return fmt.Errorf("could not write %d bytes of block %s: only %d bytes written", len(out), bcoord, written) 41 } 42 return nil 43 } 44 45 type blockTiming struct { 46 readT, transcodeT, writeT time.Duration 47 readN, transcodeN int64 48 sync.RWMutex 49 } 50 51 func (bt *blockTiming) writeDone(t0 time.Time) { 52 bt.Lock() 53 bt.writeT += time.Since(t0) 54 bt.Unlock() 55 } 56 57 func (bt *blockTiming) readDone(t0 time.Time) { 58 bt.Lock() 59 bt.readT += time.Since(t0) 60 bt.readN++ 61 bt.Unlock() 62 } 63 64 func (bt *blockTiming) transcodeDone(t0 time.Time) { 65 bt.Lock() 66 bt.transcodeT += time.Since(t0) 67 bt.transcodeN++ 68 bt.Unlock() 69 } 70 71 func (bt *blockTiming) String() string { 72 var readAvgT, transcodeAvgT, writeAvgT time.Duration 73 if bt.readN == 0 { 74 readAvgT = 0 75 } else { 76 readAvgT = bt.readT / time.Duration(bt.readN) 77 } 78 if bt.transcodeN == 0 { 79 transcodeAvgT = 0 80 } else { 81 transcodeAvgT = bt.transcodeT / time.Duration(bt.transcodeN) 82 } 83 if bt.readN == 0 { 84 writeAvgT = 0 85 } else { 86 writeAvgT = bt.writeT / time.Duration(bt.readN) 87 } 88 return fmt.Sprintf("read %s (%s), transcode %s (%s), write %s (%s)", bt.readT, readAvgT, bt.transcodeT, transcodeAvgT, bt.writeT, writeAvgT) 89 } 90 91 type blockData struct { 92 bcoord dvid.ChunkPoint3d 93 compression string 94 supervoxels bool 95 v dvid.VersionID 96 data []byte 97 } 98 99 // transcodes a block of data by doing any data modifications necessary to meet requested 100 // compression compared to stored compression as well as raw supervoxels versus mapped labels. 101 func (d *Data) transcodeBlock(b blockData) (out []byte, err error) { 102 formatIn, checksum := dvid.DecodeSerializationFormat(dvid.SerializationFormat(b.data[0])) 103 104 var start int 105 if checksum == dvid.CRC32 { 106 start = 5 107 } else { 108 start = 1 109 } 110 111 var outsize uint32 112 113 switch formatIn { 114 case dvid.LZ4: 115 outsize = binary.LittleEndian.Uint32(b.data[start : start+4]) 116 out = b.data[start+4:] 117 if len(out) != int(outsize) { 118 err = fmt.Errorf("block %s was corrupted lz4: supposed size %d but had %d bytes", b.bcoord, outsize, len(out)) 119 return 120 } 121 case dvid.Uncompressed, dvid.Gzip: 122 outsize = uint32(len(b.data[start:])) 123 out = b.data[start:] 124 default: 125 err = fmt.Errorf("labelmap data was stored in unknown compressed format: %s", formatIn) 126 return 127 } 128 129 var formatOut dvid.CompressionFormat 130 switch b.compression { 131 case "", "lz4": 132 formatOut = dvid.LZ4 133 case "blocks": 134 formatOut = formatIn 135 case "gzip": 136 formatOut = dvid.Gzip 137 case "uncompressed": 138 formatOut = dvid.Uncompressed 139 default: 140 err = fmt.Errorf("unknown compression %q requested for blocks", b.compression) 141 return 142 } 143 144 var doMapping bool 145 var mapping *VCache 146 if !b.supervoxels { 147 if mapping, err = getMapping(d, b.v); err != nil { 148 return 149 } 150 if mapping != nil && mapping.mapUsed { 151 doMapping = true 152 } 153 } 154 155 // Need to do uncompression/recompression if we are changing compression or mapping 156 var uncompressed, recompressed []byte 157 if formatIn != formatOut || b.compression == "gzip" || doMapping { 158 switch formatIn { 159 case dvid.LZ4: 160 uncompressed = make([]byte, outsize) 161 if err = lz4.Uncompress(out, uncompressed); err != nil { 162 return 163 } 164 case dvid.Uncompressed: 165 uncompressed = out 166 case dvid.Gzip: 167 gzipIn := bytes.NewBuffer(out) 168 var zr *gzip.Reader 169 zr, err = gzip.NewReader(gzipIn) 170 if err != nil { 171 return 172 } 173 uncompressed, err = ioutil.ReadAll(zr) 174 if err != nil { 175 return 176 } 177 zr.Close() 178 } 179 180 var block labels.Block 181 if err = block.UnmarshalBinary(uncompressed); err != nil { 182 err = fmt.Errorf("unable to deserialize label block %s: %v", b.bcoord, err) 183 return 184 } 185 186 if !b.supervoxels { 187 modifyBlockMapping(b.v, &block, mapping) 188 } 189 190 if b.compression == "blocks" { // send native DVID block compression with gzip 191 out, err = block.CompressGZIP() 192 if err != nil { 193 return nil, err 194 } 195 } else { // we are sending raw block data 196 uint64array, size := block.MakeLabelVolume() 197 expectedSize := d.BlockSize().(dvid.Point3d) 198 if !size.Equals(expectedSize) { 199 err = fmt.Errorf("deserialized label block size %s does not equal data %q block size %s", size, d.DataName(), expectedSize) 200 return 201 } 202 203 switch formatOut { 204 case dvid.LZ4: 205 recompressed = make([]byte, lz4.CompressBound(uint64array)) 206 var size int 207 if size, err = lz4.Compress(uint64array, recompressed); err != nil { 208 return nil, err 209 } 210 outsize = uint32(size) 211 out = recompressed[:outsize] 212 case dvid.Uncompressed: 213 out = uint64array 214 case dvid.Gzip: 215 var gzipOut bytes.Buffer 216 zw := gzip.NewWriter(&gzipOut) 217 if _, err = zw.Write(uint64array); err != nil { 218 return nil, err 219 } 220 zw.Flush() 221 zw.Close() 222 out = gzipOut.Bytes() 223 } 224 } 225 } 226 return 227 } 228 229 // try to write a single block either by streaming (allows for termination) or by writing 230 // with a simplified pipeline compared to subvolumes larger than a block. 231 func (d *Data) writeBlockToHTTP(ctx *datastore.VersionedCtx, w http.ResponseWriter, subvol *dvid.Subvolume, compression string, supervoxels bool, scale uint8, roiname dvid.InstanceName) (done bool, err error) { 232 // Can't handle ROI for now. 233 if roiname != "" { 234 return 235 } 236 237 // Can only handle 3d requests. 238 blockSize, okBlockSize := d.BlockSize().(dvid.Point3d) 239 subvolSize, okSubvolSize := subvol.Size().(dvid.Point3d) 240 startPt, okStartPt := subvol.StartPoint().(dvid.Point3d) 241 if !okBlockSize || !okSubvolSize || !okStartPt { 242 return 243 } 244 245 // Can only handle single block for now. 246 if subvolSize != blockSize { 247 return 248 } 249 250 // Can only handle aligned block for now. 251 chunkPt, aligned := dvid.GetChunkPoint3d(startPt, blockSize) 252 if !aligned { 253 return 254 } 255 256 if compression != "" { 257 err = d.sendCompressedBlock(ctx, w, subvol, compression, chunkPt, scale, supervoxels) 258 } else { 259 err = d.streamRawBlock(ctx, w, chunkPt, scale, supervoxels) 260 } 261 if err != nil { 262 return 263 } 264 265 return true, nil 266 } 267 268 // send a single aligned block of data via HTTP. 269 func (d *Data) sendCompressedBlock(ctx *datastore.VersionedCtx, w http.ResponseWriter, subvol *dvid.Subvolume, compression string, chunkPt dvid.ChunkPoint3d, scale uint8, supervoxels bool) error { 270 bcoordStr := chunkPt.ToIZYXString() 271 block, err := d.getLabelBlock(ctx, scale, bcoordStr) 272 if err != nil { 273 return err 274 } 275 if block == nil { 276 return fmt.Errorf("unable to get label block %s", bcoordStr) 277 } 278 if !supervoxels { 279 vc, err := getMapping(d, ctx.VersionID()) 280 if err != nil { 281 return err 282 } 283 modifyBlockMapping(ctx.VersionID(), block, vc) 284 } 285 data, _ := block.MakeLabelVolume() 286 if err := writeCompressedToHTTP(compression, data, subvol, w); err != nil { 287 return err 288 } 289 return nil 290 } 291 292 // writes a block of data as uncompressed ZYX uint64 to the writer in streaming fashion, allowing 293 // for possible termination / error at any point. 294 func (d *Data) streamRawBlock(ctx *datastore.VersionedCtx, w http.ResponseWriter, bcoord dvid.ChunkPoint3d, scale uint8, supervoxels bool) error { 295 bcoordStr := bcoord.ToIZYXString() 296 block, err := d.getLabelBlock(ctx, scale, bcoordStr) 297 if err != nil { 298 return err 299 } 300 if !supervoxels { 301 mapping, err := getMapping(d, ctx.VersionID()) 302 if err != nil { 303 return err 304 } 305 modifyBlockMapping(ctx.VersionID(), block, mapping) 306 } 307 if err := block.WriteLabelVolume(w); err != nil { 308 return err 309 } 310 return nil 311 } 312 313 // returns nil block if no block is at the given block coordinate 314 func (d *Data) getLabelBlock(ctx *datastore.VersionedCtx, scale uint8, bcoord dvid.IZYXString) (*labels.Block, error) { 315 store, err := datastore.GetKeyValueDB(d) 316 if err != nil { 317 return nil, fmt.Errorf("labelmap getLabelBlock() had error initializing store: %v", err) 318 } 319 tk := NewBlockTKeyByCoord(scale, bcoord) 320 val, err := store.Get(ctx, tk) 321 if err != nil { 322 return nil, fmt.Errorf("error on GET of labelmap %q label block @ %s", d.DataName(), bcoord) 323 } 324 if val == nil { 325 return nil, nil 326 } 327 data, _, err := dvid.DeserializeData(val, true) 328 if err != nil { 329 return nil, fmt.Errorf("unable to deserialize label block in %q: %v", d.DataName(), err) 330 } 331 block := new(labels.Block) 332 if err := block.UnmarshalBinary(data); err != nil { 333 return nil, err 334 } 335 return block, nil 336 } 337 338 func (d *Data) getLabelPositionedBlock(ctx *datastore.VersionedCtx, scale uint8, bcoord dvid.IZYXString) (*labels.PositionedBlock, error) { 339 block, err := d.getLabelBlock(ctx, scale, bcoord) 340 if err != nil { 341 return nil, err 342 } 343 if block == nil { 344 return nil, nil 345 } 346 return &labels.PositionedBlock{Block: *block, BCoord: bcoord}, nil 347 } 348 349 func (d *Data) putLabelBlock(ctx *datastore.VersionedCtx, scale uint8, pblock *labels.PositionedBlock) error { 350 store, err := datastore.GetKeyValueDB(d) 351 if err != nil { 352 return fmt.Errorf("labelmap putLabelBlock() had error initializing store: %v", err) 353 } 354 tk := NewBlockTKeyByCoord(scale, pblock.BCoord) 355 356 data, err := pblock.MarshalBinary() 357 if err != nil { 358 return err 359 } 360 361 val, err := dvid.SerializeData(data, d.Compression(), d.Checksum()) 362 if err != nil { 363 return fmt.Errorf("unable to serialize block %s in %q: %v", pblock.BCoord, d.DataName(), err) 364 } 365 return store.Put(ctx, tk, val) 366 } 367 368 type blockSend struct { 369 bcoord dvid.ChunkPoint3d 370 value []byte 371 err error 372 } 373 374 // convert a slice of 3 integer strings into a coordinate 375 func strArrayToBCoord(coordarray []string) (bcoord dvid.ChunkPoint3d, err error) { 376 var xloc, yloc, zloc int 377 if xloc, err = strconv.Atoi(coordarray[0]); err != nil { 378 return 379 } 380 if yloc, err = strconv.Atoi(coordarray[1]); err != nil { 381 return 382 } 383 if zloc, err = strconv.Atoi(coordarray[2]); err != nil { 384 return 385 } 386 return dvid.ChunkPoint3d{int32(xloc), int32(yloc), int32(zloc)}, nil 387 } 388 389 // sendBlocksSpecific writes data to the blocks specified -- best for non-ordered backend 390 func (d *Data) sendBlocksSpecific(ctx *datastore.VersionedCtx, w http.ResponseWriter, supervoxels bool, compression, blockstring string, scale uint8) (numBlocks int, err error) { 391 timedLog := dvid.NewTimeLog() 392 switch compression { 393 case "": 394 compression = "blocks" 395 case "lz4", "gzip", "blocks", "uncompressed": 396 break 397 default: 398 err = fmt.Errorf(`compression must be "lz4" (default), "gzip", "blocks" or "uncompressed"`) 399 return 400 } 401 402 w.Header().Set("Content-type", "application/octet-stream") 403 404 // extract querey string 405 if blockstring == "" { 406 return 407 } 408 coordarray := strings.Split(blockstring, ",") 409 if len(coordarray)%3 != 0 { 410 return 0, fmt.Errorf("block query string should be three coordinates per block") 411 } 412 413 var store storage.KeyValueDB 414 if store, err = datastore.GetKeyValueDB(d); err != nil { 415 return 416 } 417 418 // launch goroutine that will stream blocks to client 419 numBlocks = len(coordarray) / 3 420 wg := new(sync.WaitGroup) 421 422 ch := make(chan blockSend, numBlocks) 423 var sendErr error 424 var startBlock dvid.ChunkPoint3d 425 var timing blockTiming 426 go func() { 427 for data := range ch { 428 if data.err != nil && sendErr == nil { 429 sendErr = data.err 430 } else if len(data.value) > 0 { 431 t0 := time.Now() 432 err := writeBlock(w, data.bcoord, data.value) 433 if err != nil && sendErr == nil { 434 sendErr = err 435 } 436 timing.writeDone(t0) 437 } 438 wg.Done() 439 } 440 timedLog.Infof("labelmap %q specificblocks - finished sending %d blocks starting with %s", d.DataName(), numBlocks, startBlock) 441 }() 442 443 // iterate through each block, get data from store, and transcode based on request parameters 444 for i := 0; i < len(coordarray); i += 3 { 445 var bcoord dvid.ChunkPoint3d 446 if bcoord, err = strArrayToBCoord(coordarray[i : i+3]); err != nil { 447 return 448 } 449 if i == 0 { 450 startBlock = bcoord 451 } 452 wg.Add(1) 453 t0 := time.Now() 454 indexBeg := dvid.IndexZYX(bcoord) 455 keyBeg := NewBlockTKey(scale, &indexBeg) 456 457 var value []byte 458 value, err = store.Get(ctx, keyBeg) 459 timing.readDone(t0) 460 461 if err != nil { 462 ch <- blockSend{err: err} 463 return 464 } 465 466 if len(value) > 0 { 467 go func(bcoord dvid.ChunkPoint3d, value []byte) { 468 b := blockData{ 469 bcoord: bcoord, 470 v: ctx.VersionID(), 471 data: value, 472 compression: compression, 473 supervoxels: supervoxels, 474 } 475 t0 := time.Now() 476 out, err := d.transcodeBlock(b) 477 timing.transcodeDone(t0) 478 ch <- blockSend{bcoord: bcoord, value: out, err: err} 479 }(bcoord, value) 480 } else { 481 ch <- blockSend{value: nil} 482 } 483 } 484 timedLog.Infof("labelmap %q specificblocks - launched concurrent reads of %d blocks starting with %s", d.DataName(), numBlocks, startBlock) 485 wg.Wait() 486 close(ch) 487 dvid.Infof("labelmap %q specificblocks - %d blocks starting with %s: %s\n", d.DataName(), numBlocks, startBlock, &timing) 488 return numBlocks, sendErr 489 } 490 491 // sendBlocksVolume writes a series of blocks covering the given block-aligned subvolume to a HTTP response. 492 func (d *Data) sendBlocksVolume(ctx *datastore.VersionedCtx, w http.ResponseWriter, supervoxels bool, scale uint8, subvol *dvid.Subvolume, compression string) error { 493 w.Header().Set("Content-type", "application/octet-stream") 494 495 switch compression { 496 case "", "lz4", "gzip", "blocks", "uncompressed": 497 default: 498 return fmt.Errorf(`compression must be "lz4" (default), "gzip", "blocks" or "uncompressed"`) 499 } 500 501 // convert x,y,z coordinates to block coordinates for this scale 502 blocksdims := subvol.Size().Div(d.BlockSize()) 503 blocksoff := subvol.StartPoint().Div(d.BlockSize()) 504 505 timedLog := dvid.NewTimeLog() 506 defer timedLog.Infof("SendBlocks %s, span x %d, span y %d, span z %d", blocksoff, blocksdims.Value(0), blocksdims.Value(1), blocksdims.Value(2)) 507 508 numBlocks := int(blocksdims.Prod()) 509 wg := new(sync.WaitGroup) 510 511 // launch goroutine that will stream blocks to client 512 ch := make(chan blockSend, numBlocks) 513 var sendErr error 514 go func() { 515 for data := range ch { 516 if data.err != nil && sendErr == nil { 517 sendErr = data.err 518 } else { 519 err := writeBlock(w, data.bcoord, data.value) 520 if err != nil && sendErr == nil { 521 sendErr = err 522 } 523 } 524 wg.Done() 525 } 526 }() 527 528 store, err := datastore.GetOrderedKeyValueDB(d) 529 if err != nil { 530 return fmt.Errorf("Data type labelmap had error initializing store: %v", err) 531 } 532 533 okv := store.(storage.BufferableOps) 534 // extract buffer interface 535 req, hasbuffer := okv.(storage.KeyValueRequester) 536 if hasbuffer { 537 okv = req.NewBuffer(ctx) 538 } 539 540 for ziter := int32(0); ziter < blocksdims.Value(2); ziter++ { 541 for yiter := int32(0); yiter < blocksdims.Value(1); yiter++ { 542 beginPoint := dvid.ChunkPoint3d{blocksoff.Value(0), blocksoff.Value(1) + yiter, blocksoff.Value(2) + ziter} 543 endPoint := dvid.ChunkPoint3d{blocksoff.Value(0) + blocksdims.Value(0) - 1, blocksoff.Value(1) + yiter, blocksoff.Value(2) + ziter} 544 545 indexBeg := dvid.IndexZYX(beginPoint) 546 sx, sy, sz := indexBeg.Unpack() 547 begTKey := NewBlockTKey(scale, &indexBeg) 548 indexEnd := dvid.IndexZYX(endPoint) 549 endTKey := NewBlockTKey(scale, &indexEnd) 550 551 // Send the entire range of key-value pairs to chunk processor 552 err = okv.ProcessRange(ctx, begTKey, endTKey, &storage.ChunkOp{}, func(c *storage.Chunk) error { 553 if c == nil || c.TKeyValue == nil { 554 return nil 555 } 556 kv := c.TKeyValue 557 if kv.V == nil { 558 return nil 559 } 560 561 // Determine which block this is. 562 _, indexZYX, err := DecodeBlockTKey(kv.K) 563 if err != nil { 564 return err 565 } 566 x, y, z := indexZYX.Unpack() 567 if z != sz || y != sy || x < sx || x >= sx+int32(blocksdims.Value(0)) { 568 return nil 569 } 570 b := blockData{ 571 bcoord: dvid.ChunkPoint3d{x, y, z}, 572 compression: compression, 573 supervoxels: supervoxels, 574 v: ctx.VersionID(), 575 data: kv.V, 576 } 577 wg.Add(1) 578 go func(b blockData) { 579 out, err := d.transcodeBlock(b) 580 ch <- blockSend{bcoord: b.bcoord, value: out, err: err} 581 }(b) 582 return nil 583 }) 584 585 if err != nil { 586 return fmt.Errorf("unable to GET data %s: %v", ctx, err) 587 } 588 } 589 } 590 591 wg.Wait() 592 close(ch) 593 594 if hasbuffer { 595 // submit the entire buffer to the DB 596 err = okv.(storage.RequestBuffer).Flush() 597 if err != nil { 598 return fmt.Errorf("unable to GET data %s: %v", ctx, err) 599 } 600 } 601 602 return sendErr 603 } 604 605 // getSupervoxelBlock returns a compressed supervoxel Block of the given block coordinate. 606 func (d *Data) getSupervoxelBlock(v dvid.VersionID, bcoord dvid.ChunkPoint3d, scale uint8) (*labels.Block, error) { 607 store, err := datastore.GetOrderedKeyValueDB(d) 608 if err != nil { 609 return nil, err 610 } 611 612 // Retrieve the block of labels 613 ctx := datastore.NewVersionedCtx(d, v) 614 index := dvid.IndexZYX(bcoord) 615 serialization, err := store.Get(ctx, NewBlockTKey(scale, &index)) 616 if err != nil { 617 return nil, fmt.Errorf("error getting '%s' block for index %s", d.DataName(), bcoord) 618 } 619 if serialization == nil { 620 blockSize, ok := d.BlockSize().(dvid.Point3d) 621 if !ok { 622 return nil, fmt.Errorf("block size for data %q should be 3d, not: %s", d.DataName(), d.BlockSize()) 623 } 624 return labels.MakeSolidBlock(0, blockSize), nil 625 } 626 deserialization, _, err := dvid.DeserializeData(serialization, true) 627 if err != nil { 628 return nil, fmt.Errorf("unable to deserialize block %s in '%s': %v", bcoord, d.DataName(), err) 629 } 630 var block labels.Block 631 if err = block.UnmarshalBinary(deserialization); err != nil { 632 return nil, err 633 } 634 return &block, nil 635 } 636 637 // getBlockLabels returns a block of labels at given scale in packed little-endian uint64 format. 638 func (d *Data) getBlockLabels(v dvid.VersionID, bcoord dvid.ChunkPoint3d, scale uint8, supervoxels bool) ([]byte, error) { 639 block, err := d.getSupervoxelBlock(v, bcoord, scale) 640 if err != nil { 641 return nil, err 642 } 643 var mapping *VCache 644 if !supervoxels { 645 if mapping, err = getMapping(d, v); err != nil { 646 return nil, err 647 } 648 } 649 if mapping != nil { 650 err = modifyBlockMapping(v, block, mapping) 651 if err != nil { 652 return nil, fmt.Errorf("unable to modify block %s mapping: %v", bcoord, err) 653 } 654 } 655 labelData, _ := block.MakeLabelVolume() 656 return labelData, nil 657 }