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  }