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

     1  // Handler functions for HTTP requests.
     2  
     3  package labelmap
     4  
     5  import (
     6  	"encoding/binary"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"strconv"
    12  	"strings"
    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/datatype/common/proto"
    18  	"github.com/janelia-flyem/dvid/dvid"
    19  	"github.com/janelia-flyem/dvid/server"
    20  	pb "google.golang.org/protobuf/proto"
    21  )
    22  
    23  func (d *Data) handleLabel(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
    24  	// GET <api URL>/node/<UUID>/<data name>/label/<coord>
    25  	if len(parts) < 5 {
    26  		server.BadRequest(w, r, "DVID requires coord to follow 'label' command")
    27  		return
    28  	}
    29  	timedLog := dvid.NewTimeLog()
    30  
    31  	coord, err := dvid.StringToPoint3d(parts[4], "_")
    32  	if err != nil {
    33  		server.BadRequest(w, r, err)
    34  		return
    35  	}
    36  	queryStrings := r.URL.Query()
    37  	scale, err := getScale(queryStrings)
    38  	if err != nil {
    39  		server.BadRequest(w, r, "bad scale specified: %v", err)
    40  		return
    41  	}
    42  	isSupervoxel := queryStrings.Get("supervoxels") == "true"
    43  
    44  	labels, err := d.GetLabelPoints(ctx.VersionID(), []dvid.Point3d{coord}, scale, isSupervoxel)
    45  	if err != nil {
    46  		server.BadRequest(w, r, err)
    47  		return
    48  	}
    49  	w.Header().Set("Content-type", "application/json")
    50  	jsonStr := fmt.Sprintf(`{"Label": %d}`, labels[0])
    51  	fmt.Fprint(w, jsonStr)
    52  
    53  	timedLog.Infof("HTTP GET label at %s (%s)", parts[4], r.URL)
    54  }
    55  
    56  func (d *Data) handleLabels(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) {
    57  	// GET <api URL>/node/<UUID>/<data name>/labels
    58  	timedLog := dvid.NewTimeLog()
    59  
    60  	if strings.ToLower(r.Method) != "get" {
    61  		server.BadRequest(w, r, "Batch labels query must be a GET request")
    62  		return
    63  	}
    64  	data, err := ioutil.ReadAll(r.Body)
    65  	if err != nil {
    66  		server.BadRequest(w, r, "Bad GET request body for batch query: %v", err)
    67  		return
    68  	}
    69  	queryStrings := r.URL.Query()
    70  	scale, err := getScale(queryStrings)
    71  	if err != nil {
    72  		server.BadRequest(w, r, "bad scale specified: %v", err)
    73  		return
    74  	}
    75  	isSupervoxel := queryStrings.Get("supervoxels") == "true"
    76  	hash := queryStrings.Get("hash")
    77  	if err := checkContentHash(hash, data); err != nil {
    78  		server.BadRequest(w, r, err)
    79  		return
    80  	}
    81  	var coords []dvid.Point3d
    82  	if err := json.Unmarshal(data, &coords); err != nil {
    83  		server.BadRequest(w, r, fmt.Sprintf("Bad labels request JSON: %v", err))
    84  		return
    85  	}
    86  	labels, err := d.GetLabelPoints(ctx.VersionID(), coords, scale, isSupervoxel)
    87  	if err != nil {
    88  		server.BadRequest(w, r, err)
    89  		return
    90  	}
    91  	w.Header().Set("Content-type", "application/json")
    92  	fmt.Fprintf(w, "[")
    93  	sep := false
    94  	for _, label := range labels {
    95  		if sep {
    96  			fmt.Fprintf(w, ",")
    97  		}
    98  		fmt.Fprintf(w, "%d", label)
    99  		sep = true
   100  	}
   101  	fmt.Fprintf(w, "]")
   102  
   103  	timedLog.Infof("HTTP GET batch label-at-point query of %d points (%s)", len(coords), r.URL)
   104  }
   105  
   106  func (d *Data) handleMapping(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) {
   107  	// GET <api URL>/node/<UUID>/<data name>/mapping
   108  	timedLog := dvid.NewTimeLog()
   109  
   110  	if strings.ToLower(r.Method) != "get" {
   111  		server.BadRequest(w, r, "Batch mapping query must be a GET request")
   112  		return
   113  	}
   114  	data, err := ioutil.ReadAll(r.Body)
   115  	if err != nil {
   116  		server.BadRequest(w, r, "Bad GET request body for batch query: %v", err)
   117  		return
   118  	}
   119  	queryStrings := r.URL.Query()
   120  	hash := queryStrings.Get("hash")
   121  	if err := checkContentHash(hash, data); err != nil {
   122  		server.BadRequest(w, r, err)
   123  		return
   124  	}
   125  	var supervoxels []uint64
   126  	if err := json.Unmarshal(data, &supervoxels); err != nil {
   127  		server.BadRequest(w, r, fmt.Sprintf("Bad mapping request JSON: %v", err))
   128  		return
   129  	}
   130  	svmap, err := getMapping(d, ctx.VersionID())
   131  	if err != nil {
   132  		server.BadRequest(w, r, "couldn't get mapping for data %q, version %d: %v", d.DataName(), ctx.VersionID(), err)
   133  		return
   134  	}
   135  	labels, found, err := svmap.MappedLabels(ctx.VersionID(), supervoxels)
   136  	if err != nil {
   137  		server.BadRequest(w, r, err)
   138  		return
   139  	}
   140  	if queryStrings.Get("nolookup") != "true" {
   141  		labels, err = d.verifyMappings(ctx, supervoxels, labels, found)
   142  		if err != nil {
   143  			server.BadRequest(w, r, err)
   144  			return
   145  		}
   146  	}
   147  
   148  	w.Header().Set("Content-type", "application/json")
   149  	fmt.Fprintf(w, "[")
   150  	sep := false
   151  	for _, label := range labels {
   152  		if sep {
   153  			fmt.Fprintf(w, ",")
   154  		}
   155  		fmt.Fprintf(w, "%d", label)
   156  		sep = true
   157  	}
   158  	fmt.Fprintf(w, "]")
   159  
   160  	timedLog.Infof("HTTP GET batch mapping query of %d labels (%s)", len(labels), r.URL)
   161  }
   162  
   163  func (d *Data) handleSupervoxelSplits(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) {
   164  	// GET <api URL>/node/<UUID>/<data name>/supervoxel-splits
   165  	timedLog := dvid.NewTimeLog()
   166  
   167  	if strings.ToLower(r.Method) != "get" {
   168  		server.BadRequest(w, r, "The /supervoxel-splits endpoint is GET only")
   169  		return
   170  	}
   171  	svmap, err := getMapping(d, ctx.VersionID())
   172  	if err != nil {
   173  		server.BadRequest(w, r, "couldn't get mapping for data %q, version %d: %v", d.DataName(), ctx.VersionID(), err)
   174  		return
   175  	}
   176  	splitsJSON, err := svmap.SupervoxelSplitsJSON(ctx.VersionID())
   177  	if err != nil {
   178  		server.BadRequest(w, r, err)
   179  		return
   180  	}
   181  
   182  	w.Header().Set("Content-type", "application/json")
   183  	fmt.Fprintf(w, splitsJSON)
   184  
   185  	timedLog.Infof("HTTP GET supervoxel splits query (%s)", r.URL)
   186  }
   187  
   188  func (d *Data) handleBlocks(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
   189  	// GET <api URL>/node/<UUID>/<data name>/blocks/<size>/<offset>[?compression=...]
   190  	// POST <api URL>/node/<UUID>/<data name>/blocks[?compression=...]
   191  	timedLog := dvid.NewTimeLog()
   192  
   193  	queryStrings := r.URL.Query()
   194  	if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" {
   195  		if server.ThrottledHTTP(w) {
   196  			return
   197  		}
   198  		defer server.ThrottledOpDone()
   199  	}
   200  	scale, err := getScale(queryStrings)
   201  	if err != nil {
   202  		server.BadRequest(w, r, "bad scale specified: %v", err)
   203  		return
   204  	}
   205  
   206  	compression := queryStrings.Get("compression")
   207  	downscale := queryStrings.Get("downres") == "true"
   208  	supervoxels := queryStrings.Get("supervoxels") == "true"
   209  
   210  	if strings.ToLower(r.Method) == "get" {
   211  		if len(parts) < 6 {
   212  			server.BadRequest(w, r, "must specify size and offset with GET /blocks endpoint")
   213  			return
   214  		}
   215  		sizeStr, offsetStr := parts[4], parts[5]
   216  		subvol, err := dvid.NewSubvolumeFromStrings(offsetStr, sizeStr, "_")
   217  		if err != nil {
   218  			server.BadRequest(w, r, err)
   219  			return
   220  		}
   221  		if subvol.StartPoint().NumDims() != 3 || subvol.Size().NumDims() != 3 {
   222  			server.BadRequest(w, r, "must specify 3D subvolumes", subvol.StartPoint(), subvol.EndPoint())
   223  			return
   224  		}
   225  
   226  		// Make sure subvolume gets align with blocks
   227  		if !dvid.BlockAligned(subvol, d.BlockSize()) {
   228  			server.BadRequest(w, r, "cannot use labels via 'blocks' endpoint in non-block aligned geometry %s -> %s", subvol.StartPoint(), subvol.EndPoint())
   229  			return
   230  		}
   231  
   232  		if err := d.sendBlocksVolume(ctx, w, supervoxels, scale, subvol, compression); err != nil {
   233  			server.BadRequest(w, r, err)
   234  		}
   235  		timedLog.Infof("HTTP GET blocks at size %s, offset %s (%s)", parts[4], parts[5], r.URL)
   236  	} else {
   237  		var indexing bool
   238  		if queryStrings.Get("noindexing") != "true" {
   239  			indexing = true
   240  		}
   241  		if err := d.storeBlocks(ctx, r.Body, scale, downscale, compression, indexing); err != nil {
   242  			server.BadRequest(w, r, err)
   243  		}
   244  		timedLog.Infof("HTTP POST blocks, indexing = %t, downscale = %t (%s)", indexing, downscale, r.URL)
   245  	}
   246  }
   247  
   248  func (d *Data) handleIngest(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) {
   249  	// POST <api URL>/node/<UUID>/<data name>/ingest-supervoxels[?scale=...]
   250  	timedLog := dvid.NewTimeLog()
   251  
   252  	queryStrings := r.URL.Query()
   253  	scale, err := getScale(queryStrings)
   254  	if err != nil {
   255  		server.BadRequest(w, r, "bad scale specified: %v", err)
   256  		return
   257  	}
   258  	if err := d.ingestBlocks(ctx, r.Body, scale); err != nil {
   259  		server.BadRequest(w, r, err)
   260  	}
   261  	timedLog.Infof("HTTP POST ingest-supervoxels %q, scale = %d", d.DataName(), scale)
   262  }
   263  
   264  func (d *Data) handleProximity(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
   265  	// GET <api URL>/node/<UUID>/<data name>/proximity/<label 1>,<label 2>
   266  	timedLog := dvid.NewTimeLog()
   267  
   268  	queryStrings := r.URL.Query()
   269  	if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" {
   270  		if server.ThrottledHTTP(w) {
   271  			return
   272  		}
   273  		defer server.ThrottledOpDone()
   274  	}
   275  
   276  	label1, err := strconv.ParseUint(parts[4], 10, 64)
   277  	if err != nil {
   278  		server.BadRequest(w, r, err)
   279  		return
   280  	}
   281  	label2, err := strconv.ParseUint(parts[5], 10, 64)
   282  	if err != nil {
   283  		server.BadRequest(w, r, err)
   284  		return
   285  	}
   286  	if label1 == 0 || label2 == 0 {
   287  		server.BadRequest(w, r, "there is no index for protected label 0")
   288  		return
   289  	}
   290  	if label1 == label2 {
   291  		server.BadRequest(w, r, "the two supplied labels were identical")
   292  		return
   293  	}
   294  
   295  	if strings.ToLower(r.Method) != "get" {
   296  		server.BadRequest(w, r, fmt.Errorf("only GET action allowed on /proximity endpoint"))
   297  		return
   298  	}
   299  
   300  	idx1, err := getCachedLabelIndex(d, ctx.VersionID(), label1)
   301  	if err != nil {
   302  		server.BadRequest(w, r, err)
   303  		return
   304  	}
   305  	idx2, err := getCachedLabelIndex(d, ctx.VersionID(), label2)
   306  	if err != nil {
   307  		server.BadRequest(w, r, err)
   308  		return
   309  	}
   310  	if idx1 == nil || idx2 == nil {
   311  		w.WriteHeader(http.StatusNotFound)
   312  		return
   313  	}
   314  	jsonBytes, err := d.getProximity(ctx, idx1, idx2)
   315  	if err != nil {
   316  		server.BadRequest(w, r, err)
   317  		return
   318  	}
   319  	w.Header().Set("Content-Type", "application/json")
   320  	fmt.Fprintf(w, string(jsonBytes))
   321  
   322  	timedLog.Infof("HTTP %s proximity for labels %d, %d (%s)", r.Method, label1, label2, r.URL)
   323  }
   324  
   325  func (d *Data) handleIndex(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
   326  	// GET  <api URL>/node/<UUID>/<data name>/index/<label>
   327  	// POST <api URL>/node/<UUID>/<data name>/index/<label>
   328  	timedLog := dvid.NewTimeLog()
   329  
   330  	queryStrings := r.URL.Query()
   331  	if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" {
   332  		if server.ThrottledHTTP(w) {
   333  			return
   334  		}
   335  		defer server.ThrottledOpDone()
   336  	}
   337  	metadata := (queryStrings.Get("metadata-only") == "true")
   338  	mutidStr := queryStrings.Get("mutid")
   339  	var mutID uint64
   340  	var err error
   341  	if mutidStr != "" {
   342  		mutID, err = strconv.ParseUint(mutidStr, 10, 64)
   343  		if err != nil {
   344  			server.BadRequest(w, r, "mutid query string cannot be parsed: %v", err)
   345  			return
   346  		}
   347  	}
   348  
   349  	label, err := strconv.ParseUint(parts[4], 10, 64)
   350  	if err != nil {
   351  		server.BadRequest(w, r, err)
   352  		return
   353  	}
   354  	if label == 0 {
   355  		server.BadRequest(w, r, "there is no index for protected label 0")
   356  		return
   357  	}
   358  
   359  	switch strings.ToLower(r.Method) {
   360  	case "post":
   361  		if r.Body == nil {
   362  			server.BadRequest(w, r, fmt.Errorf("no data POSTed"))
   363  			return
   364  		}
   365  		serialization, err := ioutil.ReadAll(r.Body)
   366  		if err != nil {
   367  			server.BadRequest(w, r, err)
   368  			return
   369  		}
   370  		idx := new(labels.Index)
   371  		if err := pb.Unmarshal(serialization, idx); err != nil {
   372  			server.BadRequest(w, r, err)
   373  		}
   374  		if idx.Label != label {
   375  			server.BadRequest(w, r, "serialized Index was for label %d yet was POSTed to label %d", idx.Label, label)
   376  			return
   377  		}
   378  		if len(idx.Blocks) == 0 {
   379  			if err := deleteLabelIndex(ctx, label); err != nil {
   380  				server.BadRequest(w, r, err)
   381  				return
   382  			}
   383  			timedLog.Infof("HTTP POST index for label %d (%s) -- empty index so deleted index", label, r.URL)
   384  			return
   385  		}
   386  		if err := putCachedLabelIndex(d, ctx.VersionID(), idx); err != nil {
   387  			server.BadRequest(w, r, err)
   388  			return
   389  		}
   390  
   391  	case "get":
   392  		var idx *labels.Index
   393  		if mutidStr != "" {
   394  			idx, err = d.getMutcache(ctx.VersionID(), mutID, label)
   395  		} else {
   396  			idx, err = getCachedLabelIndex(d, ctx.VersionID(), label)
   397  		}
   398  		if err != nil {
   399  			server.BadRequest(w, r, err)
   400  			return
   401  		}
   402  		if idx == nil {
   403  			w.WriteHeader(http.StatusNotFound)
   404  			return
   405  		}
   406  		if metadata {
   407  			w.Header().Set("Content-type", "application/json")
   408  			fmt.Fprintf(w, `{"num_voxels":%d,"last_mutid":%d,"last_mod_time":"%s","last_mod_user":"%s","last_mod_app":"%s"}`,
   409  				idx.NumVoxels(), idx.LastMutId, idx.LastModTime, idx.LastModUser, idx.LastModApp)
   410  		} else {
   411  			serialization, err := pb.Marshal(idx)
   412  			if err != nil {
   413  				server.BadRequest(w, r, err)
   414  				return
   415  			}
   416  			w.Header().Set("Content-type", "application/octet-stream")
   417  			n, err := w.Write(serialization)
   418  			if err != nil {
   419  				server.BadRequest(w, r, err)
   420  				return
   421  			}
   422  			if n != len(serialization) {
   423  				server.BadRequest(w, r, "unable to write all %d bytes of serialized label %d index: only %d bytes written", len(serialization), label, n)
   424  				return
   425  			}
   426  		}
   427  
   428  	default:
   429  		server.BadRequest(w, r, "only POST or GET action allowed for /index endpoint")
   430  		return
   431  	}
   432  
   433  	timedLog.Infof("HTTP %s index for label %d (%s)", r.Method, label, r.URL)
   434  }
   435  
   436  func (d *Data) handleListLabels(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) {
   437  	// GET <api URL>/node/<UUID>/<data name>/listlabels
   438  	const MaxReturnedLabels = 10000000
   439  	timedLog := dvid.NewTimeLog()
   440  
   441  	queryStrings := r.URL.Query()
   442  	var err error
   443  	var start, number uint64
   444  	var sizes bool
   445  	startStr := queryStrings.Get("start")
   446  	numberStr := queryStrings.Get("number")
   447  	if queryStrings.Get("sizes") == "true" {
   448  		sizes = true
   449  	}
   450  	if startStr != "" {
   451  		start, err = strconv.ParseUint(startStr, 10, 64)
   452  		if err != nil {
   453  			server.BadRequest(w, r, err)
   454  			return
   455  		}
   456  	}
   457  	if numberStr != "" {
   458  		number, err = strconv.ParseUint(numberStr, 10, 64)
   459  		if err != nil {
   460  			server.BadRequest(w, r, err)
   461  			return
   462  		}
   463  	} else {
   464  		number = MaxReturnedLabels
   465  	}
   466  	if number > MaxReturnedLabels {
   467  		number = MaxReturnedLabels
   468  	}
   469  
   470  	method := strings.ToLower(r.Method)
   471  	switch method {
   472  	case "get":
   473  	default:
   474  		server.BadRequest(w, r, "only GET actions allowed for /listlabels endpoint")
   475  		return
   476  	}
   477  
   478  	w.Header().Set("Content-type", "application/octet-stream")
   479  	numLabels, err := d.listLabels(ctx, start, number, sizes, w)
   480  	if err != nil {
   481  		// Because streaming HTTP writes precludes ability to signal error, we cap with four 0 uint64.
   482  		for i := 0; i < 4; i++ {
   483  			binary.Write(w, binary.LittleEndian, uint64(0))
   484  		}
   485  		dvid.Errorf("Error in trying to transmit label list stream: %v\n", err)
   486  	}
   487  
   488  	timedLog.Infof("HTTP %s listlabels for %d labels (%s)", method, numLabels, r.URL)
   489  }
   490  
   491  func (d *Data) handleIndices(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) {
   492  	// GET <api URL>/node/<UUID>/<data name>/indices
   493  	// POST <api URL>/node/<UUID>/<data name>/indices
   494  	timedLog := dvid.NewTimeLog()
   495  
   496  	queryStrings := r.URL.Query()
   497  	if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" {
   498  		if server.ThrottledHTTP(w) {
   499  			return
   500  		}
   501  		defer server.ThrottledOpDone()
   502  	}
   503  
   504  	method := strings.ToLower(r.Method)
   505  	switch method {
   506  	case "post":
   507  	case "get":
   508  	default:
   509  		server.BadRequest(w, r, "only GET and POST actions allowed for /indices endpoint")
   510  		return
   511  	}
   512  	if r.Body == nil {
   513  		server.BadRequest(w, r, fmt.Errorf("expected data to be sent for /indices request"))
   514  		return
   515  	}
   516  	dataIn, err := ioutil.ReadAll(r.Body)
   517  	if err != nil {
   518  		server.BadRequest(w, r, err)
   519  	}
   520  	if method == "post" {
   521  		numAdded, numDeleted, err := putProtoLabelIndices(ctx, dataIn)
   522  		if err != nil {
   523  			server.BadRequest(w, r, "error putting protobuf label indices: %v", err)
   524  			return
   525  		}
   526  		timedLog.Infof("HTTP %s indices - %d added, %d deleted (%s)", method, numAdded, numDeleted, r.URL)
   527  	} else { // GET
   528  		numLabels, dataOut, err := getProtoLabelIndices(ctx, dataIn)
   529  		if err != nil {
   530  			server.BadRequest(w, r, "error getting protobuf label indices: %v", err)
   531  			return
   532  		}
   533  		requestSize := int64(len(dataOut))
   534  		if requestSize > server.MaxDataRequest {
   535  			server.BadRequest(w, r, "requested payload (%d bytes) exceeds this DVID server's set limit (%d)",
   536  				requestSize, server.MaxDataRequest)
   537  			return
   538  		}
   539  		w.Header().Set("Content-type", "application/octet-stream")
   540  		n, err := w.Write(dataOut)
   541  		if err != nil {
   542  			server.BadRequest(w, r, err)
   543  			return
   544  		}
   545  		if n != len(dataOut) {
   546  			server.BadRequest(w, r, "unable to write all %d bytes of serialized label indices: only %d bytes written", len(dataOut), n)
   547  			return
   548  		}
   549  		timedLog.Infof("HTTP %s indices for %d labels (%s)", method, numLabels, r.URL)
   550  	}
   551  }
   552  
   553  func (d *Data) handleIndicesCompressed(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) {
   554  	// GET <api URL>/node/<UUID>/<data name>/indices-compressed
   555  	timedLog := dvid.NewTimeLog()
   556  
   557  	queryStrings := r.URL.Query()
   558  	if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" {
   559  		if server.ThrottledHTTP(w) {
   560  			return
   561  		}
   562  		defer server.ThrottledOpDone()
   563  	}
   564  
   565  	if r.Method != http.MethodGet {
   566  		server.BadRequest(w, r, "only GET action allowed for /indices endpoint")
   567  		return
   568  	}
   569  	if r.Body == nil {
   570  		server.BadRequest(w, r, fmt.Errorf("expected data to be sent for /indices request"))
   571  		return
   572  	}
   573  	dataIn, err := ioutil.ReadAll(r.Body)
   574  	if err != nil {
   575  		server.BadRequest(w, r, err)
   576  	}
   577  	var numLabels int
   578  	var labelList []uint64
   579  	if err := json.Unmarshal(dataIn, &labelList); err != nil {
   580  		server.BadRequest(w, r, "expected JSON label list for GET /indices: %v", err)
   581  		return
   582  	}
   583  	numLabels = len(labelList)
   584  	if numLabels > 50000 {
   585  		server.BadRequest(w, r, "only 50,000 label indices can be returned at a time, %d given", len(labelList))
   586  		return
   587  	}
   588  	store, err := datastore.GetKeyValueDB(d)
   589  	if err != nil {
   590  		server.BadRequest(w, r, "can't get store for data %q", d.DataName())
   591  		return
   592  	}
   593  
   594  	defer timedLog.Infof("HTTP %s compressed indices for %d labels (%s)", r.Method, numLabels, r.URL)
   595  
   596  	headerBytes := make([]byte, 16)
   597  	zeroBytes := make([]byte, 16)
   598  	binary.LittleEndian.PutUint64(zeroBytes[0:8], 0)
   599  	binary.LittleEndian.PutUint64(zeroBytes[8:16], 0)
   600  	w.Header().Set("Content-type", "application/octet-stream")
   601  	for i, label := range labelList {
   602  		data, err := getLabelIndexCompressed(store, ctx, label)
   603  		if err != nil {
   604  			w.Write(zeroBytes)
   605  			dvid.Errorf("Error reading streaming compressed label index at pos %d, label %d: %v\n", i, label, err)
   606  			return
   607  		}
   608  		length := uint64(len(data))
   609  		binary.LittleEndian.PutUint64(headerBytes[0:8], length)
   610  		binary.LittleEndian.PutUint64(headerBytes[8:16], label)
   611  
   612  		if _, err = w.Write(headerBytes); err != nil {
   613  			w.Write(zeroBytes)
   614  			dvid.Errorf("Error writing header of streaming compressed label index at pos %d, label %d: %v\n", i, label, err)
   615  			return
   616  		}
   617  
   618  		if length > 0 {
   619  			if _, err = w.Write(data); err != nil {
   620  				dvid.Errorf("Error writing data of streaming compressed label index at pos %d, label %d: %v\n", i, label, err)
   621  				return
   622  			}
   623  		}
   624  	}
   625  }
   626  
   627  func (d *Data) handleMappings(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) {
   628  	// POST <api URL>/node/<UUID>/<data name>/mappings
   629  	timedLog := dvid.NewTimeLog()
   630  
   631  	queryStrings := r.URL.Query()
   632  	if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" {
   633  		if server.ThrottledHTTP(w) {
   634  			return
   635  		}
   636  		defer server.ThrottledOpDone()
   637  	}
   638  
   639  	format := queryStrings.Get("format")
   640  
   641  	switch strings.ToLower(r.Method) {
   642  	case "post":
   643  		if r.Body == nil {
   644  			server.BadRequest(w, r, fmt.Errorf("no data POSTed"))
   645  			return
   646  		}
   647  		serialization, err := ioutil.ReadAll(r.Body)
   648  		if err != nil {
   649  			server.BadRequest(w, r, err)
   650  		}
   651  		var mappings proto.MappingOps
   652  		if err := pb.Unmarshal(serialization, &mappings); err != nil {
   653  			server.BadRequest(w, r, err)
   654  		}
   655  		if err := d.ingestMappings(ctx, &mappings); err != nil {
   656  			server.BadRequest(w, r, err)
   657  		}
   658  		timedLog.Infof("HTTP POST %d mappings (%s)", len(mappings.Mappings), r.URL)
   659  
   660  	case "get":
   661  		if err := d.writeMappings(w, ctx.VersionID(), (format == "binary")); err != nil {
   662  			server.BadRequest(w, r, "unable to write mappings: %v", err)
   663  		}
   664  		timedLog.Infof("HTTP GET mappings (%s)", r.URL)
   665  
   666  	default:
   667  		server.BadRequest(w, r, "only POST action allowed for /mappings endpoint")
   668  	}
   669  }
   670  
   671  func (d *Data) handleMutations(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) {
   672  	// GET <api URL>/node/<UUID>/<data name>/mutations
   673  	timedLog := dvid.NewTimeLog()
   674  
   675  	queryStrings := r.URL.Query()
   676  	if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" {
   677  		if server.ThrottledHTTP(w) {
   678  			return
   679  		}
   680  		defer server.ThrottledOpDone()
   681  	}
   682  
   683  	switch strings.ToLower(r.Method) {
   684  	case "get":
   685  		if err := server.ReadJSONMutations(w, ctx.VersionUUID(), d.DataUUID()); err != nil {
   686  			server.BadRequest(w, r, "unable to write mutaions: %v", err)
   687  		}
   688  		timedLog.Infof("HTTP GET mutations (%s)", r.URL)
   689  
   690  	default:
   691  		server.BadRequest(w, r, "only POST action allowed for /mappings endpoint")
   692  	}
   693  }
   694  
   695  func (d *Data) handleHistory(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
   696  	// GET <api URL>/node/<UUID>/<data name>/history/<label>/<from UUID>/<to UUID>
   697  	if len(parts) < 7 {
   698  		server.BadRequest(w, r, "ERROR: DVID requires label ID, 'from' UUID, and 'to' UUID to follow 'history' command")
   699  		return
   700  	}
   701  	timedLog := dvid.NewTimeLog()
   702  
   703  	if strings.ToLower(r.Method) != "get" {
   704  		server.BadRequest(w, r, "only GET action allowed for /history endpoint")
   705  		return
   706  	}
   707  
   708  	label, err := strconv.ParseUint(parts[4], 10, 64)
   709  	if err != nil {
   710  		server.BadRequest(w, r, err)
   711  		return
   712  	}
   713  	if label == 0 {
   714  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be used as sparse volume.\n")
   715  		return
   716  	}
   717  	fromUUID, _, err := datastore.MatchingUUID(parts[5])
   718  	if err != nil {
   719  		server.BadRequest(w, r, err)
   720  		return
   721  	}
   722  	toUUID, _, err := datastore.MatchingUUID(parts[6])
   723  	if err != nil {
   724  		server.BadRequest(w, r, err)
   725  		return
   726  	}
   727  
   728  	if err := d.GetMutationHistory(w, fromUUID, toUUID, label); err != nil {
   729  		server.BadRequest(w, r, "unable to get mutation history: %v", err)
   730  	}
   731  	timedLog.Infof("HTTP GET history (%s)", r.URL)
   732  }
   733  
   734  func (d *Data) handlePseudocolor(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
   735  	if len(parts) < 7 {
   736  		server.BadRequest(w, r, "'%s' must be followed by shape/size/offset", parts[3])
   737  		return
   738  	}
   739  	timedLog := dvid.NewTimeLog()
   740  
   741  	queryStrings := r.URL.Query()
   742  	roiname := dvid.InstanceName(queryStrings.Get("roi"))
   743  	scale, err := getScale(queryStrings)
   744  	if err != nil {
   745  		server.BadRequest(w, r, "bad scale specified: %v", err)
   746  		return
   747  	}
   748  
   749  	shapeStr, sizeStr, offsetStr := parts[4], parts[5], parts[6]
   750  	planeStr := dvid.DataShapeString(shapeStr)
   751  	plane, err := planeStr.DataShape()
   752  	if err != nil {
   753  		server.BadRequest(w, r, err)
   754  		return
   755  	}
   756  	switch plane.ShapeDimensions() {
   757  	case 2:
   758  		slice, err := dvid.NewSliceFromStrings(planeStr, offsetStr, sizeStr, "_")
   759  		if err != nil {
   760  			server.BadRequest(w, r, err)
   761  			return
   762  		}
   763  		if strings.ToLower(r.Method) != "get" {
   764  			server.BadRequest(w, r, "DVID does not permit 2d mutations, only 3d block-aligned stores")
   765  			return
   766  		}
   767  		lbl, err := d.NewLabels(slice, nil)
   768  		if err != nil {
   769  			server.BadRequest(w, r, err)
   770  			return
   771  		}
   772  		img, err := d.GetImage(ctx.VersionID(), lbl, false, scale, roiname)
   773  		if err != nil {
   774  			server.BadRequest(w, r, err)
   775  			return
   776  		}
   777  
   778  		// Convert to pseudocolor
   779  		pseudoColor, err := colorImage(img)
   780  		if err != nil {
   781  			server.BadRequest(w, r, err)
   782  			return
   783  		}
   784  
   785  		//dvid.ElapsedTime(dvid.Normal, startTime, "%s %s upto image formatting", op, slice)
   786  		var formatStr string
   787  		if len(parts) >= 8 {
   788  			formatStr = parts[7]
   789  		}
   790  		err = dvid.WriteImageHttp(w, pseudoColor, formatStr)
   791  		if err != nil {
   792  			server.BadRequest(w, r, err)
   793  			return
   794  		}
   795  	default:
   796  		server.BadRequest(w, r, "DVID currently supports only 2d pseudocolor image requests")
   797  		return
   798  	}
   799  	timedLog.Infof("HTTP GET pseudocolor with shape %s, size %s, offset %s", parts[4], parts[5], parts[6])
   800  }
   801  
   802  func (d *Data) handleDataRequest(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
   803  	if len(parts) < 7 {
   804  		server.BadRequest(w, r, "'%s' must be followed by shape/size/offset", parts[3])
   805  		return
   806  	}
   807  	timedLog := dvid.NewTimeLog()
   808  
   809  	var isotropic = (parts[3] == "isotropic")
   810  	shapeStr, sizeStr, offsetStr := parts[4], parts[5], parts[6]
   811  	planeStr := dvid.DataShapeString(shapeStr)
   812  	plane, err := planeStr.DataShape()
   813  	if err != nil {
   814  		server.BadRequest(w, r, err)
   815  		return
   816  	}
   817  	queryStrings := r.URL.Query()
   818  	roiname := dvid.InstanceName(queryStrings.Get("roi"))
   819  	supervoxels := queryStrings.Get("supervoxels") == "true"
   820  
   821  	scale, err := getScale(queryStrings)
   822  	if err != nil {
   823  		server.BadRequest(w, r, "bad scale specified: %v", err)
   824  		return
   825  	}
   826  
   827  	switch plane.ShapeDimensions() {
   828  	case 2:
   829  		slice, err := dvid.NewSliceFromStrings(planeStr, offsetStr, sizeStr, "_")
   830  		if err != nil {
   831  			server.BadRequest(w, r, err)
   832  			return
   833  		}
   834  		if strings.ToLower(r.Method) != "get" {
   835  			server.BadRequest(w, r, "DVID does not permit 2d mutations, only 3d block-aligned stores")
   836  			return
   837  		}
   838  		rawSlice, err := dvid.Isotropy2D(d.Properties.VoxelSize, slice, isotropic)
   839  		if err != nil {
   840  			server.BadRequest(w, r, err)
   841  			return
   842  		}
   843  		lbl, err := d.NewLabels(rawSlice, nil)
   844  		if err != nil {
   845  			server.BadRequest(w, r, err)
   846  			return
   847  		}
   848  		img, err := d.GetImage(ctx.VersionID(), lbl, supervoxels, scale, roiname)
   849  		if err != nil {
   850  			server.BadRequest(w, r, err)
   851  			return
   852  		}
   853  		if isotropic {
   854  			dstW := int(slice.Size().Value(0))
   855  			dstH := int(slice.Size().Value(1))
   856  			img, err = img.ScaleImage(dstW, dstH)
   857  			if err != nil {
   858  				server.BadRequest(w, r, err)
   859  				return
   860  			}
   861  		}
   862  		var formatStr string
   863  		if len(parts) >= 8 {
   864  			formatStr = parts[7]
   865  		}
   866  		//dvid.ElapsedTime(dvid.Normal, startTime, "%s %s upto image formatting", op, slice)
   867  		err = dvid.WriteImageHttp(w, img.Get(), formatStr)
   868  		if err != nil {
   869  			server.BadRequest(w, r, err)
   870  			return
   871  		}
   872  	case 3:
   873  		if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" {
   874  			if server.ThrottledHTTP(w) {
   875  				return
   876  			}
   877  			defer server.ThrottledOpDone()
   878  		}
   879  		compression := queryStrings.Get("compression")
   880  		subvol, err := dvid.NewSubvolumeFromStrings(offsetStr, sizeStr, "_")
   881  		if err != nil {
   882  			server.BadRequest(w, r, err)
   883  			return
   884  		}
   885  		if strings.ToLower(r.Method) == "get" {
   886  			done, err := d.writeBlockToHTTP(ctx, w, subvol, compression, supervoxels, scale, roiname)
   887  			if err != nil {
   888  				server.BadRequest(w, r, err)
   889  				return
   890  			}
   891  			if !done {
   892  				// Couldn't stream blocks, so need to allocate buffer and send all at once.
   893  				lbl, err := d.NewLabels(subvol, nil)
   894  				if err != nil {
   895  					server.BadRequest(w, r, err)
   896  					return
   897  				}
   898  				data, err := d.GetVolume(ctx.VersionID(), lbl, supervoxels, scale, roiname)
   899  				if err != nil {
   900  					server.BadRequest(w, r, err)
   901  					return
   902  				}
   903  				if err := writeCompressedToHTTP(compression, data, subvol, w); err != nil {
   904  					server.BadRequest(w, r, err)
   905  					return
   906  				}
   907  			}
   908  		} else {
   909  			if isotropic {
   910  				server.BadRequest(w, r, "can only POST 'raw' not 'isotropic' images")
   911  				return
   912  			}
   913  			estsize := subvol.NumVoxels() * 8
   914  			data, err := uncompressReaderData(compression, r.Body, estsize)
   915  			if err != nil {
   916  				server.BadRequest(w, r, err)
   917  				return
   918  			}
   919  			mutate := queryStrings.Get("mutate") == "true"
   920  			if err = d.PutLabels(ctx.VersionID(), subvol, data, roiname, mutate); err != nil {
   921  				server.BadRequest(w, r, err)
   922  				return
   923  			}
   924  		}
   925  	default:
   926  		server.BadRequest(w, r, "DVID currently supports shapes of only 2 and 3 dimensions")
   927  		return
   928  	}
   929  	timedLog.Infof("HTTP %s %s with shape %s, size %s, offset %s, scale %d", r.Method, parts[3], parts[4], parts[5], parts[6], scale)
   930  }
   931  
   932  func (d *Data) getSparsevolOptions(r *http.Request) (b dvid.Bounds, compression string, err error) {
   933  	queryStrings := r.URL.Query()
   934  	compression = queryStrings.Get("compression")
   935  
   936  	if b.Voxel, err = dvid.OptionalBoundsFromQueryString(r); err != nil {
   937  		err = fmt.Errorf("error parsing bounds from query string: %v", err)
   938  		return
   939  	}
   940  	blockSize, ok := d.BlockSize().(dvid.Point3d)
   941  	if !ok {
   942  		err = fmt.Errorf("Error: BlockSize for %s wasn't 3d", d.DataName())
   943  		return
   944  	}
   945  	b.Block = b.Voxel.Divide(blockSize)
   946  	b.Exact = true
   947  	if queryStrings.Get("exact") == "false" {
   948  		b.Exact = false
   949  	}
   950  	return
   951  }
   952  
   953  func (d *Data) handleSupervoxels(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
   954  	// GET <api URL>/node/<UUID>/<data name>/supervoxels/<label>
   955  	if len(parts) < 5 {
   956  		server.BadRequest(w, r, "DVID requires label to follow 'supervoxels' command")
   957  		return
   958  	}
   959  	timedLog := dvid.NewTimeLog()
   960  
   961  	label, err := strconv.ParseUint(parts[4], 10, 64)
   962  	if err != nil {
   963  		server.BadRequest(w, r, err)
   964  		return
   965  	}
   966  	if label == 0 {
   967  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be queried as body.\n")
   968  		return
   969  	}
   970  
   971  	supervoxels, err := d.GetSupervoxels(ctx.VersionID(), label)
   972  	if err != nil {
   973  		server.BadRequest(w, r, err)
   974  		return
   975  	}
   976  	if len(supervoxels) == 0 {
   977  		w.WriteHeader(http.StatusNotFound)
   978  	} else {
   979  		w.Header().Set("Content-type", "application/json")
   980  		fmt.Fprintf(w, "[")
   981  		i := 0
   982  		for supervoxel := range supervoxels {
   983  			fmt.Fprintf(w, "%d", supervoxel)
   984  			i++
   985  			if i < len(supervoxels) {
   986  				fmt.Fprintf(w, ",")
   987  			}
   988  		}
   989  		fmt.Fprintf(w, "]")
   990  	}
   991  
   992  	timedLog.Infof("HTTP GET supervoxels for label %d (%s)", label, r.URL)
   993  }
   994  
   995  func (d *Data) handleSupervoxelSizes(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
   996  	// GET <api URL>/node/<UUID>/<data name>/supervoxel-sizes/<label>
   997  	if len(parts) < 5 {
   998  		server.BadRequest(w, r, "DVID requires label to follow 'supervoxels' command")
   999  		return
  1000  	}
  1001  	timedLog := dvid.NewTimeLog()
  1002  
  1003  	label, err := strconv.ParseUint(parts[4], 10, 64)
  1004  	if err != nil {
  1005  		server.BadRequest(w, r, err)
  1006  		return
  1007  	}
  1008  	if label == 0 {
  1009  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be queried as body.\n")
  1010  		return
  1011  	}
  1012  
  1013  	idx, err := GetLabelIndex(d, ctx.VersionID(), label, false)
  1014  	if err != nil {
  1015  		server.BadRequest(w, r, err)
  1016  		return
  1017  	}
  1018  	if idx == nil || len(idx.Blocks) == 0 {
  1019  		w.WriteHeader(http.StatusNotFound)
  1020  		return
  1021  	}
  1022  
  1023  	counts := idx.GetSupervoxelCounts()
  1024  	var supervoxels_json, sizes_json string
  1025  	for sv, count := range counts {
  1026  		supervoxels_json += strconv.FormatUint(sv, 10) + ","
  1027  		sizes_json += strconv.FormatUint(count, 10) + ","
  1028  	}
  1029  	w.Header().Set("Content-type", "application/json")
  1030  	fmt.Fprintf(w, "{\n  ")
  1031  	fmt.Fprintf(w, `"supervoxels": [%s],`, supervoxels_json[:len(supervoxels_json)-1])
  1032  	fmt.Fprintf(w, "\n  ")
  1033  	fmt.Fprintf(w, `"sizes": [%s]`, sizes_json[:len(sizes_json)-1])
  1034  	fmt.Fprintf(w, "\n}")
  1035  
  1036  	timedLog.Infof("HTTP GET supervoxel-sizes for label %d (%s)", label, r.URL)
  1037  }
  1038  
  1039  func (d *Data) handleLabelmod(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1040  	// GET <api URL>/node/<UUID>/<data name>/lastmod/<label>
  1041  	if len(parts) < 5 {
  1042  		server.BadRequest(w, r, "DVID requires label to follow 'lastmod' command")
  1043  		return
  1044  	}
  1045  	timedLog := dvid.NewTimeLog()
  1046  
  1047  	label, err := strconv.ParseUint(parts[4], 10, 64)
  1048  	if err != nil {
  1049  		server.BadRequest(w, r, err)
  1050  		return
  1051  	}
  1052  	if label == 0 {
  1053  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be queried as body.\n")
  1054  		return
  1055  	}
  1056  
  1057  	idx, err := GetLabelIndex(d, ctx.VersionID(), label, false)
  1058  	if err != nil {
  1059  		server.BadRequest(w, r, "unable to get label %d index: %v", label, err)
  1060  		return
  1061  	}
  1062  	if idx == nil || len(idx.Blocks) == 0 {
  1063  		w.WriteHeader(http.StatusNotFound)
  1064  		return
  1065  	}
  1066  
  1067  	w.Header().Set("Content-type", "application/json")
  1068  	fmt.Fprintf(w, `{`)
  1069  	fmt.Fprintf(w, `"mutation id": %d, `, idx.LastMutId)
  1070  	fmt.Fprintf(w, `"last mod user": %q, `, idx.LastModUser)
  1071  	fmt.Fprintf(w, `"last mod app": %q, `, idx.LastModApp)
  1072  	fmt.Fprintf(w, `"last mod time": %q `, idx.LastModTime)
  1073  	fmt.Fprintf(w, `}`)
  1074  
  1075  	timedLog.Infof("HTTP GET lastmod for label %d (%s)", label, r.URL)
  1076  }
  1077  
  1078  func (d *Data) handleSize(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1079  	// GET <api URL>/node/<UUID>/<data name>/size/<label>[?supervoxels=true]
  1080  	if len(parts) < 5 {
  1081  		server.BadRequest(w, r, "DVID requires label to follow 'size' command")
  1082  		return
  1083  	}
  1084  	timedLog := dvid.NewTimeLog()
  1085  
  1086  	label, err := strconv.ParseUint(parts[4], 10, 64)
  1087  	if err != nil {
  1088  		server.BadRequest(w, r, err)
  1089  		return
  1090  	}
  1091  	if label == 0 {
  1092  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be queried as body.\n")
  1093  		return
  1094  	}
  1095  	queryStrings := r.URL.Query()
  1096  	isSupervoxel := queryStrings.Get("supervoxels") == "true"
  1097  	size, err := GetLabelSize(d, ctx.VersionID(), label, isSupervoxel)
  1098  	if err != nil {
  1099  		server.BadRequest(w, r, "unable to get label %d size: %v", label, err)
  1100  		return
  1101  	}
  1102  	if size == 0 {
  1103  		w.WriteHeader(http.StatusNotFound)
  1104  	} else {
  1105  		w.Header().Set("Content-type", "application/json")
  1106  		fmt.Fprintf(w, `{"voxels": %d}`, size)
  1107  	}
  1108  
  1109  	timedLog.Infof("HTTP GET size for label %d, supervoxels=%t (%s)", label, isSupervoxel, r.URL)
  1110  }
  1111  
  1112  func (d *Data) handleSizes(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) {
  1113  	// GET <api URL>/node/<UUID>/<data name>/sizes
  1114  	timedLog := dvid.NewTimeLog()
  1115  
  1116  	if strings.ToLower(r.Method) != "get" {
  1117  		server.BadRequest(w, r, "Batch sizes query must be a GET request")
  1118  		return
  1119  	}
  1120  	data, err := ioutil.ReadAll(r.Body)
  1121  	if err != nil {
  1122  		server.BadRequest(w, r, "Bad GET request body for batch sizes query: %v", err)
  1123  		return
  1124  	}
  1125  	queryStrings := r.URL.Query()
  1126  	hash := queryStrings.Get("hash")
  1127  	if err := checkContentHash(hash, data); err != nil {
  1128  		server.BadRequest(w, r, err)
  1129  		return
  1130  	}
  1131  	var labelList []uint64
  1132  	if err := json.Unmarshal(data, &labelList); err != nil {
  1133  		server.BadRequest(w, r, fmt.Sprintf("Bad mapping request JSON: %v", err))
  1134  		return
  1135  	}
  1136  	isSupervoxel := queryStrings.Get("supervoxels") == "true"
  1137  	sizes, err := GetLabelSizes(d, ctx.VersionID(), labelList, isSupervoxel)
  1138  	if err != nil {
  1139  		server.BadRequest(w, r, "unable to get label sizes: %v", err)
  1140  		return
  1141  	}
  1142  
  1143  	w.Header().Set("Content-type", "application/json")
  1144  	fmt.Fprintf(w, "[")
  1145  	sep := false
  1146  	for _, size := range sizes {
  1147  		if sep {
  1148  			fmt.Fprintf(w, ",")
  1149  		}
  1150  		fmt.Fprintf(w, "%d", size)
  1151  		sep = true
  1152  	}
  1153  	fmt.Fprintf(w, "]")
  1154  
  1155  	timedLog.Infof("HTTP GET batch sizes query of %d labels (%s)", len(sizes), r.URL)
  1156  }
  1157  
  1158  func (d *Data) handleSparsevolSize(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1159  	// GET <api URL>/node/<UUID>/<data name>/sparsevol-size/<label>
  1160  	if len(parts) < 5 {
  1161  		server.BadRequest(w, r, "ERROR: DVID requires label ID to follow 'sparsevol-size' command")
  1162  		return
  1163  	}
  1164  	label, err := strconv.ParseUint(parts[4], 10, 64)
  1165  	if err != nil {
  1166  		server.BadRequest(w, r, err)
  1167  		return
  1168  	}
  1169  	if label == 0 {
  1170  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be used as sparse volume.\n")
  1171  		return
  1172  	}
  1173  	if strings.ToLower(r.Method) != "get" {
  1174  		server.BadRequest(w, r, "DVID does not support %s on /sparsevol-size endpoint", r.Method)
  1175  		return
  1176  	}
  1177  
  1178  	queryStrings := r.URL.Query()
  1179  	isSupervoxel := queryStrings.Get("supervoxels") == "true"
  1180  
  1181  	idx, err := GetLabelIndex(d, ctx.VersionID(), label, isSupervoxel)
  1182  	if err != nil {
  1183  		server.BadRequest(w, r, "problem getting label set idx on label %: %v", label, err)
  1184  		return
  1185  	}
  1186  	if idx == nil || len(idx.Blocks) == 0 {
  1187  		w.WriteHeader(http.StatusNotFound)
  1188  		return
  1189  	}
  1190  	if isSupervoxel {
  1191  		idx, err = idx.LimitToSupervoxel(label)
  1192  		if err != nil {
  1193  			server.BadRequest(w, r, "error limiting label %d index to supervoxel %d: %v\n", idx.Label, label, err)
  1194  			return
  1195  		}
  1196  		if idx == nil || len(idx.Blocks) == 0 {
  1197  			w.WriteHeader(http.StatusNotFound)
  1198  			return
  1199  		}
  1200  	}
  1201  
  1202  	w.Header().Set("Content-type", "application/json")
  1203  	fmt.Fprintf(w, "{")
  1204  	fmt.Fprintf(w, `"voxels": %d, `, idx.NumVoxels())
  1205  	fmt.Fprintf(w, `"numblocks": %d, `, len(idx.Blocks))
  1206  	minBlock, maxBlock, err := idx.GetBlockIndices().GetBounds()
  1207  	if err != nil {
  1208  		server.BadRequest(w, r, "problem getting bounds on blocks of label %d: %v", label, err)
  1209  		return
  1210  	}
  1211  	blockSize, ok := d.BlockSize().(dvid.Point3d)
  1212  	if !ok {
  1213  		server.BadRequest(w, r, "Error: BlockSize for %s wasn't 3d", d.DataName())
  1214  		return
  1215  	}
  1216  	minx := minBlock[0] * blockSize[0]
  1217  	miny := minBlock[1] * blockSize[1]
  1218  	minz := minBlock[2] * blockSize[2]
  1219  	maxx := (maxBlock[0]+1)*blockSize[0] - 1
  1220  	maxy := (maxBlock[1]+1)*blockSize[1] - 1
  1221  	maxz := (maxBlock[2]+1)*blockSize[2] - 1
  1222  	fmt.Fprintf(w, `"minvoxel": [%d, %d, %d], `, minx, miny, minz)
  1223  	fmt.Fprintf(w, `"maxvoxel": [%d, %d, %d]`, maxx, maxy, maxz)
  1224  	fmt.Fprintf(w, "}")
  1225  }
  1226  
  1227  func (d *Data) handleSparsevol(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1228  	// GET <api URL>/node/<UUID>/<data name>/sparsevol/<label>
  1229  	// POST <api URL>/node/<UUID>/<data name>/sparsevol/<label>
  1230  	// HEAD <api URL>/node/<UUID>/<data name>/sparsevol/<label>
  1231  	if len(parts) < 5 {
  1232  		server.BadRequest(w, r, "ERROR: DVID requires label ID to follow 'sparsevol' command")
  1233  		return
  1234  	}
  1235  	queryStrings := r.URL.Query()
  1236  	scale, err := getScale(queryStrings)
  1237  	if err != nil {
  1238  		server.BadRequest(w, r, "bad scale specified: %v", err)
  1239  		return
  1240  	}
  1241  	isSupervoxel := queryStrings.Get("supervoxels") == "true"
  1242  
  1243  	label, err := strconv.ParseUint(parts[4], 10, 64)
  1244  	if err != nil {
  1245  		server.BadRequest(w, r, err)
  1246  		return
  1247  	}
  1248  	if label == 0 {
  1249  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be used as sparse volume.\n")
  1250  		return
  1251  	}
  1252  	b, compression, err := d.getSparsevolOptions(r)
  1253  	if err != nil {
  1254  		server.BadRequest(w, r, err)
  1255  		return
  1256  	}
  1257  
  1258  	timedLog := dvid.NewTimeLog()
  1259  	switch strings.ToLower(r.Method) {
  1260  	case "get":
  1261  		labelBlockMeta, exists, err := d.constrainLabelIndex(ctx, label, scale, b, isSupervoxel)
  1262  		if err != nil {
  1263  			server.BadRequest(w, r, err)
  1264  			return
  1265  		}
  1266  		if !exists {
  1267  			dvid.Infof("GET sparsevol on label %d was not found.\n", label)
  1268  			w.WriteHeader(http.StatusNotFound)
  1269  			return
  1270  		}
  1271  
  1272  		w.Header().Set("Content-type", "application/octet-stream")
  1273  
  1274  		switch svformatFromQueryString(r) {
  1275  		case FormatLegacyRLE:
  1276  			err = d.writeLegacyRLE(ctx, labelBlockMeta, compression, w)
  1277  		case FormatBinaryBlocks:
  1278  			err = d.writeBinaryBlocks(ctx, labelBlockMeta, compression, w)
  1279  		case FormatStreamingRLE:
  1280  			err = d.writeStreamingRLE(ctx, labelBlockMeta, compression, w)
  1281  		}
  1282  		if err != nil {
  1283  			server.BadRequest(w, r, err)
  1284  			return
  1285  		}
  1286  
  1287  	case "head":
  1288  		w.Header().Set("Content-type", "text/html")
  1289  		found, err := d.FoundSparseVol(ctx, label, b, isSupervoxel)
  1290  		if err != nil {
  1291  			server.BadRequest(w, r, err)
  1292  			return
  1293  		}
  1294  		if found {
  1295  			w.WriteHeader(http.StatusOK)
  1296  		} else {
  1297  			w.WriteHeader(http.StatusNoContent)
  1298  		}
  1299  		return
  1300  
  1301  	case "post":
  1302  		server.BadRequest(w, r, "POST of sparsevol not currently implemented\n")
  1303  		return
  1304  	default:
  1305  		server.BadRequest(w, r, "Unable to handle HTTP action %s on sparsevol endpoint", r.Method)
  1306  		return
  1307  	}
  1308  
  1309  	timedLog.Infof("HTTP %s: sparsevol on label %s (%s)", r.Method, parts[4], r.URL)
  1310  }
  1311  
  1312  func (d *Data) handleSparsevolByPoint(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1313  	// GET <api URL>/node/<UUID>/<data name>/sparsevol-by-point/<coord>
  1314  	if len(parts) < 5 {
  1315  		server.BadRequest(w, r, "ERROR: DVID requires coord to follow 'sparsevol-by-point' command")
  1316  		return
  1317  	}
  1318  	timedLog := dvid.NewTimeLog()
  1319  
  1320  	queryStrings := r.URL.Query()
  1321  	scale, err := getScale(queryStrings)
  1322  	if err != nil {
  1323  		server.BadRequest(w, r, "bad scale specified: %v", err)
  1324  		return
  1325  	}
  1326  	isSupervoxel := queryStrings.Get("supervoxels") == "true"
  1327  
  1328  	coord, err := dvid.StringToPoint(parts[4], "_")
  1329  	if err != nil {
  1330  		server.BadRequest(w, r, err)
  1331  		return
  1332  	}
  1333  	label, err := d.GetLabelAtScaledPoint(ctx.VersionID(), coord, scale, isSupervoxel)
  1334  	if err != nil {
  1335  		server.BadRequest(w, r, err)
  1336  		return
  1337  	}
  1338  	if label == 0 {
  1339  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be used as sparse volume.\n")
  1340  		return
  1341  	}
  1342  	b, compression, err := d.getSparsevolOptions(r)
  1343  	if err != nil {
  1344  		server.BadRequest(w, r, err)
  1345  		return
  1346  	}
  1347  
  1348  	w.Header().Set("Content-type", "application/octet-stream")
  1349  
  1350  	labelBlockMeta, exists, err := d.constrainLabelIndex(ctx, label, scale, b, isSupervoxel)
  1351  	if err != nil {
  1352  		server.BadRequest(w, r, err)
  1353  		return
  1354  	}
  1355  	if !exists {
  1356  		dvid.Infof("GET sparsevol on label %d was not found.\n", label)
  1357  		w.WriteHeader(http.StatusNotFound)
  1358  		return
  1359  	}
  1360  
  1361  	switch svformatFromQueryString(r) {
  1362  	case FormatLegacyRLE:
  1363  		err = d.writeLegacyRLE(ctx, labelBlockMeta, compression, w)
  1364  	case FormatBinaryBlocks:
  1365  		err = d.writeBinaryBlocks(ctx, labelBlockMeta, compression, w)
  1366  	case FormatStreamingRLE:
  1367  		err = d.writeStreamingRLE(ctx, labelBlockMeta, compression, w)
  1368  	}
  1369  	if err != nil {
  1370  		server.BadRequest(w, r, err)
  1371  		return
  1372  	}
  1373  
  1374  	timedLog.Infof("HTTP %s: sparsevol-by-point at %s (%s)", r.Method, parts[4], r.URL)
  1375  }
  1376  
  1377  func (d *Data) handleSparsevolCoarse(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1378  	// GET <api URL>/node/<UUID>/<data name>/sparsevol-coarse/<label>
  1379  	if len(parts) < 5 {
  1380  		server.BadRequest(w, r, "DVID requires label ID to follow 'sparsevol-coarse' command")
  1381  		return
  1382  	}
  1383  	timedLog := dvid.NewTimeLog()
  1384  
  1385  	label, err := strconv.ParseUint(parts[4], 10, 64)
  1386  	if err != nil {
  1387  		server.BadRequest(w, r, err)
  1388  		return
  1389  	}
  1390  	if label == 0 {
  1391  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be used as sparse volume.\n")
  1392  		return
  1393  	}
  1394  	queryStrings := r.URL.Query()
  1395  	isSupervoxel := queryStrings.Get("supervoxels") == "true"
  1396  	var b dvid.Bounds
  1397  	b.Voxel, err = dvid.OptionalBoundsFromQueryString(r)
  1398  	if err != nil {
  1399  		server.BadRequest(w, r, "Error parsing bounds from query string: %v\n", err)
  1400  		return
  1401  	}
  1402  	blockSize, ok := d.BlockSize().(dvid.Point3d)
  1403  	if !ok {
  1404  		server.BadRequest(w, r, "Error: BlockSize for %s wasn't 3d", d.DataName())
  1405  		return
  1406  	}
  1407  	b.Block = b.Voxel.Divide(blockSize)
  1408  	data, err := d.GetSparseCoarseVol(ctx, label, b, isSupervoxel)
  1409  	if err != nil {
  1410  		server.BadRequest(w, r, err)
  1411  		return
  1412  	}
  1413  	if data == nil {
  1414  		w.WriteHeader(http.StatusNotFound)
  1415  		return
  1416  	}
  1417  	w.Header().Set("Content-type", "application/octet-stream")
  1418  	_, err = w.Write(data)
  1419  	if err != nil {
  1420  		server.BadRequest(w, r, err)
  1421  		return
  1422  	}
  1423  	timedLog.Infof("HTTP %s: sparsevol-coarse on label %s (%s)", r.Method, parts[4], r.URL)
  1424  }
  1425  
  1426  func (d *Data) handleSparsevolsCoarse(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1427  	// GET <api URL>/node/<UUID>/<data name>/sparsevols-coarse/<start label>/<end label>
  1428  	if len(parts) < 6 {
  1429  		server.BadRequest(w, r, "DVID requires start and end label ID to follow 'sparsevols-coarse' command")
  1430  		return
  1431  	}
  1432  	timedLog := dvid.NewTimeLog()
  1433  
  1434  	begLabel, err := strconv.ParseUint(parts[4], 10, 64)
  1435  	if err != nil {
  1436  		server.BadRequest(w, r, err)
  1437  		return
  1438  	}
  1439  	endLabel, err := strconv.ParseUint(parts[5], 10, 64)
  1440  	if err != nil {
  1441  		server.BadRequest(w, r, err)
  1442  		return
  1443  	}
  1444  	if begLabel == 0 || endLabel == 0 {
  1445  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be used as sparse volume.\n")
  1446  		return
  1447  	}
  1448  
  1449  	var b dvid.Bounds
  1450  	b.Voxel, err = dvid.OptionalBoundsFromQueryString(r)
  1451  	if err != nil {
  1452  		server.BadRequest(w, r, "Error parsing bounds from query string: %v\n", err)
  1453  		return
  1454  	}
  1455  	blockSize, ok := d.BlockSize().(dvid.Point3d)
  1456  	if !ok {
  1457  		server.BadRequest(w, r, "Error: BlockSize for %s wasn't 3d", d.DataName())
  1458  		return
  1459  	}
  1460  	b.Block = b.Voxel.Divide(blockSize)
  1461  
  1462  	w.Header().Set("Content-type", "application/octet-stream")
  1463  	if err := d.WriteSparseCoarseVols(ctx, w, begLabel, endLabel, b); err != nil {
  1464  		server.BadRequest(w, r, err)
  1465  		return
  1466  	}
  1467  	timedLog.Infof("HTTP %s: sparsevols-coarse on label %s to %s (%s)", r.Method, parts[4], parts[5], r.URL)
  1468  }
  1469  
  1470  func (d *Data) handleMaxlabel(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1471  	// GET <api URL>/node/<UUID>/<data name>/maxlabel
  1472  	// POST <api URL>/node/<UUID>/<data name>/maxlabel/<max label>
  1473  	timedLog := dvid.NewTimeLog()
  1474  	switch strings.ToLower(r.Method) {
  1475  	case "get":
  1476  		w.Header().Set("Content-Type", "application/json")
  1477  		maxlabel, ok := d.MaxLabel[ctx.VersionID()]
  1478  		if !ok {
  1479  			server.BadRequest(w, r, "No maximum label found for %s version %d\n", d.DataName(), ctx.VersionID())
  1480  			return
  1481  		}
  1482  		fmt.Fprintf(w, "{%q: %d}", "maxlabel", maxlabel)
  1483  
  1484  	case "post":
  1485  		if len(parts) < 5 {
  1486  			server.BadRequest(w, r, "DVID requires max label ID to follow POST /maxlabel")
  1487  			return
  1488  		}
  1489  		maxlabel, err := strconv.ParseUint(parts[4], 10, 64)
  1490  		if err != nil {
  1491  			server.BadRequest(w, r, err)
  1492  			return
  1493  		}
  1494  		changed, err := d.updateMaxLabel(ctx.VersionID(), maxlabel)
  1495  		if err != nil {
  1496  			server.BadRequest(w, r, err)
  1497  			return
  1498  		}
  1499  		if changed {
  1500  			versionuuid, _ := datastore.UUIDFromVersion(ctx.VersionID())
  1501  			msginfo := map[string]interface{}{
  1502  				"Action":    "post-maxlabel",
  1503  				"MaxLabel":  maxlabel,
  1504  				"UUID":      string(versionuuid),
  1505  				"Timestamp": time.Now().String(),
  1506  			}
  1507  			jsonmsg, _ := json.Marshal(msginfo)
  1508  			if err = d.PublishKafkaMsg(jsonmsg); err != nil {
  1509  				dvid.Errorf("error on sending split op to kafka: %v", err)
  1510  			}
  1511  		}
  1512  
  1513  	default:
  1514  		server.BadRequest(w, r, "Unknown action %q requested: %s\n", r.Method, r.URL)
  1515  		return
  1516  	}
  1517  	timedLog.Infof("HTTP maxlabel request (%s)", r.URL)
  1518  }
  1519  
  1520  func (d *Data) handleNextlabel(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1521  	// GET <api URL>/node/<UUID>/<data name>/nextlabel
  1522  	// POST <api URL>/node/<UUID>/<data name>/nextlabel/<number of labels>
  1523  	timedLog := dvid.NewTimeLog()
  1524  	w.Header().Set("Content-Type", "application/json")
  1525  	switch strings.ToLower(r.Method) {
  1526  	case "get":
  1527  		if d.NextLabel == 0 {
  1528  			fmt.Fprintf(w, `{"nextlabel": %d}`, d.MaxRepoLabel+1)
  1529  		} else {
  1530  			fmt.Fprintf(w, `{"nextlabel": %d}`, d.NextLabel+1)
  1531  		}
  1532  	case "post":
  1533  		if len(parts) < 5 {
  1534  			server.BadRequest(w, r, "DVID requires number of requested labels to follow POST /nextlabel")
  1535  			return
  1536  		}
  1537  		numLabels, err := strconv.ParseUint(parts[4], 10, 64)
  1538  		if err != nil {
  1539  			server.BadRequest(w, r, err)
  1540  			return
  1541  		}
  1542  		start, end, err := d.newLabels(ctx.VersionID(), numLabels)
  1543  		if err != nil {
  1544  			server.BadRequest(w, r, err)
  1545  			return
  1546  		}
  1547  		fmt.Fprintf(w, `{"start": %d, "end": %d}`, start, end)
  1548  		versionuuid, _ := datastore.UUIDFromVersion(ctx.VersionID())
  1549  		msginfo := map[string]interface{}{
  1550  			"Action":      "post-nextlabel",
  1551  			"Start Label": start,
  1552  			"End Label":   end,
  1553  			"UUID":        string(versionuuid),
  1554  			"Timestamp":   time.Now().String(),
  1555  		}
  1556  		jsonmsg, _ := json.Marshal(msginfo)
  1557  		if err = d.PublishKafkaMsg(jsonmsg); err != nil {
  1558  			dvid.Errorf("error on sending split op to kafka: %v", err)
  1559  		}
  1560  		return
  1561  	default:
  1562  		server.BadRequest(w, r, "Unknown action %q requested: %s\n", r.Method, r.URL)
  1563  		return
  1564  	}
  1565  	timedLog.Infof("HTTP maxlabel request (%s)", r.URL)
  1566  }
  1567  
  1568  func (d *Data) handleSplitSupervoxel(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1569  	// POST <api URL>/node/<UUID>/<data name>/split-supervoxel/<supervoxel>?<options>
  1570  	if strings.ToLower(r.Method) != "post" {
  1571  		server.BadRequest(w, r, "Split requests must be POST actions.")
  1572  		return
  1573  	}
  1574  	if len(parts) < 5 {
  1575  		server.BadRequest(w, r, "ERROR: DVID requires label ID to follow 'split' command")
  1576  		return
  1577  	}
  1578  	queryStrings := r.URL.Query()
  1579  	splitStr := queryStrings.Get("split")
  1580  	remainStr := queryStrings.Get("remain")
  1581  	downscale := true
  1582  	if queryStrings.Get("downres") == "false" {
  1583  		downscale = false
  1584  	}
  1585  	var err error
  1586  	var split, remain uint64
  1587  	if splitStr != "" {
  1588  		split, err = strconv.ParseUint(splitStr, 10, 64)
  1589  		if err != nil {
  1590  			server.BadRequest(w, r, "bad split query string provided: %s", splitStr)
  1591  			return
  1592  		}
  1593  	}
  1594  	if remainStr != "" {
  1595  		remain, err = strconv.ParseUint(remainStr, 10, 64)
  1596  		if err != nil {
  1597  			server.BadRequest(w, r, "bad remain query string provided: %s", remainStr)
  1598  			return
  1599  		}
  1600  	}
  1601  
  1602  	timedLog := dvid.NewTimeLog()
  1603  
  1604  	supervoxel, err := strconv.ParseUint(parts[4], 10, 64)
  1605  	if err != nil {
  1606  		server.BadRequest(w, r, err)
  1607  		return
  1608  	}
  1609  	if supervoxel == 0 {
  1610  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be used as split target\n")
  1611  		return
  1612  	}
  1613  	info := dvid.GetModInfo(r)
  1614  	splitSupervoxel, remainSupervoxel, mutID, err := d.SplitSupervoxel(ctx.VersionID(), supervoxel, split, remain, r.Body, info, downscale)
  1615  	if err != nil {
  1616  		server.BadRequest(w, r, fmt.Sprintf("split supervoxel %d -> %d, %d: %v", supervoxel, splitSupervoxel, remainSupervoxel, err))
  1617  		return
  1618  	}
  1619  	w.Header().Set("Content-Type", "application/json")
  1620  	fmt.Fprintf(w, `{"SplitSupervoxel": %d, "RemainSupervoxel": %d, "MutationID": %d}`, splitSupervoxel, remainSupervoxel, mutID)
  1621  
  1622  	timedLog.Infof("HTTP split supervoxel of supervoxel %d request (%s)", supervoxel, r.URL)
  1623  }
  1624  
  1625  func (d *Data) handleCleave(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1626  	// POST <api URL>/node/<UUID>/<data name>/cleave/<label>
  1627  	if strings.ToLower(r.Method) != "post" {
  1628  		server.BadRequest(w, r, "Cleave requests must be POST actions.")
  1629  		return
  1630  	}
  1631  	if len(parts) < 5 {
  1632  		server.BadRequest(w, r, "ERROR: DVID requires label ID to follow 'cleave' command")
  1633  		return
  1634  	}
  1635  	timedLog := dvid.NewTimeLog()
  1636  
  1637  	label, err := strconv.ParseUint(parts[4], 10, 64)
  1638  	if err != nil {
  1639  		server.BadRequest(w, r, err)
  1640  		return
  1641  	}
  1642  	if label == 0 {
  1643  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be used as cleave target\n")
  1644  		return
  1645  	}
  1646  	modInfo := dvid.GetModInfo(r)
  1647  	cleaveLabel, mutID, err := d.CleaveLabel(ctx.VersionID(), label, modInfo, r.Body)
  1648  	if err != nil {
  1649  		server.BadRequest(w, r, err)
  1650  		return
  1651  	}
  1652  	w.Header().Set("Content-Type", "application/json")
  1653  	fmt.Fprintf(w, `{"CleavedLabel": %d, "MutationID": %d}`, cleaveLabel, mutID)
  1654  
  1655  	timedLog.Infof("HTTP cleave of label %d request (%s)", label, r.URL)
  1656  }
  1657  
  1658  func (d *Data) handleSplit(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1659  	// POST <api URL>/node/<UUID>/<data name>/split/<label>[?splitlabel=X]
  1660  	if server.NoLabelmapSplit() {
  1661  		server.BadRequest(w, r, "Split endpoint deactivated in this DVID server's configuration.")
  1662  		return
  1663  	}
  1664  	if strings.ToLower(r.Method) != "post" {
  1665  		server.BadRequest(w, r, "Split requests must be POST actions.")
  1666  		return
  1667  	}
  1668  	if len(parts) < 5 {
  1669  		server.BadRequest(w, r, "ERROR: DVID requires label ID to follow 'split' command")
  1670  		return
  1671  	}
  1672  	timedLog := dvid.NewTimeLog()
  1673  
  1674  	fromLabel, err := strconv.ParseUint(parts[4], 10, 64)
  1675  	if err != nil {
  1676  		server.BadRequest(w, r, err)
  1677  		return
  1678  	}
  1679  	if fromLabel == 0 {
  1680  		server.BadRequest(w, r, "Label 0 is protected background value and cannot be used as sparse volume.\n")
  1681  		return
  1682  	}
  1683  	info := dvid.GetModInfo(r)
  1684  	toLabel, mutID, err := d.SplitLabels(ctx.VersionID(), fromLabel, r.Body, info)
  1685  	if err != nil {
  1686  		server.BadRequest(w, r, fmt.Sprintf("split label %d: %v", fromLabel, err))
  1687  		return
  1688  	}
  1689  	w.Header().Set("Content-Type", "application/json")
  1690  	fmt.Fprintf(w, `{"label": %d, "MutationID": %d}`, toLabel, mutID)
  1691  
  1692  	timedLog.Infof("HTTP split of label %d request (%s)", fromLabel, r.URL)
  1693  }
  1694  
  1695  func (d *Data) handleMerge(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request, parts []string) {
  1696  	// POST <api URL>/node/<UUID>/<data name>/merge
  1697  	if strings.ToLower(r.Method) != "post" {
  1698  		server.BadRequest(w, r, "Merge requests must be POST actions.")
  1699  		return
  1700  	}
  1701  	timedLog := dvid.NewTimeLog()
  1702  
  1703  	data, err := ioutil.ReadAll(r.Body)
  1704  	if err != nil {
  1705  		server.BadRequest(w, r, "Bad POSTed data for merge.  Should be JSON.")
  1706  		return
  1707  	}
  1708  	var tuple labels.MergeTuple
  1709  	if err := json.Unmarshal(data, &tuple); err != nil {
  1710  		server.BadRequest(w, r, fmt.Sprintf("Bad merge op JSON: %v", err))
  1711  		return
  1712  	}
  1713  	mergeOp, err := tuple.Op()
  1714  	if err != nil {
  1715  		server.BadRequest(w, r, err)
  1716  		return
  1717  	}
  1718  	info := dvid.GetModInfo(r)
  1719  	mutID, err := d.MergeLabels(ctx.VersionID(), mergeOp, info)
  1720  	if err != nil {
  1721  		server.BadRequest(w, r, fmt.Sprintf("Error on merge: %v", err))
  1722  		return
  1723  	}
  1724  	w.Header().Set("Content-Type", "application/json")
  1725  	fmt.Fprintf(w, `{"MutationID": %d}`, mutID)
  1726  
  1727  	timedLog.Infof("HTTP merge request (%s)", r.URL)
  1728  }
  1729  
  1730  func (d *Data) handleRenumber(ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) {
  1731  	// POST <api URL>/node/<UUID>/<data name>/renumber
  1732  
  1733  	if strings.ToLower(r.Method) != "post" {
  1734  		server.BadRequest(w, r, "Renumber requests must be POST actions.")
  1735  		return
  1736  	}
  1737  	timedLog := dvid.NewTimeLog()
  1738  
  1739  	data, err := ioutil.ReadAll(r.Body)
  1740  	if err != nil {
  1741  		server.BadRequest(w, r, "Bad POSTed data for renumber.  Should be JSON.")
  1742  		return
  1743  	}
  1744  	var renumberPairs []uint64
  1745  	if err := json.Unmarshal(data, &renumberPairs); err != nil {
  1746  		server.BadRequest(w, r, fmt.Sprintf("Bad renumber op JSON, must be list of numbers: %v", err))
  1747  		return
  1748  	}
  1749  	if len(renumberPairs)%2 != 0 {
  1750  		server.BadRequest(w, r, "Bad renumber op JSON, must be even list of numbers [new1, old1, new2, old2, ...]")
  1751  		return
  1752  	}
  1753  	info := dvid.GetModInfo(r)
  1754  	for i := 0; i < len(renumberPairs); i += 2 {
  1755  		origLabel := renumberPairs[i+1]
  1756  		newLabel := renumberPairs[i]
  1757  		if _, err := d.RenumberLabels(ctx.VersionID(), origLabel, newLabel, info); err != nil {
  1758  			server.BadRequest(w, r, fmt.Sprintf("Error on merge: %v", err))
  1759  			return
  1760  		}
  1761  	}
  1762  	timedLog.Infof("HTTP renumber request (%s)", r.URL)
  1763  }