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 }