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

     1  /*
     2  Package labelblk supports only label volumes.  See labelvol package for support
     3  of sparse labels.  The labelblk and labelvol datatypes typically are synced to each other.
     4  */
     5  package labelblk
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/md5"
    10  	"encoding/binary"
    11  	"encoding/gob"
    12  	"encoding/json"
    13  	"fmt"
    14  	"image"
    15  	"io"
    16  	"io/ioutil"
    17  	"math"
    18  	"net/http"
    19  	"strings"
    20  	"sync"
    21  
    22  	"compress/gzip"
    23  
    24  	"github.com/janelia-flyem/dvid/datastore"
    25  	"github.com/janelia-flyem/dvid/datatype/imageblk"
    26  	"github.com/janelia-flyem/dvid/dvid"
    27  	"github.com/janelia-flyem/dvid/server"
    28  	"github.com/janelia-flyem/dvid/storage"
    29  
    30  	lz4 "github.com/janelia-flyem/go/golz4-updated"
    31  )
    32  
    33  const (
    34  	Version  = "0.1"
    35  	RepoURL  = "github.com/janelia-flyem/dvid/datatype/labelblk"
    36  	TypeName = "labelblk"
    37  )
    38  
    39  const helpMessage = `
    40  API for label block data type (github.com/janelia-flyem/dvid/datatype/labelblk)
    41  ===============================================================================
    42  
    43  Note: UUIDs referenced below are strings that may either be a unique prefix of a
    44  hexadecimal UUID string (e.g., 3FA22) or a branch leaf specification that adds
    45  a colon (":") followed by the case-dependent branch name.  In the case of a
    46  branch leaf specification, the unique UUID prefix just identifies the repo of
    47  the branch, and the UUID referenced is really the leaf of the branch name.
    48  For example, if we have a DAG with root A -> B -> C where C is the current
    49  HEAD or leaf of the "master" (default) branch, then asking for "B:master" is
    50  the same as asking for "C".  If we add another version so A -> B -> C -> D, then
    51  references to "B:master" now return the data from "D".
    52  
    53  ------
    54  
    55  Denormalizations like sparse volumes are *not* performed for the "0" label, which is
    56  considered a special label useful for designating background.  This allows users to define
    57  sparse labeled structures in a large volume without requiring processing of entire volume.
    58  
    59  
    60  Command-line:
    61  
    62  $ dvid repo <UUID> new labelblk <data name> <settings...>
    63  
    64  	Adds newly named data of the 'type name' to repo with specified UUID.
    65  
    66  	Example (note anisotropic resolution specified instead of default 8 nm isotropic):
    67  
    68  	$ dvid repo 3f8c new labelblk superpixels VoxelSize=3.2,3.2,40.0
    69  
    70      Arguments:
    71  
    72      UUID           Hexadecimal string with enough characters to uniquely identify a version node.
    73      data name      Name of data to create, e.g., "superpixels"
    74      settings       Configuration settings in "key=value" format separated by spaces.
    75  
    76      Configuration Settings (case-insensitive keys)
    77  
    78      BlockSize      Size in pixels  (default: %s)
    79      VoxelSize      Resolution of voxels (default: 8.0, 8.0, 8.0)
    80      VoxelUnits     Resolution units (default: "nanometers")
    81  
    82  $ dvid node <UUID> <data name> load <offset> <image glob> <settings...>
    83  
    84      Initializes version node to a set of XY label images described by glob of filenames.
    85      The DVID server must have access to the named files.  Currently, XY images are required.
    86  
    87      Example: 
    88  
    89      $ dvid node 3f8c superpixels load 0,0,100 "data/*.png" proc=noindex
    90  
    91      Arguments:
    92  
    93      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
    94      data name     Name of data to add.
    95      offset        3d coordinate in the format "x,y,z".  Gives coordinate of top upper left voxel.
    96      image glob    Filenames of label images, preferably in quotes, e.g., "foo-xy-*.png"
    97  
    98      Configuration Settings (case-insensitive keys)
    99  
   100      Proc          "noindex": prevents creation of denormalized data to speed up obtaining sparse 
   101      				 volumes and size query responses using the loaded labels.
   102  
   103  $ dvid node <UUID> <data name> composite <uint8 data name> <new rgba8 data name>
   104  
   105      Creates a RGBA8 image where the RGB is a hash of the labels and the A is the
   106      grayscale intensity.
   107  
   108      Example: 
   109  
   110      $ dvid node 3f8c bodies composite grayscale bodyview
   111  
   112      Arguments:
   113  
   114      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   115      data name     Name of data to add.
   116  	
   117  	
   118      ------------------
   119  
   120  HTTP API (Level 2 REST):
   121  
   122  GET  <api URL>/node/<UUID>/<data name>/help
   123  
   124  	Returns data-specific help message.
   125  
   126  
   127  GET  <api URL>/node/<UUID>/<data name>/info
   128  POST <api URL>/node/<UUID>/<data name>/info
   129  
   130      Retrieves or puts DVID-specific data properties for these voxels.
   131  
   132      Example: 
   133  
   134      GET <api URL>/node/3f8c/segmentation/info
   135  
   136      Returns JSON with configuration settings that include location in DVID space and
   137      min/max block indices.
   138  
   139      Arguments:
   140  
   141      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   142      data name     Name of voxels data.
   143  
   144  POST  <api URL>/node/<UUID>/<data name>/resolution
   145    
   146    	Sets the resolution for the image volume. 
   147    
   148    	Extents should be in JSON in the following format:
   149    	[8,8,8]
   150  
   151  POST <api URL>/node/<UUID>/<data name>/sync?<options>
   152  
   153      Appends data instances with which this labelblk is synced.  Expects JSON to be POSTed
   154      with the following format:
   155  
   156      { "sync": "bodies" }
   157  
   158  	To delete syncs, pass an empty string of names with query string "replace=true":
   159  
   160  	{ "sync": "" }
   161  
   162      The "sync" property should be followed by a comma-delimited list of data instances that MUST
   163      already exist.  Currently, syncs should be created before any annotations are pushed to
   164      the server.  If annotations already exist, these are currently not synced.
   165  
   166      The labelblk data type accepts syncs to labelvol and other labelblk data instances.  Syncs to
   167  	labelblk instances automatically create a 2x downsampling compared to the synced labelblk,
   168  	for use in multiscale.
   169  
   170      GET Query-string Options:
   171  
   172      replace    Set to "true" if you want passed syncs to replace and not be appended to current syncs.
   173  			   Default operation is false.
   174  
   175  
   176  GET  <api URL>/node/<UUID>/<data name>/metadata
   177  
   178  	Retrieves a JSON schema (application/vnd.dvid-nd-data+json) that describes the layout
   179  	of bytes returned for n-d images.
   180  
   181  
   182  GET  <api URL>/node/<UUID>/<data name>/isotropic/<dims>/<size>/<offset>[/<format>][?queryopts]
   183  
   184      Retrieves either 2d images (PNG by default) or 3d binary data, depending on the dims parameter.  
   185      The 3d binary data response has "Content-type" set to "application/octet-stream" and is an array of 
   186      voxel values in ZYX order (X iterates most rapidly).
   187  
   188      Example: 
   189  
   190      GET <api URL>/node/3f8c/segmentation/isotropic/0_1/512_256/0_0_100/jpg:80
   191  
   192      Returns an isotropic XY slice (0th and 1st dimensions) with width (x) of 512 voxels and
   193      height (y) of 256 voxels with offset (0,0,100) in JPG format with quality 80.
   194      Additional processing is applied based on voxel resolutions to make sure the retrieved image 
   195      has isotropic pixels.  For example, if an XZ image is requested and the image volume has 
   196      X resolution 3 nm and Z resolution 40 nm, the returned image's height will be magnified 40/3
   197      relative to the raw data.
   198      The example offset assumes the "grayscale" data in version node "3f8c" is 3d.
   199      The "Content-type" of the HTTP response should agree with the requested format.
   200      For example, returned PNGs will have "Content-type" of "image/png", and returned
   201      nD data will be "application/octet-stream".
   202  
   203      Arguments:
   204  
   205      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   206      data name     Name of data to add.
   207      dims          The axes of data extraction in form "i_j_k,..."  Example: "0_2" can be XZ.
   208                      Slice strings ("xy", "xz", or "yz") are also accepted.
   209      size          Size in voxels along each dimension specified in <dims>.
   210      offset        Gives coordinate of first voxel using dimensionality of data.
   211      format        Valid formats depend on the dimensionality of the request and formats
   212                      available in server implementation.
   213                    2D: "png", "jpg" (default: "png")
   214                      jpg allows lossy quality setting, e.g., "jpg:80"
   215                    nD: uses default "octet-stream".
   216  
   217      Query-string Options:
   218  
   219      roi       	  Name of roi data instance used to mask the requested data.
   220      compression   Allows retrieval or submission of 3d data in "lz4" and "gzip"
   221                      compressed format.  The 2d data will ignore this and use
   222                      the image-based codec.
   223      throttle      Only works for 3d data requests.  If "true", makes sure only N compute-intense operation 
   224      				(all API calls that can be throttled) are handled.  If the server can't initiate the API 
   225      				call right away, a 503 (Service Unavailable) status code is returned.
   226  
   227  
   228  GET  <api URL>/node/<UUID>/<data name>/raw/<dims>/<size>/<offset>[/<format>][?queryopts]
   229  
   230      Retrieves either 2d images (PNG by default) or 3d binary data, depending on the dims parameter.  
   231      The 3d binary data response has "Content-type" set to "application/octet-stream" and is an array of 
   232      voxel values in ZYX order (X iterates most rapidly).
   233  
   234      Example: 
   235  
   236      GET <api URL>/node/3f8c/segmentation/raw/0_1/512_256/0_0_100/jpg:80
   237  
   238      Returns a raw XY slice (0th and 1st dimensions) with width (x) of 512 voxels and
   239      height (y) of 256 voxels with offset (0,0,100) in JPG format with quality 80.
   240      By "raw", we mean that no additional processing is applied based on voxel
   241      resolutions to make sure the retrieved image has isotropic pixels.
   242      The example offset assumes the "grayscale" data in version node "3f8c" is 3d.
   243      The "Content-type" of the HTTP response should agree with the requested format.
   244      For example, returned PNGs will have "Content-type" of "image/png", and returned
   245      nD data will be "application/octet-stream". 
   246  
   247      Arguments:
   248  
   249      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   250      data name     Name of data to add.
   251      dims          The axes of data extraction in form "i_j_k,..."  
   252                      Slice strings ("xy", "xz", or "yz") are also accepted.
   253                      Example: "0_2" is XZ, and "0_1_2" is a 3d subvolume.
   254      size          Size in voxels along each dimension specified in <dims>.
   255      offset        Gives coordinate of first voxel using dimensionality of data.
   256      format        Valid formats depend on the dimensionality of the request and formats
   257                      available in server implementation.
   258                    2D: "png", "jpg" (default: "png")
   259                      jpg allows lossy quality setting, e.g., "jpg:80"
   260                    nD: uses default "octet-stream".
   261  
   262      Query-string Options:
   263  
   264      roi           Name of roi data instance used to mask the requested data.
   265      compression   Allows retrieval or submission of 3d data in "lz4","gzip", "google"
   266  		            (neuroglancer compression format), "googlegzip" (google + gzip)
   267                      compressed format.  The 2d data will ignore this and use
   268                      the image-based codec.
   269      throttle      Only works for 3d data requests.  If "true", makes sure only N compute-intense operation 
   270      				(all API calls that can be throttled) are handled.  If the server can't initiate the API 
   271      				call right away, a 503 (Service Unavailable) status code is returned.
   272  
   273  
   274  POST <api URL>/node/<UUID>/<data name>/raw/0_1_2/<size>/<offset>[?queryopts]
   275  
   276      Puts block-aligned voxel data using the block sizes defined for  this data instance.  
   277      For example, if the BlockSize = 32, offset and size must be multiples of 32.
   278  
   279      Example: 
   280  
   281      POST <api URL>/node/3f8c/segmentation/raw/0_1_2/512_256_128/0_0_32
   282  
   283      Arguments:
   284  
   285      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   286      data name     Name of data to add.
   287      size          Size in voxels along each dimension specified in <dims>.
   288      offset        Gives coordinate of first voxel using dimensionality of data.
   289  
   290      Query-string Options:
   291  
   292      roi           Name of roi data instance used to mask the requested data.
   293      mutate        Default "false" corresponds to ingestion, i.e., the first write of the given block.
   294                      Use "true" to indicate the POST is a mutation of prior data, which allows any
   295                      synced data instance to cleanup prior denormalizations.  If "mutate=true", the
   296                      POST operations will be slower due to a required GET to retrieve past data.
   297      compression   Allows retrieval or submission of 3d data in "lz4" and "gzip"
   298                      compressed format.
   299      throttle      If "true", makes sure only N compute-intense operation (all API calls that can be throttled) 
   300                      are handled.  If the server can't initiate the API call right away, a 503 (Service Unavailable) 
   301                      status code is returned.
   302  
   303  GET  <api URL>/node/<UUID>/<data name>/pseudocolor/<dims>/<size>/<offset>[?queryopts]
   304  
   305      Retrieves label data as pseudocolored 2D PNG color images where each label hashed to a different RGB.
   306  
   307      Example: 
   308  
   309      GET <api URL>/node/3f8c/segmentation/pseudocolor/0_1/512_256/0_0_100
   310  
   311      Returns an XY slice (0th and 1st dimensions) with width (x) of 512 voxels and
   312      height (y) of 256 voxels with offset (0,0,100) in PNG format.
   313  
   314      Arguments:
   315  
   316      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   317      data name     Name of data to add.
   318      dims          The axes of data extraction.  Example: "0_2" can be XZ.
   319                      Slice strings ("xy", "xz", or "yz") are also accepted.
   320      size          Size in voxels along each dimension specified in <dims>.
   321      offset        Gives coordinate of first voxel using dimensionality of data.
   322  
   323      Query-string Options:
   324  
   325      roi       	  Name of roi data instance used to mask the requested data.
   326      compression   Allows retrieval or submission of 3d data in "lz4" and "gzip"
   327                      compressed format.
   328      throttle      If "true", makes sure only N compute-intense operation (all API calls that can be throttled) 
   329                      are handled.  If the server can't initiate the API call right away, a 503 (Service Unavailable) 
   330                      status code is returned.
   331  
   332  GET <api URL>/node/<UUID>/<data name>/label/<coord>
   333  
   334  	Returns JSON for the label at the given coordinate:
   335  	{ "Label": 23 }
   336  	
   337      Arguments:
   338      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   339      data name     Name of label data.
   340      coord     	  Coordinate of voxel with underscore as separator, e.g., 10_20_30
   341  
   342  GET <api URL>/node/<UUID>/<data name>/labels[?queryopts]
   343  
   344  	Returns JSON for the labels at a list of coordinates.  Expects JSON in GET body:
   345  
   346  	[ [x0, y0, z0], [x1, y1, z1], ...]
   347  
   348  	Returns for each POSTed coordinate the corresponding label:
   349  
   350  	[ 23, 911, ...]
   351  	
   352      Arguments:
   353      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   354      data name     Name of label data.
   355  
   356      Query-string Options:
   357  
   358      hash          MD5 hash of request body content in hexidecimal string format.
   359  
   360  GET  <api URL>/node/<UUID>/<data name>/blocks/<size>/<offset>[?queryopts]
   361  
   362      Retrieves blocks corresponding to the extents specified by the size and offset.  The
   363      subvolume request must be block aligned.  This is the most server-efficient way of
   364      retrieving labelblk data, where data read from the underlying storage engine
   365      is written directly to the HTTP connection.
   366  
   367      Example: 
   368  
   369      GET <api URL>/node/3f8c/segmentation/blocks/64_64_64/0_0_0
   370  
   371  	If block size is 32x32x32, this call retrieves up to 8 blocks where the first potential
   372  	block is at 0, 0, 0.  The returned byte stream has a list of blocks with a leading block 
   373  	coordinate (3 x int32) plus int32 giving the # of bytes in this block, and  then the 
   374  	bytes for the value.  If blocks are unset within the span, they will not appear in the stream,
   375  	so the returned data will be equal to or less than spanX blocks worth of data.  
   376  
   377      The returned data format has the following format where int32 is in little endian and the bytes of
   378      block data have been compressed in LZ4 format.
   379  
   380          int32  Block 1 coordinate X (Note that this may not be starting block coordinate if it is unset.)
   381          int32  Block 1 coordinate Y
   382          int32  Block 1 coordinate Z
   383          int32  # bytes for first block (N1)
   384          byte0  Block N1 serialization using chosen compression format (see "compression" option below)
   385          byte1
   386          ...
   387          byteN1
   388  
   389          int32  Block 2 coordinate X
   390          int32  Block 2 coordinate Y
   391          int32  Block 2 coordinate Z
   392          int32  # bytes for second block (N2)
   393          byte0  Block N2 serialization using chosen compression format (see "compression" option below)
   394          byte1
   395          ...
   396          byteN2
   397  
   398          ...
   399  
   400      If no data is available for given block span, nothing is returned.
   401  
   402      Arguments:
   403  
   404      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   405      data name     Name of data to add.
   406      size          Size in voxels along each dimension specified in <dims>.
   407      offset        Gives coordinate of first voxel using dimensionality of data.
   408  
   409      Query-string Options:
   410  
   411      compression   Allows retrieval of block data in "lz4" (default) or "uncompressed".
   412      throttle      If "true", makes sure only N compute-intense operation (all API calls that can be 
   413                    throttled) are handled.  If the server can't initiate the API call right away, a 503 
   414                    (Service Unavailable) status code is returned.
   415  `
   416  
   417  var (
   418  	dtype        Type
   419  	encodeFormat dvid.DataValues
   420  
   421  	zeroLabelBytes = make([]byte, 8, 8)
   422  
   423  	DefaultBlockSize int32   = imageblk.DefaultBlockSize
   424  	DefaultRes       float32 = imageblk.DefaultRes
   425  	DefaultUnits             = imageblk.DefaultUnits
   426  )
   427  
   428  func init() {
   429  	encodeFormat = dvid.DataValues{
   430  		{
   431  			T:     dvid.T_uint64,
   432  			Label: TypeName,
   433  		},
   434  	}
   435  	interpolable := false
   436  	dtype = Type{imageblk.NewType(encodeFormat, interpolable)}
   437  	dtype.Type.Name = TypeName
   438  	dtype.Type.URL = RepoURL
   439  	dtype.Type.Version = Version
   440  
   441  	// See doc for package on why channels are segregated instead of interleaved.
   442  	// Data types must be registered with the datastore to be used.
   443  	datastore.Register(&dtype)
   444  
   445  	// Need to register types that will be used to fulfill interfaces.
   446  	gob.Register(&Type{})
   447  	gob.Register(&Data{})
   448  }
   449  
   450  // ZeroBytes returns a slice of bytes that represents the zero label.
   451  func ZeroBytes() []byte {
   452  	return zeroLabelBytes
   453  }
   454  
   455  func EncodeFormat() dvid.DataValues {
   456  	return encodeFormat
   457  }
   458  
   459  // --- Labels64 Datatype -----
   460  
   461  // Type uses imageblk data type by composition.
   462  type Type struct {
   463  	imageblk.Type
   464  }
   465  
   466  // --- TypeService interface ---
   467  
   468  func (dtype *Type) NewDataService(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (datastore.DataService, error) {
   469  	return NewData(uuid, id, name, c)
   470  }
   471  
   472  func (dtype *Type) Help() string {
   473  	return helpMessage
   474  }
   475  
   476  // -------
   477  
   478  // GetByDataUUID returns a pointer to labelblk data given a data UUID.
   479  func GetByDataUUID(dataUUID dvid.UUID) (*Data, error) {
   480  	source, err := datastore.GetDataByDataUUID(dataUUID)
   481  	if err != nil {
   482  		return nil, err
   483  	}
   484  	data, ok := source.(*Data)
   485  	if !ok {
   486  		return nil, fmt.Errorf("Instance '%s' is not a labelblk datatype!", source.DataName())
   487  	}
   488  	return data, nil
   489  }
   490  
   491  // GetByUUIDName returns a pointer to labelblk data given a UUID and data name.
   492  func GetByUUIDName(uuid dvid.UUID, name dvid.InstanceName) (*Data, error) {
   493  	source, err := datastore.GetDataByUUIDName(uuid, name)
   494  	if err != nil {
   495  		return nil, err
   496  	}
   497  	data, ok := source.(*Data)
   498  	if !ok {
   499  		return nil, fmt.Errorf("Instance '%s' is not a labelblk datatype!", name)
   500  	}
   501  	return data, nil
   502  }
   503  
   504  // GetByVersionName returns a pointer to labelblk data given a version and data name.
   505  func GetByVersionName(v dvid.VersionID, name dvid.InstanceName) (*Data, error) {
   506  	source, err := datastore.GetDataByVersionName(v, name)
   507  	if err != nil {
   508  		return nil, err
   509  	}
   510  	data, ok := source.(*Data)
   511  	if !ok {
   512  		return nil, fmt.Errorf("Instance '%s' is not a labelblk datatype!", name)
   513  	}
   514  	return data, nil
   515  }
   516  
   517  // -------  ExtData interface implementation -------------
   518  
   519  // Labels are voxels that have uint64 labels.
   520  type Labels struct {
   521  	*imageblk.Voxels
   522  }
   523  
   524  func (l *Labels) String() string {
   525  	return fmt.Sprintf("Labels of size %s @ offset %s", l.Size(), l.StartPoint())
   526  }
   527  
   528  func (l *Labels) Interpolable() bool {
   529  	return false
   530  }
   531  
   532  // Data of labelblk type is an extended form of imageblk Data
   533  type Data struct {
   534  	*imageblk.Data
   535  	datastore.Updater
   536  
   537  	// unpersisted data: channels for mutations and downres caching.
   538  	syncCh   chan datastore.SyncMessage
   539  	syncDone chan *sync.WaitGroup
   540  
   541  	procCh    [numBlockHandlers]chan procMsg // channels into block processors.
   542  	vcache    map[dvid.VersionID]blockCache
   543  	vcache_mu sync.RWMutex
   544  }
   545  
   546  func (d *Data) Equals(d2 *Data) bool {
   547  	if !d.Data.Equals(d2.Data) {
   548  		return false
   549  	}
   550  	return true
   551  }
   552  
   553  // CopyPropertiesFrom copies the data instance-specific properties from a given
   554  // data instance into the receiver's properties.  Fulfills the datastore.PropertyCopier interface.
   555  func (d *Data) CopyPropertiesFrom(src datastore.DataService, fs storage.FilterSpec) error {
   556  	d2, ok := src.(*Data)
   557  	if !ok {
   558  		return fmt.Errorf("unable to copy properties from non-labelblk data %q", src.DataName())
   559  	}
   560  	return d.Data.CopyPropertiesFrom(d2.Data, fs)
   561  }
   562  
   563  // NewData returns a pointer to labelblk data.
   564  func NewData(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (*Data, error) {
   565  	imgblkData, err := dtype.Type.NewData(uuid, id, name, c)
   566  	if err != nil {
   567  		return nil, err
   568  	}
   569  
   570  	data := &Data{
   571  		Data: imgblkData,
   572  	}
   573  
   574  	return data, nil
   575  }
   576  
   577  func (d *Data) MarshalJSON() ([]byte, error) {
   578  	return json.Marshal(struct {
   579  		Base     *datastore.Data
   580  		Extended imageblk.Properties
   581  	}{
   582  		d.Data.Data,
   583  		d.Data.Properties,
   584  	})
   585  }
   586  
   587  func (d *Data) GobDecode(b []byte) error {
   588  	buf := bytes.NewBuffer(b)
   589  	dec := gob.NewDecoder(buf)
   590  	if err := dec.Decode(&(d.Data)); err != nil {
   591  		return err
   592  	}
   593  	return nil
   594  }
   595  
   596  func (d *Data) GobEncode() ([]byte, error) {
   597  	var buf bytes.Buffer
   598  	enc := gob.NewEncoder(&buf)
   599  	if err := enc.Encode(d.Data); err != nil {
   600  		return nil, err
   601  	}
   602  	return buf.Bytes(), nil
   603  }
   604  
   605  // --- imageblk.IntData interface -------------
   606  
   607  func (d *Data) BlockSize() dvid.Point {
   608  	return d.Properties.BlockSize
   609  }
   610  
   611  func (d *Data) Extents() *dvid.Extents {
   612  	return &(d.Properties.Extents)
   613  }
   614  
   615  // NewLabels returns labelblk Labels, a representation of externally usable subvolume
   616  // or slice data, given some geometry and optional image data.
   617  // If img is passed in, the function will initialize Voxels with data from the image.
   618  // Otherwise, it will allocate a zero buffer of appropriate size.
   619  func (d *Data) NewLabels(geom dvid.Geometry, img interface{}) (*Labels, error) {
   620  	bytesPerVoxel := d.Properties.Values.BytesPerElement()
   621  	stride := geom.Size().Value(0) * bytesPerVoxel
   622  	var data []byte
   623  
   624  	if img == nil {
   625  		numVoxels := geom.NumVoxels()
   626  		if numVoxels <= 0 {
   627  			return nil, fmt.Errorf("Illegal geometry requested: %s", geom)
   628  		}
   629  		requestSize := int64(bytesPerVoxel) * numVoxels
   630  		if requestSize > server.MaxDataRequest {
   631  			return nil, fmt.Errorf("Requested payload (%d bytes) exceeds this DVID server's set limit (%d)",
   632  				requestSize, server.MaxDataRequest)
   633  		}
   634  		data = make([]byte, requestSize)
   635  	} else {
   636  		switch t := img.(type) {
   637  		case image.Image:
   638  			var inputBytesPerVoxel, actualStride int32
   639  			var err error
   640  			data, inputBytesPerVoxel, actualStride, err = dvid.ImageData(t)
   641  			if err != nil {
   642  				return nil, err
   643  			}
   644  			if actualStride != stride {
   645  				data, err = d.convertTo64bit(geom, data, int(inputBytesPerVoxel), int(actualStride))
   646  				if err != nil {
   647  					return nil, err
   648  				}
   649  			}
   650  		case []byte:
   651  			data = t
   652  			actualLen := int64(len(data))
   653  			expectedLen := int64(bytesPerVoxel) * geom.NumVoxels()
   654  			if actualLen != expectedLen {
   655  				return nil, fmt.Errorf("labels data was %d bytes, expected %d bytes for %s",
   656  					actualLen, expectedLen, geom)
   657  			}
   658  		default:
   659  			return nil, fmt.Errorf("unexpected image type given to NewVoxels(): %T", t)
   660  		}
   661  	}
   662  
   663  	labels := &Labels{
   664  		imageblk.NewVoxels(geom, d.Properties.Values, data, stride),
   665  	}
   666  	return labels, nil
   667  }
   668  
   669  // Convert raw image data into a 2d array of 64-bit labels
   670  func (d *Data) convertTo64bit(geom dvid.Geometry, data []uint8, bytesPerVoxel, stride int) ([]byte, error) {
   671  	nx := int(geom.Size().Value(0))
   672  	ny := int(geom.Size().Value(1))
   673  	numBytes := nx * ny * 8
   674  	data64 := make([]byte, numBytes, numBytes)
   675  
   676  	var byteOrder binary.ByteOrder
   677  	if geom.DataShape().ShapeDimensions() == 2 {
   678  		byteOrder = binary.BigEndian // This is the default for PNG
   679  	} else {
   680  		byteOrder = binary.LittleEndian
   681  	}
   682  
   683  	switch bytesPerVoxel {
   684  	case 1:
   685  		dstI := 0
   686  		for y := 0; y < ny; y++ {
   687  			srcI := y * stride
   688  			for x := 0; x < nx; x++ {
   689  				binary.LittleEndian.PutUint64(data64[dstI:dstI+8], uint64(data[srcI]))
   690  				srcI++
   691  				dstI += 8
   692  			}
   693  		}
   694  	case 2:
   695  		dstI := 0
   696  		for y := 0; y < ny; y++ {
   697  			srcI := y * stride
   698  			for x := 0; x < nx; x++ {
   699  				value := byteOrder.Uint16(data[srcI : srcI+2])
   700  				binary.LittleEndian.PutUint64(data64[dstI:dstI+8], uint64(value))
   701  				srcI += 2
   702  				dstI += 8
   703  			}
   704  		}
   705  	case 4:
   706  		dstI := 0
   707  		for y := 0; y < ny; y++ {
   708  			srcI := y * stride
   709  			for x := 0; x < nx; x++ {
   710  				value := byteOrder.Uint32(data[srcI : srcI+4])
   711  				binary.LittleEndian.PutUint64(data64[dstI:dstI+8], uint64(value))
   712  				srcI += 4
   713  				dstI += 8
   714  			}
   715  		}
   716  	case 8:
   717  		dstI := 0
   718  		for y := 0; y < ny; y++ {
   719  			srcI := y * stride
   720  			for x := 0; x < nx; x++ {
   721  				value := byteOrder.Uint64(data[srcI : srcI+8])
   722  				binary.LittleEndian.PutUint64(data64[dstI:dstI+8], uint64(value))
   723  				srcI += 8
   724  				dstI += 8
   725  			}
   726  		}
   727  	default:
   728  		return nil, fmt.Errorf("could not convert to 64-bit label given %d bytes/voxel", bytesPerVoxel)
   729  	}
   730  	return data64, nil
   731  }
   732  
   733  func sendBlockLZ4(w http.ResponseWriter, x, y, z int32, v []byte, compression string) error {
   734  	// Check internal format and see if it's valid with compression choice.
   735  	format, checksum := dvid.DecodeSerializationFormat(dvid.SerializationFormat(v[0]))
   736  	if (compression == "lz4" || compression == "") && format != dvid.LZ4 {
   737  		return fmt.Errorf("Expected internal block data to be LZ4, was %s instead.", format)
   738  	}
   739  
   740  	// Send block coordinate and size of data.
   741  	if err := binary.Write(w, binary.LittleEndian, x); err != nil {
   742  		return err
   743  	}
   744  	if err := binary.Write(w, binary.LittleEndian, y); err != nil {
   745  		return err
   746  	}
   747  	if err := binary.Write(w, binary.LittleEndian, z); err != nil {
   748  		return err
   749  	}
   750  
   751  	start := 5
   752  	if checksum == dvid.CRC32 {
   753  		start += 4
   754  	}
   755  
   756  	// Do any adjustment of sent data based on compression request
   757  	var data []byte
   758  	if compression == "uncompressed" {
   759  		var err error
   760  		data, _, err = dvid.DeserializeData(v, true)
   761  		if err != nil {
   762  			return err
   763  		}
   764  	} else {
   765  		data = v[start:]
   766  	}
   767  	n := len(data)
   768  	if err := binary.Write(w, binary.LittleEndian, int32(n)); err != nil {
   769  		return err
   770  	}
   771  
   772  	// Send data itself, skipping the first byte for internal format and next 4 for uncompressed length.
   773  	if written, err := w.Write(data); err != nil || written != n {
   774  		if err != nil {
   775  			return err
   776  		}
   777  		return fmt.Errorf("could not write %d bytes of value: only %d bytes written", n, written)
   778  	}
   779  	return nil
   780  }
   781  
   782  // SendBlocks writes all blocks within the given subvolume to the http.ResponseWriter.
   783  func (d *Data) SendBlocks(ctx *datastore.VersionedCtx, w http.ResponseWriter, subvol *dvid.Subvolume, compression string) error {
   784  	w.Header().Set("Content-type", "application/octet-stream")
   785  
   786  	if compression != "uncompressed" && compression != "lz4" && compression != "" {
   787  		return fmt.Errorf("don't understand 'compression' query string value: %s", compression)
   788  	}
   789  
   790  	// convert x,y,z coordinates to block coordinates
   791  	blocksize := subvol.Size().Div(d.BlockSize())
   792  	blockoffset := subvol.StartPoint().Div(d.BlockSize())
   793  
   794  	timedLog := dvid.NewTimeLog()
   795  	defer timedLog.Infof("SendBlocks %s, span x %d, span y %d, span z %d", blockoffset, blocksize.Value(0), blocksize.Value(1), blocksize.Value(2))
   796  
   797  	store, err := datastore.GetOrderedKeyValueDB(d)
   798  	if err != nil {
   799  		return fmt.Errorf("Data type labelblk had error initializing store: %v\n", err)
   800  	}
   801  
   802  	// if only one block is requested, avoid the range query
   803  	if blocksize.Value(0) == int32(1) && blocksize.Value(1) == int32(1) && blocksize.Value(2) == int32(1) {
   804  		indexBeg := dvid.IndexZYX(dvid.ChunkPoint3d{blockoffset.Value(0), blockoffset.Value(1), blockoffset.Value(2)})
   805  		keyBeg := NewTKey(&indexBeg)
   806  
   807  		value, err := store.Get(ctx, keyBeg)
   808  		if err != nil {
   809  			return err
   810  		}
   811  		if len(value) > 0 {
   812  			return sendBlockLZ4(w, blockoffset.Value(0), blockoffset.Value(1), blockoffset.Value(2), value, compression)
   813  		}
   814  		return nil
   815  	}
   816  
   817  	// only do one request at a time, although each request can start many goroutines.
   818  	if subvol.NumVoxels() > 256*256*256 {
   819  		server.LargeMutationMutex.Lock()
   820  		defer server.LargeMutationMutex.Unlock()
   821  	}
   822  
   823  	okv := store.(storage.BufferableOps)
   824  	// extract buffer interface
   825  	req, hasbuffer := okv.(storage.KeyValueRequester)
   826  	if hasbuffer {
   827  		okv = req.NewBuffer(ctx)
   828  	}
   829  
   830  	for ziter := int32(0); ziter < blocksize.Value(2); ziter++ {
   831  		for yiter := int32(0); yiter < blocksize.Value(1); yiter++ {
   832  			beginPoint := dvid.ChunkPoint3d{blockoffset.Value(0), blockoffset.Value(1) + yiter, blockoffset.Value(2) + ziter}
   833  			endPoint := dvid.ChunkPoint3d{blockoffset.Value(0) + blocksize.Value(0) - 1, blockoffset.Value(1) + yiter, blockoffset.Value(2) + ziter}
   834  
   835  			indexBeg := dvid.IndexZYX(beginPoint)
   836  			sx, sy, sz := indexBeg.Unpack()
   837  			begTKey := NewTKey(&indexBeg)
   838  			indexEnd := dvid.IndexZYX(endPoint)
   839  			endTKey := NewTKey(&indexEnd)
   840  
   841  			// Send the entire range of key-value pairs to chunk processor
   842  			err = okv.ProcessRange(ctx, begTKey, endTKey, &storage.ChunkOp{}, func(c *storage.Chunk) error {
   843  				if c == nil || c.TKeyValue == nil {
   844  					return nil
   845  				}
   846  				kv := c.TKeyValue
   847  				if kv.V == nil {
   848  					return nil
   849  				}
   850  
   851  				// Determine which block this is.
   852  				indexZYX, err := DecodeTKey(kv.K)
   853  				if err != nil {
   854  					return err
   855  				}
   856  				x, y, z := indexZYX.Unpack()
   857  				if z != sz || y != sy || x < sx || x >= sx+int32(blocksize.Value(0)) {
   858  					return nil
   859  				}
   860  				if err := sendBlockLZ4(w, x, y, z, kv.V, compression); err != nil {
   861  					return err
   862  				}
   863  				return nil
   864  			})
   865  
   866  			if err != nil {
   867  				return fmt.Errorf("Unable to GET data %s: %v", ctx, err)
   868  			}
   869  		}
   870  	}
   871  
   872  	if hasbuffer {
   873  		// submit the entire buffer to the DB
   874  		err = okv.(storage.RequestBuffer).Flush()
   875  
   876  		if err != nil {
   877  			return fmt.Errorf("Unable to GET data %s: %v", ctx, err)
   878  
   879  		}
   880  	}
   881  
   882  	return err
   883  }
   884  
   885  // --- datastore.DataService interface ---------
   886  
   887  // PushData pushes labelblk data to a remote DVID.
   888  func (d *Data) PushData(p *datastore.PushSession) error {
   889  	// Delegate to imageblk's implementation.
   890  	return d.Data.PushData(p)
   891  }
   892  
   893  // DoRPC acts as a switchboard for RPC commands.
   894  func (d *Data) DoRPC(req datastore.Request, reply *datastore.Response) error {
   895  	switch req.TypeCommand() {
   896  	case "load":
   897  		if len(req.Command) < 5 {
   898  			return fmt.Errorf("Poorly formatted load command.  See command-line help.")
   899  		}
   900  		// Parse the request
   901  		var uuidStr, dataName, cmdStr, offsetStr string
   902  		filenames, err := req.FilenameArgs(1, &uuidStr, &dataName, &cmdStr, &offsetStr)
   903  		if err != nil {
   904  			return err
   905  		}
   906  		if len(filenames) == 0 {
   907  			return fmt.Errorf("Need to include at least one file to add: %s", req)
   908  		}
   909  
   910  		// Get offset
   911  		offset, err := dvid.StringToPoint(offsetStr, ",")
   912  		if err != nil {
   913  			return fmt.Errorf("Illegal offset specification: %s: %v", offsetStr, err)
   914  		}
   915  
   916  		// Get list of files to add
   917  		var addedFiles string
   918  		if len(filenames) == 1 {
   919  			addedFiles = filenames[0]
   920  		} else {
   921  			addedFiles = fmt.Sprintf("filenames: %s [%d more]", filenames[0], len(filenames)-1)
   922  		}
   923  		dvid.Debugf(addedFiles + "\n")
   924  
   925  		uuid, versionID, err := datastore.MatchingUUID(uuidStr)
   926  		if err != nil {
   927  			return err
   928  		}
   929  		if err = datastore.AddToNodeLog(uuid, []string{req.Command.String()}); err != nil {
   930  			return err
   931  		}
   932  		go func() {
   933  			if err = d.LoadImages(versionID, offset, filenames); err != nil {
   934  				dvid.Errorf("Cannot load images into data instance %q @ node %s: %v\n", dataName, uuidStr, err)
   935  			}
   936  			if err := datastore.SaveDataByUUID(uuid, d); err != nil {
   937  				dvid.Errorf("Could not store metadata changes into data instance %q @ node %s: %v\n", dataName, uuidStr, err)
   938  			}
   939  		}()
   940  		reply.Text = fmt.Sprintf("Asynchronously loading %d files into data instance %q @ node %s (errors will be printed in server log) ...\n", len(filenames), dataName, uuidStr)
   941  		return nil
   942  
   943  	case "composite":
   944  		if len(req.Command) < 6 {
   945  			return fmt.Errorf("Poorly formatted composite command.  See command-line help.")
   946  		}
   947  		return d.CreateComposite(req, reply)
   948  
   949  	default:
   950  		return fmt.Errorf("Unknown command.  Data type '%s' [%s] does not support '%s' command.",
   951  			d.DataName(), d.TypeName(), req.TypeCommand())
   952  	}
   953  }
   954  
   955  func colorImage(labels *dvid.Image) (image.Image, error) {
   956  	if labels == nil || labels.Which != 3 || labels.NRGBA64 == nil {
   957  		return nil, fmt.Errorf("writePseudoColor can't use labels image with wrong format: %v\n", labels)
   958  	}
   959  	src := labels.NRGBA64
   960  	srcRect := src.Bounds()
   961  	srcW := srcRect.Dx()
   962  	srcH := srcRect.Dy()
   963  
   964  	dst := image.NewNRGBA(image.Rect(0, 0, srcW, srcH))
   965  
   966  	for y := 0; y < srcH; y++ {
   967  		srcI := src.PixOffset(0, y)
   968  		dstI := dst.PixOffset(0, y)
   969  		for x := 0; x < srcW; x++ {
   970  			murmurhash3(src.Pix[srcI:srcI+8], dst.Pix[dstI:dstI+4])
   971  			dst.Pix[dstI+3] = 255
   972  
   973  			srcI += 8
   974  			dstI += 4
   975  		}
   976  	}
   977  	return dst, nil
   978  }
   979  
   980  // compressGoogle uses the neuroglancer compression format
   981  func compressGoogle(data []byte, subvol *dvid.Subvolume) ([]byte, error) {
   982  	// TODO: share table between blocks
   983  	subvolsizes := subvol.Size()
   984  
   985  	// must <= 32
   986  	BLKSIZE := int32(8)
   987  
   988  	xsize := subvolsizes.Value(0)
   989  	ysize := subvolsizes.Value(1)
   990  	zsize := subvolsizes.Value(2)
   991  	gx := subvolsizes.Value(0) / BLKSIZE
   992  	gy := subvolsizes.Value(1) / BLKSIZE
   993  	gz := subvolsizes.Value(2) / BLKSIZE
   994  	if xsize%BLKSIZE > 0 || ysize%BLKSIZE > 0 || zsize%BLKSIZE > 0 {
   995  		return nil, fmt.Errorf("volume must be a multiple of the block size")
   996  	}
   997  
   998  	// add initial 4 byte to designate as a header for the compressed data
   999  	// 64 bit headers for each 8x8x8 block and pre-allocate some data based on expected data size
  1000  	dword := 4
  1001  	globaloffset := dword
  1002  
  1003  	datagoogle := make([]byte, gx*gy*gz*8+int32(globaloffset), xsize*ysize*zsize*8/10)
  1004  	datagoogle[0] = byte(globaloffset / dword) // compressed data starts after first 4 bytes
  1005  
  1006  	// everything is written out little-endian
  1007  	for gziter := int32(0); gziter < gz; gziter++ {
  1008  		for gyiter := int32(0); gyiter < gy; gyiter++ {
  1009  			for gxiter := int32(0); gxiter < gx; gxiter++ {
  1010  				unique_vals := make(map[uint64]uint32)
  1011  				unique_list := make([]uint64, 0)
  1012  
  1013  				currpos := (gziter*BLKSIZE*(xsize*ysize) + gyiter*BLKSIZE*xsize + gxiter*BLKSIZE) * 8
  1014  
  1015  				// extract unique values in the 8x8x8 block
  1016  				for z := int32(0); z < BLKSIZE; z++ {
  1017  					for y := int32(0); y < BLKSIZE; y++ {
  1018  						for x := int32(0); x < BLKSIZE; x++ {
  1019  							if _, ok := unique_vals[binary.LittleEndian.Uint64(data[currpos:currpos+8])]; !ok {
  1020  								unique_vals[binary.LittleEndian.Uint64(data[currpos:currpos+8])] = 0
  1021  								unique_list = append(unique_list, binary.LittleEndian.Uint64(data[currpos:currpos+8]))
  1022  							}
  1023  							currpos += 8
  1024  						}
  1025  						currpos += ((xsize - BLKSIZE) * 8)
  1026  					}
  1027  					currpos += (xsize*ysize - (xsize * (BLKSIZE))) * 8
  1028  				}
  1029  				// write out mapping
  1030  				for pos, val := range unique_list {
  1031  					unique_vals[val] = uint32(pos)
  1032  				}
  1033  
  1034  				// write-out compressed data
  1035  				encodedBits := uint32(math.Ceil(math.Log2(float64(len(unique_vals)))))
  1036  				switch {
  1037  				case encodedBits == 0, encodedBits == 1, encodedBits == 2:
  1038  				case encodedBits <= 4:
  1039  					encodedBits = 4
  1040  				case encodedBits <= 8:
  1041  					encodedBits = 8
  1042  				case encodedBits <= 16:
  1043  					encodedBits = 16
  1044  				}
  1045  
  1046  				// starting location for writing out data
  1047  				currpos2 := len(datagoogle)
  1048  				compressstart := len(datagoogle) / dword // in 4-byte units
  1049  				// number of bytes to add (encode bytes + table size of 8 byte numbers)
  1050  				addedBytes := uint32(encodedBits*uint32(BLKSIZE*BLKSIZE*BLKSIZE)/8) + uint32(len(unique_vals)*8) // will always be a multiple of 4 bytes
  1051  				datagoogle = append(datagoogle, make([]byte, addedBytes)...)
  1052  
  1053  				// do not need to write-out anything if there is only one entry
  1054  				if encodedBits > 0 {
  1055  					currpos := (gziter*BLKSIZE*(xsize*ysize) + gyiter*BLKSIZE*xsize + gxiter*BLKSIZE) * 8
  1056  
  1057  					for z := uint32(0); z < uint32(BLKSIZE); z++ {
  1058  						for y := uint32(0); y < uint32(BLKSIZE); y++ {
  1059  							for x := uint32(0); x < uint32(BLKSIZE); x++ {
  1060  								mappedval := unique_vals[binary.LittleEndian.Uint64(data[currpos:currpos+8])]
  1061  								currpos += 8
  1062  
  1063  								// write out encoding
  1064  								startbit := uint32((encodedBits * x) % uint32(8))
  1065  								if encodedBits == 16 {
  1066  									// write two bytes worth of data
  1067  									datagoogle[currpos2] = byte(255 & mappedval)
  1068  									currpos2++
  1069  									datagoogle[currpos2] = byte(255 & (mappedval >> 8))
  1070  									currpos2++
  1071  								} else {
  1072  									// write bit-shifted data
  1073  									datagoogle[currpos2] |= byte(255 & (mappedval << startbit))
  1074  								}
  1075  								if int(startbit) == (8 - int(encodedBits)) {
  1076  									currpos2++
  1077  								}
  1078  
  1079  							}
  1080  							currpos += ((xsize - BLKSIZE) * 8)
  1081  						}
  1082  						currpos += (xsize*ysize - (xsize * (BLKSIZE))) * 8
  1083  					}
  1084  				}
  1085  				tablestart := currpos2 / dword // in 4-byte units
  1086  				// write-out lookup table
  1087  				for _, val := range unique_list {
  1088  					for bytespot := uint32(0); bytespot < uint32(8); bytespot++ {
  1089  						datagoogle[currpos2] = byte(255 & (val >> (bytespot * 8)))
  1090  						currpos2++
  1091  					}
  1092  				}
  1093  
  1094  				// write-out block header
  1095  				// 8 bytes per header entry
  1096  				headerpos := (gziter*(gy*gx)+gyiter*gx+gxiter)*8 + int32(globaloffset) // shift start by global offset
  1097  
  1098  				// write out lookup table start
  1099  				tablestart -= (globaloffset / dword) // relative to the start of the compressed data
  1100  				datagoogle[headerpos] = byte(255 & tablestart)
  1101  				headerpos++
  1102  				datagoogle[headerpos] = byte(255 & (tablestart >> 8))
  1103  				headerpos++
  1104  				datagoogle[headerpos] = byte(255 & (tablestart >> 16))
  1105  				headerpos++
  1106  
  1107  				// write out number of encoded bits
  1108  				datagoogle[headerpos] = byte(255 & encodedBits)
  1109  				headerpos++
  1110  
  1111  				// write out block compress start
  1112  				compressstart -= (globaloffset / dword) // relative to the start of the compressed data
  1113  				datagoogle[headerpos] = byte(255 & compressstart)
  1114  				headerpos++
  1115  				datagoogle[headerpos] = byte(255 & (compressstart >> 8))
  1116  				headerpos++
  1117  				datagoogle[headerpos] = byte(255 & (compressstart >> 16))
  1118  				headerpos++
  1119  				datagoogle[headerpos] = byte(255 & (compressstart >> 24))
  1120  			}
  1121  		}
  1122  	}
  1123  
  1124  	return datagoogle, nil
  1125  }
  1126  
  1127  func sendBinaryData(compression string, data []byte, subvol *dvid.Subvolume, w http.ResponseWriter) error {
  1128  	var err error
  1129  	w.Header().Set("Content-type", "application/octet-stream")
  1130  	switch compression {
  1131  	case "":
  1132  		_, err = w.Write(data)
  1133  		if err != nil {
  1134  			return err
  1135  		}
  1136  	case "lz4":
  1137  		compressed := make([]byte, lz4.CompressBound(data))
  1138  		var n, outSize int
  1139  		if outSize, err = lz4.Compress(data, compressed); err != nil {
  1140  			return err
  1141  		}
  1142  		compressed = compressed[:outSize]
  1143  		if n, err = w.Write(compressed); err != nil {
  1144  			return err
  1145  		}
  1146  		if n != outSize {
  1147  			errmsg := fmt.Sprintf("Only able to write %d of %d lz4 compressed bytes\n", n, outSize)
  1148  			dvid.Errorf(errmsg)
  1149  			return err
  1150  		}
  1151  	case "gzip":
  1152  		gw := gzip.NewWriter(w)
  1153  		if _, err = gw.Write(data); err != nil {
  1154  			return err
  1155  		}
  1156  		if err = gw.Close(); err != nil {
  1157  			return err
  1158  		}
  1159  	case "google", "googlegzip": // see neuroglancer for details of compressed segmentation format
  1160  		datagoogle, err := compressGoogle(data, subvol)
  1161  		if err != nil {
  1162  			return err
  1163  		}
  1164  		if compression == "googlegzip" {
  1165  			w.Header().Set("Content-encoding", "gzip")
  1166  			gw := gzip.NewWriter(w)
  1167  			if _, err = gw.Write(datagoogle); err != nil {
  1168  				return err
  1169  			}
  1170  			if err = gw.Close(); err != nil {
  1171  				return err
  1172  			}
  1173  
  1174  		} else {
  1175  			_, err = w.Write(datagoogle)
  1176  			if err != nil {
  1177  				return err
  1178  			}
  1179  		}
  1180  	default:
  1181  		return fmt.Errorf("unknown compression type %q", compression)
  1182  	}
  1183  	return nil
  1184  }
  1185  
  1186  func getBinaryData(compression string, in io.ReadCloser, estsize int64) ([]byte, error) {
  1187  	var err error
  1188  	var data []byte
  1189  	switch compression {
  1190  	case "":
  1191  		tlog := dvid.NewTimeLog()
  1192  		data, err = ioutil.ReadAll(in)
  1193  		if err != nil {
  1194  			return nil, err
  1195  		}
  1196  		tlog.Debugf("read 3d uncompressed POST")
  1197  	case "lz4":
  1198  		tlog := dvid.NewTimeLog()
  1199  		data, err = ioutil.ReadAll(in)
  1200  		if err != nil {
  1201  			return nil, err
  1202  		}
  1203  		if len(data) == 0 {
  1204  			return nil, fmt.Errorf("received 0 LZ4 compressed bytes")
  1205  		}
  1206  		tlog.Debugf("read 3d lz4 POST")
  1207  		tlog = dvid.NewTimeLog()
  1208  		uncompressed := make([]byte, estsize)
  1209  		if err = lz4.Uncompress(data, uncompressed); err != nil {
  1210  			return nil, err
  1211  		}
  1212  		data = uncompressed
  1213  		tlog.Debugf("uncompressed 3d lz4 POST")
  1214  	case "gzip":
  1215  		tlog := dvid.NewTimeLog()
  1216  		gr, err := gzip.NewReader(in)
  1217  		if err != nil {
  1218  			return nil, err
  1219  		}
  1220  		data, err = ioutil.ReadAll(gr)
  1221  		if err != nil {
  1222  			return nil, err
  1223  		}
  1224  		if err = gr.Close(); err != nil {
  1225  			return nil, err
  1226  		}
  1227  		tlog.Debugf("read and uncompress 3d gzip POST")
  1228  	default:
  1229  		return nil, fmt.Errorf("unknown compression type %q", compression)
  1230  	}
  1231  	return data, nil
  1232  }
  1233  
  1234  // SetResolution loads JSON data giving Resolution.
  1235  func (d *Data) SetResolution(uuid dvid.UUID, jsonBytes []byte) error {
  1236  	config := make(dvid.NdFloat32, 3)
  1237  	if err := json.Unmarshal(jsonBytes, &config); err != nil {
  1238  		return err
  1239  	}
  1240  	d.Properties.VoxelSize = config
  1241  	if err := datastore.SaveDataByUUID(uuid, d); err != nil {
  1242  		return err
  1243  	}
  1244  	return nil
  1245  }
  1246  
  1247  // if hash is not empty, make sure it is hash of data.
  1248  func checkContentHash(hash string, data []byte) error {
  1249  	if hash == "" {
  1250  		return nil
  1251  	}
  1252  	hexHash := fmt.Sprintf("%x", md5.Sum(data))
  1253  	if hexHash != hash {
  1254  		return fmt.Errorf("content hash incorrect.  expected %s, got %s", hash, hexHash)
  1255  	}
  1256  	return nil
  1257  }
  1258  
  1259  // ServeHTTP handles all incoming HTTP requests for this data.
  1260  func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) (activity map[string]interface{}) {
  1261  	// TODO -- Refactor this method to break it up and make it simpler.  Use the web routing for the endpoints.
  1262  
  1263  	timedLog := dvid.NewTimeLog()
  1264  
  1265  	// Get the action (GET, POST)
  1266  	action := strings.ToLower(r.Method)
  1267  	switch action {
  1268  	case "get":
  1269  	case "post":
  1270  	case "delete":
  1271  	default:
  1272  		server.BadRequest(w, r, "labelblk only handles GET, POST, and DELETE HTTP verbs")
  1273  		return
  1274  	}
  1275  
  1276  	// Break URL request into arguments
  1277  	url := r.URL.Path[len(server.WebAPIPath):]
  1278  	parts := strings.Split(url, "/")
  1279  	if len(parts[len(parts)-1]) == 0 {
  1280  		parts = parts[:len(parts)-1]
  1281  	}
  1282  
  1283  	// Get query strings and possible roi
  1284  	queryStrings := r.URL.Query()
  1285  	roiname := dvid.InstanceName(queryStrings.Get("roi"))
  1286  
  1287  	// Handle POST on data -> setting of configuration
  1288  	if len(parts) == 3 && action == "post" {
  1289  		config, err := server.DecodeJSON(r)
  1290  		if err != nil {
  1291  			server.BadRequest(w, r, err)
  1292  			return
  1293  		}
  1294  		if err := d.ModifyConfig(config); err != nil {
  1295  			server.BadRequest(w, r, err)
  1296  			return
  1297  		}
  1298  		if err := datastore.SaveDataByUUID(uuid, d); err != nil {
  1299  			server.BadRequest(w, r, err)
  1300  			return
  1301  		}
  1302  		fmt.Fprintf(w, "Changed '%s' based on received configuration:\n%s\n", d.DataName(), config)
  1303  		return
  1304  	}
  1305  
  1306  	if len(parts) < 4 {
  1307  		server.BadRequest(w, r, "Incomplete API request")
  1308  		return
  1309  	}
  1310  
  1311  	// Process help and info.
  1312  	switch parts[3] {
  1313  	case "help":
  1314  		w.Header().Set("Content-Type", "text/plain")
  1315  		fmt.Fprintln(w, dtype.Help())
  1316  
  1317  	case "metadata":
  1318  		jsonStr, err := d.NdDataMetadata(ctx)
  1319  		if err != nil {
  1320  			server.BadRequest(w, r, err)
  1321  			return
  1322  		}
  1323  		w.Header().Set("Content-Type", "application/vnd.dvid-nd-data+json")
  1324  		fmt.Fprintln(w, jsonStr)
  1325  
  1326  	case "resolution":
  1327  		jsonBytes, err := ioutil.ReadAll(r.Body)
  1328  		if err != nil {
  1329  			server.BadRequest(w, r, err)
  1330  			return
  1331  		}
  1332  		if err := d.SetResolution(uuid, jsonBytes); err != nil {
  1333  			server.BadRequest(w, r, err)
  1334  			return
  1335  		}
  1336  
  1337  	case "info":
  1338  		jsonBytes, err := d.MarshalJSON()
  1339  		if err != nil {
  1340  			server.BadRequest(w, r, err)
  1341  			return
  1342  		}
  1343  		w.Header().Set("Content-Type", "application/json")
  1344  		fmt.Fprintf(w, string(jsonBytes))
  1345  
  1346  	case "sync":
  1347  		if action != "post" {
  1348  			server.BadRequest(w, r, "Only POST allowed to sync endpoint")
  1349  			return
  1350  		}
  1351  		replace := r.URL.Query().Get("replace") == "true"
  1352  		if err := datastore.SetSyncByJSON(d, uuid, replace, r.Body); err != nil {
  1353  			server.BadRequest(w, r, err)
  1354  			return
  1355  		}
  1356  
  1357  	case "label":
  1358  		// GET <api URL>/node/<UUID>/<data name>/label/<coord>
  1359  		if len(parts) < 5 {
  1360  			server.BadRequest(w, r, "DVID requires coord to follow 'label' command")
  1361  			return
  1362  		}
  1363  		coord, err := dvid.StringToPoint(parts[4], "_")
  1364  		if err != nil {
  1365  			server.BadRequest(w, r, err)
  1366  			return
  1367  		}
  1368  		label, err := d.GetLabelAtPoint(ctx.VersionID(), coord)
  1369  		if err != nil {
  1370  			server.BadRequest(w, r, err)
  1371  			return
  1372  		}
  1373  		w.Header().Set("Content-type", "application/json")
  1374  		jsonStr := fmt.Sprintf(`{"Label": %d}`, label)
  1375  		fmt.Fprintf(w, jsonStr)
  1376  		timedLog.Infof("HTTP %s: label at %s (%s)", r.Method, coord, r.URL)
  1377  
  1378  	case "labels":
  1379  		// POST <api URL>/node/<UUID>/<data name>/labels
  1380  		if action != "get" {
  1381  			server.BadRequest(w, r, "Batch labels query must be a GET request")
  1382  			return
  1383  		}
  1384  		data, err := ioutil.ReadAll(r.Body)
  1385  		if err != nil {
  1386  			server.BadRequest(w, r, "Bad GET request body for batch query: %v", err)
  1387  			return
  1388  		}
  1389  		hash := queryStrings.Get("hash")
  1390  		if err := checkContentHash(hash, data); err != nil {
  1391  			server.BadRequest(w, r, err)
  1392  			return
  1393  		}
  1394  		var coords []dvid.Point3d
  1395  		if err := json.Unmarshal(data, &coords); err != nil {
  1396  			server.BadRequest(w, r, fmt.Sprintf("Bad labels request JSON: %v", err))
  1397  			return
  1398  		}
  1399  		w.Header().Set("Content-type", "application/json")
  1400  		fmt.Fprintf(w, "[")
  1401  		sep := false
  1402  		for _, coord := range coords {
  1403  			label, err := d.GetLabelAtPoint(ctx.VersionID(), coord)
  1404  			if err != nil {
  1405  				server.BadRequest(w, r, err)
  1406  				return
  1407  			}
  1408  			if sep {
  1409  				fmt.Fprintf(w, ",")
  1410  			}
  1411  			fmt.Fprintf(w, "%d", label)
  1412  			sep = true
  1413  		}
  1414  		fmt.Fprintf(w, "]")
  1415  		timedLog.Infof("HTTP batch label-at-point query (%d coordinates)", len(coords))
  1416  
  1417  	case "blocks":
  1418  		// GET <api URL>/node/<UUID>/<data name>/blocks/<coord>/<offset>[?compression=...]
  1419  		sizeStr, offsetStr := parts[4], parts[5]
  1420  
  1421  		if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" {
  1422  			if server.ThrottledHTTP(w) {
  1423  				return
  1424  			}
  1425  			defer server.ThrottledOpDone()
  1426  		}
  1427  		compression := queryStrings.Get("compression")
  1428  		subvol, err := dvid.NewSubvolumeFromStrings(offsetStr, sizeStr, "_")
  1429  		if err != nil {
  1430  			server.BadRequest(w, r, err)
  1431  			return
  1432  		}
  1433  
  1434  		if subvol.StartPoint().NumDims() != 3 || subvol.Size().NumDims() != 3 {
  1435  			server.BadRequest(w, r, "must specify 3D subvolumes", subvol.StartPoint(), subvol.EndPoint())
  1436  			return
  1437  		}
  1438  
  1439  		// Make sure subvolume gets align with blocks
  1440  		if !dvid.BlockAligned(subvol, d.BlockSize()) {
  1441  			server.BadRequest(w, r, "cannot use labels via 'block' endpoint in non-block aligned geometry %s -> %s", subvol.StartPoint(), subvol.EndPoint())
  1442  			return
  1443  		}
  1444  
  1445  		if action == "get" {
  1446  			if err := d.SendBlocks(ctx, w, subvol, compression); err != nil {
  1447  				server.BadRequest(w, r, err)
  1448  				return
  1449  			}
  1450  			timedLog.Infof("HTTP %s: %s (%s)", r.Method, subvol, r.URL)
  1451  		} else {
  1452  			server.BadRequest(w, r, "DVID does not accept the %s action on the 'blocks' endpoint", action)
  1453  			return
  1454  		}
  1455  
  1456  	case "pseudocolor":
  1457  		if len(parts) < 7 {
  1458  			server.BadRequest(w, r, "'%s' must be followed by shape/size/offset", parts[3])
  1459  			return
  1460  		}
  1461  		shapeStr, sizeStr, offsetStr := parts[4], parts[5], parts[6]
  1462  		planeStr := dvid.DataShapeString(shapeStr)
  1463  		plane, err := planeStr.DataShape()
  1464  		if err != nil {
  1465  			server.BadRequest(w, r, err)
  1466  			return
  1467  		}
  1468  		switch plane.ShapeDimensions() {
  1469  		case 2:
  1470  			slice, err := dvid.NewSliceFromStrings(planeStr, offsetStr, sizeStr, "_")
  1471  			if err != nil {
  1472  				server.BadRequest(w, r, err)
  1473  				return
  1474  			}
  1475  			if action != "get" {
  1476  				server.BadRequest(w, r, "DVID does not permit 2d mutations, only 3d block-aligned stores")
  1477  				return
  1478  			}
  1479  			lbl, err := d.NewLabels(slice, nil)
  1480  			if err != nil {
  1481  				server.BadRequest(w, r, err)
  1482  				return
  1483  			}
  1484  			img, err := d.GetImage(ctx.VersionID(), lbl, roiname)
  1485  			if err != nil {
  1486  				server.BadRequest(w, r, err)
  1487  				return
  1488  			}
  1489  
  1490  			// Convert to pseudocolor
  1491  			pseudoColor, err := colorImage(img)
  1492  			if err != nil {
  1493  				server.BadRequest(w, r, err)
  1494  				return
  1495  			}
  1496  
  1497  			//dvid.ElapsedTime(dvid.Normal, startTime, "%s %s upto image formatting", op, slice)
  1498  			var formatStr string
  1499  			if len(parts) >= 8 {
  1500  				formatStr = parts[7]
  1501  			}
  1502  			err = dvid.WriteImageHttp(w, pseudoColor, formatStr)
  1503  			if err != nil {
  1504  				server.BadRequest(w, r, err)
  1505  				return
  1506  			}
  1507  			timedLog.Infof("HTTP %s: %s (%s)", r.Method, plane, r.URL)
  1508  		default:
  1509  			server.BadRequest(w, r, "DVID currently supports only 2d pseudocolor image requests")
  1510  			return
  1511  		}
  1512  
  1513  	case "raw", "isotropic":
  1514  		if len(parts) < 7 {
  1515  			server.BadRequest(w, r, "'%s' must be followed by shape/size/offset", parts[3])
  1516  			return
  1517  		}
  1518  		var isotropic bool = (parts[3] == "isotropic")
  1519  		shapeStr, sizeStr, offsetStr := parts[4], parts[5], parts[6]
  1520  		planeStr := dvid.DataShapeString(shapeStr)
  1521  		plane, err := planeStr.DataShape()
  1522  		if err != nil {
  1523  			server.BadRequest(w, r, err)
  1524  			return
  1525  		}
  1526  		switch plane.ShapeDimensions() {
  1527  		case 2:
  1528  			slice, err := dvid.NewSliceFromStrings(planeStr, offsetStr, sizeStr, "_")
  1529  			if err != nil {
  1530  				server.BadRequest(w, r, err)
  1531  				return
  1532  			}
  1533  			if action != "get" {
  1534  				server.BadRequest(w, r, "DVID does not permit 2d mutations, only 3d block-aligned stores")
  1535  				return
  1536  			}
  1537  			rawSlice, err := dvid.Isotropy2D(d.Properties.VoxelSize, slice, isotropic)
  1538  			lbl, err := d.NewLabels(rawSlice, nil)
  1539  			if err != nil {
  1540  				server.BadRequest(w, r, err)
  1541  				return
  1542  			}
  1543  			img, err := d.GetImage(ctx.VersionID(), lbl, roiname)
  1544  			if err != nil {
  1545  				server.BadRequest(w, r, err)
  1546  				return
  1547  			}
  1548  			if isotropic {
  1549  				dstW := int(slice.Size().Value(0))
  1550  				dstH := int(slice.Size().Value(1))
  1551  				img, err = img.ScaleImage(dstW, dstH)
  1552  				if err != nil {
  1553  					server.BadRequest(w, r, err)
  1554  					return
  1555  				}
  1556  			}
  1557  			var formatStr string
  1558  			if len(parts) >= 8 {
  1559  				formatStr = parts[7]
  1560  			}
  1561  			//dvid.ElapsedTime(dvid.Normal, startTime, "%s %s upto image formatting", op, slice)
  1562  			err = dvid.WriteImageHttp(w, img.Get(), formatStr)
  1563  			if err != nil {
  1564  				server.BadRequest(w, r, err)
  1565  				return
  1566  			}
  1567  			timedLog.Infof("HTTP %s: %s (%s)", r.Method, plane, r.URL)
  1568  		case 3:
  1569  			if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" {
  1570  				if server.ThrottledHTTP(w) {
  1571  					return
  1572  				}
  1573  				defer server.ThrottledOpDone()
  1574  			}
  1575  			compression := queryStrings.Get("compression")
  1576  			subvol, err := dvid.NewSubvolumeFromStrings(offsetStr, sizeStr, "_")
  1577  			if err != nil {
  1578  				server.BadRequest(w, r, err)
  1579  				return
  1580  			}
  1581  			if action == "get" {
  1582  				lbl, err := d.NewLabels(subvol, nil)
  1583  				if err != nil {
  1584  					server.BadRequest(w, r, err)
  1585  					return
  1586  				}
  1587  				data, err := d.GetVolume(ctx.VersionID(), lbl, roiname)
  1588  				if err != nil {
  1589  					server.BadRequest(w, r, err)
  1590  					return
  1591  				}
  1592  				if err := sendBinaryData(compression, data, subvol, w); err != nil {
  1593  					server.BadRequest(w, r, err)
  1594  					return
  1595  				}
  1596  			} else {
  1597  				if isotropic {
  1598  					server.BadRequest(w, r, "can only POST 'raw' not 'isotropic' images")
  1599  					return
  1600  				}
  1601  				estsize := subvol.NumVoxels() * 8
  1602  				data, err := getBinaryData(compression, r.Body, estsize)
  1603  				if err != nil {
  1604  					server.BadRequest(w, r, err)
  1605  					return
  1606  				}
  1607  				lbl, err := d.NewLabels(subvol, data)
  1608  				if err != nil {
  1609  					server.BadRequest(w, r, err)
  1610  					return
  1611  				}
  1612  				mutID := d.NewMutationID()
  1613  				if queryStrings.Get("mutate") == "true" {
  1614  					if err = d.MutateVoxels(ctx.VersionID(), mutID, lbl.Voxels, roiname); err != nil {
  1615  						server.BadRequest(w, r, err)
  1616  						return
  1617  					}
  1618  				} else {
  1619  					if err = d.IngestVoxels(ctx.VersionID(), mutID, lbl.Voxels, roiname); err != nil {
  1620  						server.BadRequest(w, r, err)
  1621  						return
  1622  					}
  1623  				}
  1624  				// Let any synced downres instance that we've completed block-level ops.
  1625  				d.publishDownresCommit(ctx.VersionID(), mutID)
  1626  			}
  1627  			timedLog.Infof("HTTP %s: %s (%s)", r.Method, subvol, r.URL)
  1628  		default:
  1629  			server.BadRequest(w, r, "DVID currently supports shapes of only 2 and 3 dimensions")
  1630  			return
  1631  		}
  1632  
  1633  	default:
  1634  		server.BadAPIRequest(w, r, d)
  1635  	}
  1636  	return
  1637  }
  1638  
  1639  // --------- Other functions on labelblk Data -----------------
  1640  
  1641  // GetLabelBytes returns a slice of little-endian uint64 corresponding to the block coordinate.
  1642  func (d *Data) GetLabelBytes(v dvid.VersionID, bcoord dvid.ChunkPoint3d) ([]byte, error) {
  1643  	store, err := datastore.GetOrderedKeyValueDB(d)
  1644  	if err != nil {
  1645  		return nil, err
  1646  	}
  1647  
  1648  	// Retrieve the block of labels
  1649  	ctx := datastore.NewVersionedCtx(d, v)
  1650  	index := dvid.IndexZYX(bcoord)
  1651  	serialization, err := store.Get(ctx, NewTKey(&index))
  1652  	if err != nil {
  1653  		return nil, fmt.Errorf("Error getting '%s' block for index %s\n", d.DataName(), bcoord)
  1654  	}
  1655  	if serialization == nil {
  1656  		return []byte{}, nil
  1657  	}
  1658  	labelData, _, err := dvid.DeserializeData(serialization, true)
  1659  	if err != nil {
  1660  		return nil, fmt.Errorf("Unable to deserialize block %s in '%s': %v\n", bcoord, d.DataName(), err)
  1661  	}
  1662  	return labelData, nil
  1663  }
  1664  
  1665  // GetLabelBytesAtPoint returns the 8 byte slice corresponding to a 64-bit label at a point.
  1666  func (d *Data) GetLabelBytesAtPoint(v dvid.VersionID, pt dvid.Point) ([]byte, error) {
  1667  	coord, ok := pt.(dvid.Chunkable)
  1668  	if !ok {
  1669  		return nil, fmt.Errorf("Can't determine block of point %s", pt)
  1670  	}
  1671  	blockSize := d.BlockSize()
  1672  	bcoord := coord.Chunk(blockSize).(dvid.ChunkPoint3d)
  1673  
  1674  	labelData, err := d.GetLabelBytes(v, bcoord)
  1675  	if err != nil {
  1676  		return nil, err
  1677  	}
  1678  	if len(labelData) == 0 {
  1679  		return zeroLabelBytes, nil
  1680  	}
  1681  
  1682  	// Retrieve the particular label within the block.
  1683  	ptInBlock := coord.PointInChunk(blockSize)
  1684  	nx := int64(blockSize.Value(0))
  1685  	nxy := nx * int64(blockSize.Value(1))
  1686  	i := (int64(ptInBlock.Value(0)) + int64(ptInBlock.Value(1))*nx + int64(ptInBlock.Value(2))*nxy) * 8
  1687  	return labelData[i : i+8], nil
  1688  }
  1689  
  1690  // GetLabelAtPoint returns the 64-bit unsigned int label for a given point.
  1691  func (d *Data) GetLabelAtPoint(v dvid.VersionID, pt dvid.Point) (uint64, error) {
  1692  	labelBytes, err := d.GetLabelBytesAtPoint(v, pt)
  1693  	if err != nil {
  1694  		return 0, err
  1695  	}
  1696  	return binary.LittleEndian.Uint64(labelBytes), nil
  1697  }