github.com/janelia-flyem/dvid@v1.0.0/datatype/labelmap/write.go (about) 1 package labelmap 2 3 import ( 4 "fmt" 5 "io" 6 "sync" 7 8 "github.com/janelia-flyem/dvid/datastore" 9 "github.com/janelia-flyem/dvid/datatype/common/downres" 10 "github.com/janelia-flyem/dvid/datatype/common/labels" 11 "github.com/janelia-flyem/dvid/datatype/imageblk" 12 "github.com/janelia-flyem/dvid/dvid" 13 "github.com/janelia-flyem/dvid/server" 14 "github.com/janelia-flyem/dvid/storage" 15 ) 16 17 type putOperation struct { 18 data []byte // the full label volume sent to PUT 19 scale uint8 20 subvol *dvid.Subvolume 21 indexZYX dvid.IndexZYX 22 version dvid.VersionID 23 mutate bool // if false, we just ingest without needing to GET previous value 24 mutID uint64 // should be unique within a server's uptime. 25 downresMut *downres.Mutation 26 blockCh chan blockChange 27 } 28 29 // PutLabels persists voxels from a subvolume into the storage engine. This involves transforming 30 // a supplied volume of uint64 with known geometry into many labels.Block that tiles the subvolume. 31 // Messages are sent to subscribers for ingest events. 32 func (d *Data) PutLabels(v dvid.VersionID, subvol *dvid.Subvolume, data []byte, roiname dvid.InstanceName, mutate bool) error { 33 if subvol.DataShape().ShapeDimensions() != 3 { 34 return fmt.Errorf("cannot store labels for data %q in non 3D format", d.DataName()) 35 } 36 37 // Make sure data is block-aligned 38 if !dvid.BlockAligned(subvol, d.BlockSize()) { 39 return fmt.Errorf("cannot store labels for data %q in non-block aligned geometry %s -> %s", d.DataName(), subvol.StartPoint(), subvol.EndPoint()) 40 } 41 42 // Make sure the received data buffer is of appropriate size. 43 labelBytes := subvol.Size().Prod() * 8 44 if labelBytes != int64(len(data)) { 45 return fmt.Errorf("expected %d bytes for data %q label PUT but only received %d bytes", labelBytes, d.DataName(), len(data)) 46 } 47 48 r, err := imageblk.GetROI(v, roiname, subvol) 49 if err != nil { 50 return err 51 } 52 53 // Only do voxel-based mutations one at a time. This lets us remove handling for block-level concurrency. 54 d.voxelMu.Lock() 55 defer d.voxelMu.Unlock() 56 57 // Keep track of changing extents, labels and mark repo as dirty if changed. 58 var extentChanged bool 59 defer func() { 60 if extentChanged { 61 err := datastore.SaveDataByVersion(v, d) 62 if err != nil { 63 dvid.Infof("Error in trying to save repo on change: %v\n", err) 64 } 65 } 66 }() 67 68 // Track point extents 69 ctx := datastore.NewVersionedCtx(d, v) 70 extents := d.Extents() 71 if extents.AdjustPoints(subvol.StartPoint(), subvol.EndPoint()) { 72 extentChanged = true 73 if err := d.PostExtents(ctx, extents.MinPoint, extents.MaxPoint); err != nil { 74 return err 75 } 76 } 77 78 // extract buffer interface if it exists 79 var putbuffer storage.RequestBuffer 80 store, err := datastore.GetOrderedKeyValueDB(d) 81 if err != nil { 82 return fmt.Errorf("Data type imageblk had error initializing store: %v\n", err) 83 } 84 if req, ok := store.(storage.KeyValueRequester); ok { 85 putbuffer = req.NewBuffer(ctx) 86 } 87 88 // Iterate through index space for this data. 89 mutID := d.NewMutationID() 90 downresMut := downres.NewMutation(d, v, mutID) 91 92 wg := new(sync.WaitGroup) 93 94 blockCh := make(chan blockChange, 100) 95 svmap, err := getMapping(d, v) 96 if err != nil { 97 return fmt.Errorf("PutLabels couldn't get mapping for data %q, version %d: %v", d.DataName(), v, err) 98 } 99 go d.aggregateBlockChanges(v, svmap, blockCh) 100 101 blocks := 0 102 for it, err := subvol.NewIndexZYXIterator(d.BlockSize()); err == nil && it.Valid(); it.NextSpan() { 103 i0, i1, err := it.IndexSpan() 104 if err != nil { 105 close(blockCh) 106 return err 107 } 108 ptBeg := i0.Duplicate().(dvid.ChunkIndexer) 109 ptEnd := i1.Duplicate().(dvid.ChunkIndexer) 110 111 begX := ptBeg.Value(0) 112 endX := ptEnd.Value(0) 113 114 if extents.AdjustIndices(ptBeg, ptEnd) { 115 extentChanged = true 116 } 117 118 wg.Add(int(endX-begX) + 1) 119 c := dvid.ChunkPoint3d{begX, ptBeg.Value(1), ptBeg.Value(2)} 120 for x := begX; x <= endX; x++ { 121 c[0] = x 122 curIndex := dvid.IndexZYX(c) 123 124 // Don't PUT if this index is outside a specified ROI 125 if r != nil && r.Iter != nil && !r.Iter.InsideFast(curIndex) { 126 wg.Done() 127 continue 128 } 129 130 putOp := &putOperation{ 131 data: data, 132 subvol: subvol, 133 indexZYX: curIndex, 134 version: v, 135 mutate: mutate, 136 mutID: mutID, 137 downresMut: downresMut, 138 blockCh: blockCh, 139 } 140 server.CheckChunkThrottling() 141 go d.putChunk(putOp, wg, putbuffer) 142 blocks++ 143 } 144 } 145 wg.Wait() 146 close(blockCh) 147 148 // if a bufferable op, flush 149 if putbuffer != nil { 150 putbuffer.Flush() 151 } 152 153 return downresMut.Execute() 154 } 155 156 // Puts a chunk of data as part of a mapped operation. 157 // Only some multiple of the # of CPU cores can be used for chunk handling before 158 // it waits for chunk processing to abate via the buffered server.HandlerToken channel. 159 func (d *Data) putChunk(op *putOperation, wg *sync.WaitGroup, putbuffer storage.RequestBuffer) { 160 defer func() { 161 // After processing a chunk, return the token. 162 server.HandlerToken <- 1 163 164 // Notify the requestor that this chunk is done. 165 wg.Done() 166 }() 167 168 bcoord := op.indexZYX.ToIZYXString() 169 ctx := datastore.NewVersionedCtx(d, op.version) 170 171 // If we are mutating, get the previous label Block 172 var scale uint8 173 var oldBlock *labels.PositionedBlock 174 if op.mutate { 175 var err error 176 if oldBlock, err = d.getLabelPositionedBlock(ctx, scale, bcoord); err != nil { 177 dvid.Errorf("Unable to load previous block in %q, key %v: %v\n", d.DataName(), bcoord, err) 178 return 179 } 180 } 181 182 // Get the current label Block from the received label array 183 blockSize, ok := d.BlockSize().(dvid.Point3d) 184 if !ok { 185 dvid.Errorf("can't putChunk() on data %q with non-3d block size: %s", d.DataName(), d.BlockSize()) 186 return 187 } 188 curBlock, err := labels.SubvolumeToBlock(op.subvol, op.data, op.indexZYX, blockSize) 189 if err != nil { 190 dvid.Errorf("error creating compressed block from label array at %s", op.subvol) 191 return 192 } 193 go d.updateBlockMaxLabel(op.version, curBlock) 194 195 blockData, _ := curBlock.MarshalBinary() 196 serialization, err := dvid.SerializeData(blockData, d.Compression(), d.Checksum()) 197 if err != nil { 198 dvid.Errorf("Unable to serialize block in %q: %v\n", d.DataName(), err) 199 return 200 } 201 202 store, err := datastore.GetOrderedKeyValueDB(d) 203 if err != nil { 204 dvid.Errorf("Data type imageblk had error initializing store: %v\n", err) 205 return 206 } 207 208 callback := func(ready chan error) { 209 if ready != nil { 210 if resperr := <-ready; resperr != nil { 211 dvid.Errorf("Unable to PUT voxel data for block %v: %v\n", bcoord, resperr) 212 return 213 } 214 } 215 var event string 216 var delta interface{} 217 if oldBlock != nil && op.mutate { 218 event = labels.MutateBlockEvent 219 block := MutatedBlock{op.mutID, bcoord, &(oldBlock.Block), curBlock} 220 d.handleBlockMutate(op.version, op.blockCh, block) 221 delta = block 222 } else { 223 event = labels.IngestBlockEvent 224 block := IngestedBlock{op.mutID, bcoord, curBlock} 225 d.handleBlockIndexing(op.version, op.blockCh, block) 226 delta = block 227 } 228 if err := op.downresMut.BlockMutated(bcoord, curBlock); err != nil { 229 dvid.Errorf("data %q publishing downres: %v\n", d.DataName(), err) 230 } 231 evt := datastore.SyncEvent{d.DataUUID(), event} 232 msg := datastore.SyncMessage{event, op.version, delta} 233 if err := datastore.NotifySubscribers(evt, msg); err != nil { 234 dvid.Errorf("Unable to notify subscribers of event %s in %s\n", event, d.DataName()) 235 } 236 } 237 238 // put data -- use buffer if available 239 tk := NewBlockTKeyByCoord(op.scale, bcoord) 240 if putbuffer != nil { 241 ready := make(chan error, 1) 242 go callback(ready) 243 putbuffer.PutCallback(ctx, tk, serialization, ready) 244 } else { 245 if err := store.Put(ctx, tk, serialization); err != nil { 246 dvid.Errorf("Unable to PUT voxel data for block %s: %v\n", bcoord, err) 247 return 248 } 249 callback(nil) 250 } 251 } 252 253 // Writes a XY image into the blocks that intersect it. This function assumes the 254 // blocks have been allocated and if necessary, filled with old data. 255 func (d *Data) writeXYImage(v dvid.VersionID, vox *imageblk.Voxels, b storage.TKeyValues) (extentChanged bool, err error) { 256 257 // Setup concurrency in image -> block transfers. 258 var wg sync.WaitGroup 259 defer wg.Wait() 260 261 // Iterate through index space for this data using ZYX ordering. 262 blockSize := d.BlockSize() 263 var startingBlock int32 264 265 for it, err := vox.NewIndexIterator(blockSize); err == nil && it.Valid(); it.NextSpan() { 266 indexBeg, indexEnd, err := it.IndexSpan() 267 if err != nil { 268 return extentChanged, err 269 } 270 271 ptBeg := indexBeg.Duplicate().(dvid.ChunkIndexer) 272 ptEnd := indexEnd.Duplicate().(dvid.ChunkIndexer) 273 274 // Track point extents 275 if d.Extents().AdjustIndices(ptBeg, ptEnd) { 276 extentChanged = true 277 } 278 279 // Do image -> block transfers in concurrent goroutines. 280 begX := ptBeg.Value(0) 281 endX := ptEnd.Value(0) 282 283 server.CheckChunkThrottling() 284 wg.Add(1) 285 go func(blockNum int32) { 286 c := dvid.ChunkPoint3d{begX, ptBeg.Value(1), ptBeg.Value(2)} 287 for x := begX; x <= endX; x++ { 288 c[0] = x 289 curIndex := dvid.IndexZYX(c) 290 b[blockNum].K = NewBlockTKey(0, &curIndex) 291 292 // Write this slice data into the block. 293 vox.WriteBlock(&(b[blockNum]), blockSize) 294 blockNum++ 295 } 296 server.HandlerToken <- 1 297 wg.Done() 298 }(startingBlock) 299 300 startingBlock += (endX - begX + 1) 301 } 302 return 303 } 304 305 // KVWriteSize is the # of key-value pairs we will write as one atomic batch write. 306 const KVWriteSize = 500 307 308 // TODO -- Clean up all the writing and simplify now that we have block-aligned writes. 309 // writeBlocks ingests blocks of voxel data asynchronously using batch writes. 310 func (d *Data) writeBlocks(v dvid.VersionID, b storage.TKeyValues, wg1, wg2 *sync.WaitGroup) error { 311 batcher, err := datastore.GetKeyValueBatcher(d) 312 if err != nil { 313 return err 314 } 315 316 preCompress, postCompress := 0, 0 317 blockSize := d.BlockSize().(dvid.Point3d) 318 319 ctx := datastore.NewVersionedCtx(d, v) 320 evt := datastore.SyncEvent{d.DataUUID(), labels.IngestBlockEvent} 321 322 server.CheckChunkThrottling() 323 blockCh := make(chan blockChange, 100) 324 svmap, err := getMapping(d, v) 325 if err != nil { 326 return fmt.Errorf("writeBlocks couldn't get mapping for data %q, version %d: %v", d.DataName(), v, err) 327 } 328 go d.aggregateBlockChanges(v, svmap, blockCh) 329 go func() { 330 defer func() { 331 wg1.Done() 332 wg2.Done() 333 dvid.Debugf("Wrote voxel blocks. Before %s: %d bytes. After: %d bytes\n", d.Compression(), preCompress, postCompress) 334 close(blockCh) 335 server.HandlerToken <- 1 336 }() 337 338 mutID := d.NewMutationID() 339 batch := batcher.NewBatch(ctx) 340 for i, block := range b { 341 preCompress += len(block.V) 342 lblBlock, err := labels.MakeBlock(block.V, blockSize) 343 if err != nil { 344 dvid.Errorf("unable to compute dvid block compression in %q: %v\n", d.DataName(), err) 345 return 346 } 347 go d.updateBlockMaxLabel(v, lblBlock) 348 349 compressed, _ := lblBlock.MarshalBinary() 350 serialization, err := dvid.SerializeData(compressed, d.Compression(), d.Checksum()) 351 if err != nil { 352 dvid.Errorf("Unable to serialize block in %q: %v\n", d.DataName(), err) 353 return 354 } 355 postCompress += len(serialization) 356 batch.Put(block.K, serialization) 357 358 _, indexZYX, err := DecodeBlockTKey(block.K) 359 if err != nil { 360 dvid.Errorf("Unable to recover index from block key: %v\n", block.K) 361 return 362 } 363 364 block := IngestedBlock{mutID, indexZYX.ToIZYXString(), lblBlock} 365 d.handleBlockIndexing(v, blockCh, block) 366 367 msg := datastore.SyncMessage{labels.IngestBlockEvent, v, block} 368 if err := datastore.NotifySubscribers(evt, msg); err != nil { 369 dvid.Errorf("Unable to notify subscribers of ChangeBlockEvent in %s\n", d.DataName()) 370 return 371 } 372 373 // Check if we should commit 374 if i%KVWriteSize == KVWriteSize-1 { 375 if err := batch.Commit(); err != nil { 376 dvid.Errorf("Error on trying to write batch: %v\n", err) 377 return 378 } 379 batch = batcher.NewBatch(ctx) 380 } 381 } 382 if err := batch.Commit(); err != nil { 383 dvid.Errorf("Error on trying to write batch: %v\n", err) 384 return 385 } 386 }() 387 return nil 388 } 389 390 func (d *Data) blockChangesExtents(extents *dvid.Extents, bx, by, bz int32) bool { 391 blockSize := d.BlockSize().(dvid.Point3d) 392 start := dvid.Point3d{bx * blockSize[0], by * blockSize[1], bz * blockSize[2]} 393 end := dvid.Point3d{start[0] + blockSize[0] - 1, start[1] + blockSize[1] - 1, start[2] + blockSize[2] - 1} 394 return extents.AdjustPoints(start, end) 395 } 396 397 // storeBlocks reads blocks from io.ReadCloser and puts them in store, handling metadata bookkeeping 398 // unlike ingestBlocks function. 399 func (d *Data) storeBlocks(ctx *datastore.VersionedCtx, r io.ReadCloser, scale uint8, downscale bool, compression string, indexing bool) error { 400 if r == nil { 401 return fmt.Errorf("no data blocks POSTed") 402 } 403 404 if downscale && scale != 0 { 405 return fmt.Errorf("cannot downscale blocks of scale > 0") 406 } 407 408 switch compression { 409 case "", "blocks": 410 default: 411 return fmt.Errorf(`compression must be "blocks" (default) at this time`) 412 } 413 414 timedLog := dvid.NewTimeLog() 415 store, err := datastore.GetOrderedKeyValueDB(d) 416 if err != nil { 417 return fmt.Errorf("Data type labelmap had error initializing store: %v", err) 418 } 419 420 // Only do voxel-based mutations one at a time. This lets us remove handling for block-level concurrency. 421 d.voxelMu.Lock() 422 defer d.voxelMu.Unlock() 423 424 d.StartUpdate() 425 defer d.StopUpdate() 426 427 // extract buffer interface if it exists 428 var putbuffer storage.RequestBuffer 429 if req, ok := store.(storage.KeyValueRequester); ok { 430 putbuffer = req.NewBuffer(ctx) 431 } 432 433 mutID := d.NewMutationID() 434 var downresMut *downres.Mutation 435 if downscale { 436 downresMut = downres.NewMutation(d, ctx.VersionID(), mutID) 437 } 438 439 svmap, err := getMapping(d, ctx.VersionID()) 440 if err != nil { 441 return fmt.Errorf("ReceiveBlocks couldn't get mapping for data %q, version %d: %v", d.DataName(), ctx.VersionID(), err) 442 } 443 var blockCh chan blockChange 444 var putWG, processWG sync.WaitGroup 445 if indexing { 446 blockCh = make(chan blockChange, 100) 447 processWG.Add(1) 448 go func() { 449 d.aggregateBlockChanges(ctx.VersionID(), svmap, blockCh) 450 processWG.Done() 451 }() 452 } 453 454 callback := func(bcoord dvid.IZYXString, block *labels.Block, ready chan error) { 455 if ready != nil { 456 if resperr := <-ready; resperr != nil { 457 dvid.Errorf("Unable to PUT voxel data for block %v: %v\n", bcoord, resperr) 458 return 459 } 460 } 461 event := labels.IngestBlockEvent 462 ingestBlock := IngestedBlock{mutID, bcoord, block} 463 if scale == 0 { 464 if indexing { 465 d.handleBlockIndexing(ctx.VersionID(), blockCh, ingestBlock) 466 } 467 go d.updateBlockMaxLabel(ctx.VersionID(), ingestBlock.Data) 468 evt := datastore.SyncEvent{d.DataUUID(), event} 469 msg := datastore.SyncMessage{event, ctx.VersionID(), ingestBlock} 470 if err := datastore.NotifySubscribers(evt, msg); err != nil { 471 dvid.Errorf("Unable to notify subscribers of event %s in %s\n", event, d.DataName()) 472 } 473 if downscale { 474 if err := downresMut.BlockMutated(bcoord, block); err != nil { 475 dvid.Errorf("data %q publishing downres: %v\n", d.DataName(), err) 476 } 477 } 478 } 479 480 putWG.Done() 481 } 482 483 if d.Compression().Format() != dvid.Gzip { 484 return fmt.Errorf("labelmap %q cannot accept GZIP /blocks POST since it internally uses %s", d.DataName(), d.Compression().Format()) 485 } 486 var extentsChanged bool 487 extents, err := d.GetExtents(ctx) 488 if err != nil { 489 return err 490 } 491 var numBlocks int 492 for { 493 block, compressed, bx, by, bz, err := readStreamedBlock(r, scale) 494 if err == io.EOF { 495 break 496 } 497 if err != nil { 498 return err 499 } 500 bcoord := dvid.ChunkPoint3d{bx, by, bz}.ToIZYXString() 501 tk := NewBlockTKeyByCoord(scale, bcoord) 502 if scale == 0 { 503 if mod := d.blockChangesExtents(&extents, bx, by, bz); mod { 504 extentsChanged = true 505 } 506 go d.updateBlockMaxLabel(ctx.VersionID(), block) 507 } 508 serialization, err := dvid.SerializePrecompressedData(compressed, d.Compression(), d.Checksum()) 509 if err != nil { 510 return fmt.Errorf("can't serialize received block %s data: %v", bcoord, err) 511 } 512 putWG.Add(1) 513 if putbuffer != nil { 514 ready := make(chan error, 1) 515 go callback(bcoord, block, ready) 516 putbuffer.PutCallback(ctx, tk, serialization, ready) 517 } else { 518 if err := store.Put(ctx, tk, serialization); err != nil { 519 return fmt.Errorf("Unable to PUT voxel data for block %s: %v", bcoord, err) 520 } 521 go callback(bcoord, block, nil) 522 } 523 numBlocks++ 524 } 525 526 putWG.Wait() 527 if blockCh != nil { 528 close(blockCh) 529 } 530 processWG.Wait() 531 532 if extentsChanged { 533 if err := d.PostExtents(ctx, extents.StartPoint(), extents.EndPoint()); err != nil { 534 dvid.Criticalf("could not modify extents for labelmap %q: %v\n", d.DataName(), err) 535 } 536 } 537 538 // if a bufferable op, flush 539 if putbuffer != nil { 540 putbuffer.Flush() 541 } 542 if downscale { 543 if err := downresMut.Execute(); err != nil { 544 return err 545 } 546 } 547 timedLog.Infof("Received and stored %d blocks for labelmap %q", numBlocks, d.DataName()) 548 return nil 549 } 550 551 // Writes supervoxel blocks without worrying about overlap or computation of indices, syncs, maxlabel, or extents. 552 // Additional goroutines are not spawned so caller can set concurrency through parallel evocations. 553 func (d *Data) ingestBlocks(ctx *datastore.VersionedCtx, r io.ReadCloser, scale uint8) error { 554 if r == nil { 555 return fmt.Errorf("no data blocks POSTed") 556 } 557 558 timedLog := dvid.NewTimeLog() 559 store, err := datastore.GetKeyValueDB(d) 560 if err != nil { 561 return fmt.Errorf("Data type labelmap had error initializing store: %v", err) 562 } 563 564 if d.Compression().Format() != dvid.Gzip { 565 return fmt.Errorf("labelmap %q cannot accept GZIP /blocks POST since it internally uses %s", d.DataName(), d.Compression().Format()) 566 } 567 568 d.StartUpdate() 569 defer d.StopUpdate() 570 571 var numBlocks int 572 for { 573 _, compressed, bx, by, bz, err := readStreamedBlock(r, scale) 574 if err == io.EOF { 575 break 576 } 577 if err != nil { 578 return err 579 } 580 bcoord := dvid.ChunkPoint3d{bx, by, bz}.ToIZYXString() 581 tk := NewBlockTKeyByCoord(scale, bcoord) 582 serialization, err := dvid.SerializePrecompressedData(compressed, d.Compression(), d.Checksum()) 583 if err != nil { 584 return fmt.Errorf("can't serialize received block %s data: %v", bcoord, err) 585 } 586 if err := store.Put(ctx, tk, serialization); err != nil { 587 return fmt.Errorf("unable to PUT voxel data for block %s: %v", bcoord, err) 588 } 589 numBlocks++ 590 } 591 592 timedLog.Infof("Received and stored %d blocks for labelmap %q", numBlocks, d.DataName()) 593 return nil 594 }