github.com/janelia-flyem/dvid@v1.0.0/datatype/roi/roi.go (about) 1 /* 2 Package roi implements DVID support for Region-Of-Interest operations. 3 */ 4 package roi 5 6 import ( 7 "bytes" 8 "encoding/binary" 9 "encoding/gob" 10 "encoding/json" 11 "fmt" 12 "io/ioutil" 13 "math" 14 "net/http" 15 "reflect" 16 "sort" 17 "strconv" 18 "strings" 19 "sync" 20 21 "github.com/janelia-flyem/dvid/datastore" 22 "github.com/janelia-flyem/dvid/dvid" 23 "github.com/janelia-flyem/dvid/server" 24 "github.com/janelia-flyem/dvid/storage" 25 ) 26 27 const ( 28 Version = "0.1" 29 RepoURL = "github.com/janelia-flyem/dvid/datatype/roi" 30 TypeName = "roi" 31 32 DefaultBlockSize = 32 33 ) 34 35 const HelpMessage = ` 36 API for 'roi' datatype (github.com/janelia-flyem/dvid/datatype/roi) 37 =================================================================== 38 39 Note: UUIDs referenced below are strings that may either be a unique prefix of a 40 hexadecimal UUID string (e.g., 3FA22) or a branch leaf specification that adds 41 a colon (":") followed by the case-dependent branch name. In the case of a 42 branch leaf specification, the unique UUID prefix just identifies the repo of 43 the branch, and the UUID referenced is really the leaf of the branch name. 44 For example, if we have a DAG with root A -> B -> C where C is the current 45 HEAD or leaf of the "master" (default) branch, then asking for "B:master" is 46 the same as asking for "C". If we add another version so A -> B -> C -> D, then 47 references to "B:master" now return the data from "D". 48 49 Command-line: 50 51 $ dvid repo <UUID> new roi <data name> <settings...> 52 53 Adds newly named roi data to repo with specified UUID. 54 55 Example: 56 57 $ dvid repo 3f8c new roi medulla 58 59 Arguments: 60 61 UUID Hexadecimal string with enough characters to uniquely identify a version node. 62 data name Name of data to create, e.g., "medulla" 63 settings Configuration settings in "key=value" format separated by spaces. 64 65 Configuration Settings (case-insensitive keys) 66 67 Versioned "true" or "false" (default) 68 BlockSize Size in pixels (default: %d) 69 70 ------------------ 71 72 HTTP API (Level 2 REST): 73 74 Note that browsers support HTTP PUT and DELETE via javascript but only GET/POST are 75 included in HTML specs. For ease of use in constructing clients, HTTP POST is used 76 to create or modify resources in an idempotent fashion. 77 78 GET <api URL>/node/<UUID>/<data name>/help 79 80 Returns data-specific help message. 81 82 83 GET <api URL>/node/<UUID>/<data name>/info 84 POST <api URL>/node/<UUID>/<data name>/info 85 86 Retrieves or puts data properties. 87 88 Example: 89 90 GET <api URL>/node/3f8c/stuff/info 91 92 Returns JSON with configuration settings. 93 94 Arguments: 95 96 UUID Hexadecimal string with enough characters to uniquely identify a version node. 97 data name Name of roi data. 98 99 100 GET <api URL>/node/<UUID>/<data name>/roi 101 POST <api URL>/node/<UUID>/<data name>/roi 102 DEL <api URL>/node/<UUID>/<data name>/roi 103 104 Performs operations on an ROI depending on the HTTP verb. 105 106 Example: 107 108 GET <api URL>/node/3f8c/medulla/roi 109 110 Returns the data associated with the "medulla" ROI at version 3f8c. 111 If an ROI is currently being created asynchronously, e.g., during an imageblk 112 foreground command, then a HTTP status code 206 (Partial Content) is returned 113 until the ROI is completely stored (HTTP status code 200). 114 115 The "Content-type" of the HTTP response (and usually the request) are 116 "application/json" for arbitrary binary data. Returns a list of 4-tuples: 117 118 "[[0, 0, 0, 1], [0, 2, 3, 5], [0, 2, 8, 9], [1, 2, 3, 4]]" 119 120 Each element is expressed as [z, y, x0, x1], which represents blocks with the block coordinates 121 (x0, y, z) to (x1, y, z). Each block is a chunking of voxel space using the BlockSize for 122 the ROI. 123 124 Arguments: 125 126 UUID Hexadecimal string with enough characters to uniquely identify a version node. 127 data name Name of ROI data to save/modify or get. 128 129 GET <api URL>/node/<UUID>/<data name>/mask/0_1_2/<size>/<offset> 130 131 Returns a binary volume in ZYX order (increasing X is contiguous in array) same as format of 132 the nD voxels GET request. The returned payload is marked as "octet-stream". 133 134 The request must have size and offset arguments (both must be given if included) similar 135 to the nD voxels GET request. Currently, only the 3d GET is implemented, although in the 136 future this endpoint will parallel voxel GET request. 137 138 Example: 139 140 GET <api URL>/node/3f8c/myroi/mask/0_1_2/512_512_256/100_200_300 141 142 Returns a binary volume with non-zero elements for voxels within ROI. The binary volume 143 has size 512 x 512 x 256 voxels and an offset of (100, 200, 300). 144 145 146 POST <api URL>/node/<UUID>/<data name>/ptquery 147 148 Determines whether a list of 3d points (voxel coordinates) in JSON format sent by POST is within 149 the ROI. Returns a list of true/false answers for each point in the same sequence as the POSTed 150 list. The send format is: 151 152 [[x0, y0, z0], [x1, y1, z1], ...] 153 154 The "Content-type" of the HTTP response (and usually the request) are 155 "application/json" for arbitrary binary data. Example: 156 157 Sent: "[[0, 100, 910], [0, 121, 900]]" 158 159 Returned: "[false, true]" 160 161 162 GET <api URL>/node/<UUID>/<data name>/partition?batchsize=8 163 164 Returns JSON of subvolumes that are batchsize^3 blocks in volume and cover the ROI. 165 166 Query-string Options: 167 168 batchsize Number of blocks along each axis to batch to make one subvolume (default = 8) 169 optimized If "true" or "on", partioning returns non-fixed sized subvolumes where the coverage 170 is better in terms of subvolumes having more active blocks. 171 172 TODO (API endpoints that are planned in near future) 173 174 GET <api URL>/node/<UUID>/<data name>/erode/<element size> 175 176 Returns a ROI that has been eroded with a cubic structuring element of the given size. 177 178 Example: 179 180 GET <api URL>/node/3f8c/medulla/erode/1 181 182 This returns JSON for an ROI that has been eroded by 1 block. 183 184 ` 185 186 func init() { 187 datastore.Register(NewType()) 188 189 // Need to register types that will be used to fulfill interfaces. 190 gob.Register(&Type{}) 191 gob.Register(&Data{}) 192 } 193 194 // Type embeds the datastore's Type to create a unique type for keyvalue functions. 195 type Type struct { 196 datastore.Type 197 } 198 199 // NewType returns a pointer to a new keyvalue Type with default values set. 200 func NewType() *Type { 201 dtype := new(Type) 202 dtype.Type = datastore.Type{ 203 Name: TypeName, 204 URL: RepoURL, 205 Version: Version, 206 Requirements: &storage.Requirements{ 207 Batcher: true, 208 }, 209 } 210 return dtype 211 } 212 213 // --- TypeService interface --- 214 215 // NewData returns a pointer to new ROI data with default values. 216 func (dtype *Type) NewDataService(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (datastore.DataService, error) { 217 basedata, err := datastore.NewDataService(dtype, uuid, id, name, c) 218 if err != nil { 219 return nil, err 220 } 221 s, found, err := c.GetString("BlockSize") 222 if err != nil { 223 return nil, err 224 } 225 var blockSize dvid.Point3d 226 if found { 227 pt, err := dvid.StringToPoint(s, ",") 228 if err != nil { 229 return nil, err 230 } 231 if pt.NumDims() != 3 { 232 return nil, fmt.Errorf("BlockSize must be 3d, not %dd", pt.NumDims()) 233 } 234 blockSize, _ = pt.(dvid.Point3d) 235 } else { 236 blockSize = dvid.Point3d{DefaultBlockSize, DefaultBlockSize, DefaultBlockSize} 237 } 238 d := &Data{ 239 Data: basedata, 240 Properties: Properties{blockSize, math.MaxInt32, math.MinInt32}, 241 } 242 return d, nil 243 } 244 245 func (dtype *Type) Help() string { 246 return fmt.Sprintf(HelpMessage, DefaultBlockSize) 247 } 248 249 // Properties are additional properties for keyvalue data instances beyond those 250 // in standard datastore.Data. These will be persisted to metadata storage. 251 type Properties struct { 252 BlockSize dvid.Point3d 253 254 // Minimum Block Coord Z for ROI 255 MinZ int32 256 257 // Maximum Block Coord Z for ROI 258 MaxZ int32 259 } 260 261 // Immutable is an ROI fixed to a particular version that you can check 262 // voxel coordinates against. 263 type Immutable struct { 264 version dvid.VersionID 265 blockSize dvid.Point3d 266 blocks map[dvid.IZYXString]struct{} 267 } 268 269 func (i Immutable) VoxelWithin(p dvid.Point3d) bool { 270 izyx := p.ToBlockIZYXString(i.blockSize) 271 _, found := i.blocks[izyx] 272 return found 273 } 274 275 // ImmutableBySpec returns an Immutable ROI (or nil if not available) given 276 // a name and uuid using string format "<roiname>,<uuid>" 277 func ImmutableBySpec(spec string) (*Immutable, error) { 278 d, v, found, err := DataBySpec(spec) 279 if err != nil { 280 return nil, err 281 } 282 if !found { 283 return nil, nil 284 } 285 286 // Read the ROI from this version. 287 spans, err := d.GetSpans(v) 288 if err != nil { 289 return nil, err 290 } 291 292 // Setup the immutable. 293 im := Immutable{ 294 version: v, 295 blockSize: d.BlockSize, 296 blocks: make(map[dvid.IZYXString]struct{}), 297 } 298 for _, span := range spans { 299 z, y, x0, x1 := span[0], span[1], span[2], span[3] 300 for x := x0; x <= x1; x++ { 301 c := dvid.ChunkPoint3d{x, y, z} 302 izyx := c.ToIZYXString() 303 im.blocks[izyx] = struct{}{} 304 } 305 } 306 return &im, nil 307 } 308 309 // Data embeds the datastore's Data and extends it with keyvalue properties (none for now). 310 type Data struct { 311 *datastore.Data 312 datastore.Updater 313 Properties 314 315 sync.RWMutex 316 } 317 318 // IsMutationRequest overrides the default behavior to specify POST /ptquery as an immutable 319 // request. 320 func (d *Data) IsMutationRequest(action, endpoint string) bool { 321 lc := strings.ToLower(action) 322 if endpoint == "ptquery" && lc == "post" { 323 return false 324 } 325 return d.Data.IsMutationRequest(action, endpoint) // default for rest. 326 } 327 328 // DescribeTKeyClass returns a string explanation of what a particular TKeyClass 329 // is used for. Implements the datastore.TKeyClassDescriber interface. 330 func (d *Data) DescribeTKeyClass(tkc storage.TKeyClass) string { 331 return "ROI block + span key" 332 } 333 334 // CopyPropertiesFrom copies the data instance-specific properties from a given 335 // data instance into the receiver's properties. Fulfills the datastore.PropertyCopier interface. 336 func (d *Data) CopyPropertiesFrom(src datastore.DataService, fs storage.FilterSpec) error { 337 d2, ok := src.(*Data) 338 if !ok { 339 return fmt.Errorf("unable to copy properties from non-roi data %q", src.DataName()) 340 } 341 d.Properties.BlockSize = d2.Properties.BlockSize 342 343 // TODO -- Handle mutable data that could be potentially altered by filter. 344 d.Properties.MinZ = d2.Properties.MinZ 345 d.Properties.MaxZ = d2.Properties.MaxZ 346 347 return nil 348 } 349 350 // DataBySpec returns a ROI Data based on a string specification of the form 351 // "<roiname>,<uuid>". If the given string is not parsable, the "found" return value is false. 352 func DataBySpec(spec string) (d *Data, v dvid.VersionID, found bool, err error) { 353 roispec := strings.Split(spec, ",") 354 if len(roispec) != 2 { 355 err = fmt.Errorf("Expect ROI filters to have format %q, but got %q", "roi:<roiname>,<uuid>", spec) 356 return 357 } 358 roiName := dvid.InstanceName(roispec[0]) 359 _, v, err = datastore.MatchingUUID(roispec[1]) 360 if err != nil { 361 return 362 } 363 var data datastore.DataService 364 data, err = datastore.GetDataByVersionName(v, roiName) 365 if err != nil { 366 return 367 } 368 var ok bool 369 d, ok = data.(*Data) 370 if !ok { 371 err = fmt.Errorf("Data instance %q is not ROI instance", roiName) 372 return 373 } 374 found = true 375 return 376 } 377 378 // DataByFilter returns a ROI Data based on a string specification of the form 379 // "roi:<roiname>,<uuid>". If the given string is not parsable, the "found" return value is false. 380 func DataByFilter(spec storage.FilterSpec) (d *Data, v dvid.VersionID, found bool, err error) { 381 filterval, found := spec.GetFilterSpec("roi") 382 if !found { 383 return 384 } 385 return DataBySpec(filterval) 386 } 387 388 // Equals returns false if any version of the ROI is different 389 func (d *Data) Equals(d2 *Data) bool { 390 if !d.Data.Equals(d2.Data) || !reflect.DeepEqual(d.Properties, d2.Properties) { 391 return false 392 } 393 return true 394 } 395 396 // GetByUUIDName returns a pointer to ROI data given a version (UUID) and data name. 397 func GetByUUIDName(uuid dvid.UUID, name dvid.InstanceName) (*Data, error) { 398 source, err := datastore.GetDataByUUIDName(uuid, name) 399 if err != nil { 400 return nil, err 401 } 402 data, ok := source.(*Data) 403 if !ok { 404 return nil, fmt.Errorf("Instance '%s' is not a ROI datatype!", name) 405 } 406 return data, nil 407 } 408 409 func (d *Data) MarshalJSON() ([]byte, error) { 410 return json.Marshal(struct { 411 Base *datastore.Data 412 Extended Properties 413 }{ 414 d.Data, 415 d.Properties, 416 }) 417 } 418 419 func (d *Data) GobDecode(b []byte) error { 420 buf := bytes.NewBuffer(b) 421 dec := gob.NewDecoder(buf) 422 if err := dec.Decode(&(d.Data)); err != nil { 423 return err 424 } 425 if err := dec.Decode(&(d.Properties)); err != nil { 426 return err 427 } 428 return nil 429 } 430 431 func (d *Data) GobEncode() ([]byte, error) { 432 var buf bytes.Buffer 433 enc := gob.NewEncoder(&buf) 434 if err := enc.Encode(d.Data); err != nil { 435 return nil, err 436 } 437 if err := enc.Encode(d.Properties); err != nil { 438 return nil, err 439 } 440 return buf.Bytes(), nil 441 } 442 443 type ZRange struct { 444 MinZ, MaxZ int32 445 } 446 447 const ( 448 // keyUnknown should never be used and is a check for corrupt or incorrectly set keys 449 keyUnknown storage.TKeyClass = iota 450 451 // keyROI are keys for ROI RLEs 452 keyROI = 90 453 ) 454 455 var ( 456 minIndexRLE = indexRLE{dvid.MinIndexZYX, 0} 457 maxIndexRLE = indexRLE{dvid.MaxIndexZYX, math.MaxUint32} 458 ) 459 460 // indexRLE is the key component for block indices included in an ROI. 461 // Because we use dvid.IndexZYX for index byte slices, we know 462 // the key ordering will be Z, then Y, then X0 (and then X1). 463 type indexRLE struct { 464 start dvid.IndexZYX 465 span uint32 // the span along X 466 } 467 468 func (i *indexRLE) Bytes() []byte { 469 buf := new(bytes.Buffer) 470 _, err := buf.Write(i.start.Bytes()) 471 if err != nil { 472 dvid.Errorf("Error in roi.go, indexRLE.Bytes(): %v\n", err) 473 } 474 binary.Write(buf, binary.BigEndian, i.span) 475 return buf.Bytes() 476 } 477 478 func (i *indexRLE) IndexFromBytes(b []byte) error { 479 if len(b) != 16 { 480 return fmt.Errorf("Illegal byte length (%d) for ROI RLE Index", len(b)) 481 } 482 if err := i.start.IndexFromBytes(b[0:12]); err != nil { 483 return err 484 } 485 i.span = binary.BigEndian.Uint32(b[12:]) 486 return nil 487 } 488 489 func minIndexByBlockZ(z int32) indexRLE { 490 return indexRLE{dvid.IndexZYX{math.MinInt32, math.MinInt32, z}, 0} 491 } 492 493 func maxIndexByBlockZ(z int32) indexRLE { 494 return indexRLE{dvid.IndexZYX{math.MaxInt32, math.MaxInt32, z}, math.MaxUint32} 495 } 496 497 // Returns all (z, y, x0, x1) Spans in sorted order: z, then y, then x0. 498 func getSpans(ctx *datastore.VersionedCtx, minIndex, maxIndex indexRLE) ([]dvid.Span, error) { 499 db, err := ctx.GetOrderedKeyValueDB() 500 if err != nil { 501 return nil, err 502 } 503 spans := []dvid.Span{} 504 505 var f storage.ChunkFunc = func(chunk *storage.Chunk) error { 506 ibytes, err := chunk.K.ClassBytes(keyROI) 507 if err != nil { 508 return err 509 } 510 index := new(indexRLE) 511 if err = index.IndexFromBytes(ibytes); err != nil { 512 return fmt.Errorf("Unable to get indexRLE out of []byte encoding: %v\n", err) 513 } 514 z := index.start.Value(2) 515 y := index.start.Value(1) 516 x0 := index.start.Value(0) 517 x1 := x0 + int32(index.span) - 1 518 spans = append(spans, dvid.Span{z, y, x0, x1}) 519 return nil 520 } 521 mintk := storage.NewTKey(keyROI, minIndex.Bytes()) 522 maxtk := storage.NewTKey(keyROI, maxIndex.Bytes()) 523 err = db.ProcessRange(ctx, mintk, maxtk, &storage.ChunkOp{}, f) 524 return spans, err 525 } 526 527 // VoxelBoundsInside returns true if the given voxel extents intersects the spans. 528 func VoxelBoundsInside(e dvid.Extents3d, blocksize dvid.Point3d, spans []dvid.Span) (bool, error) { 529 // Convert the voxel bounds to block coordinates 530 emin := e.MinPoint.Chunk(blocksize).(dvid.ChunkPoint3d) 531 emax := e.MaxPoint.Chunk(blocksize).(dvid.ChunkPoint3d) 532 533 // Iterate through the spans to see if there's intersection between 534 // the extents and span. 535 for _, span := range spans { 536 bz, by, bx0, bx1 := span[0], span[1], span[2], span[3] 537 if bz > emax[2] { 538 return false, nil 539 } 540 if bz < emin[2] || by < emin[1] || bx1 < emin[0] { 541 continue 542 } 543 if by > emax[1] || bx0 > emax[0] { 544 continue 545 } 546 return true, nil 547 } 548 return false, nil 549 } 550 551 // GetSpans returns all (z, y, x0, x1) Spans in sorted order: z, then y, then x0. 552 func GetSpans(ctx *datastore.VersionedCtx) ([]dvid.Span, error) { 553 return getSpans(ctx, minIndexRLE, maxIndexRLE) 554 } 555 556 // GetSpans returns all (z, y, x0, x1) Spans in sorted order: z, then y, then x0. 557 func (d *Data) GetSpans(v dvid.VersionID) ([]dvid.Span, error) { 558 ctx := datastore.NewVersionedCtx(d, v) 559 return getSpans(ctx, minIndexRLE, maxIndexRLE) 560 } 561 562 // Get returns a JSON-encoded byte slice of the ROI in the form of 4-Spans, 563 // where each Span is [z, y, xstart, xend] 564 func Get(ctx *datastore.VersionedCtx) ([]byte, error) { 565 spans, err := GetSpans(ctx) 566 if err != nil { 567 return nil, err 568 } 569 jsonBytes, err := json.Marshal(spans) 570 if err != nil { 571 return nil, err 572 } 573 return jsonBytes, nil 574 } 575 576 // Delete removes an ROI. 577 func (d *Data) Delete(ctx storage.VersionedCtx) error { 578 db, err := datastore.GetOrderedKeyValueDB(d) 579 if err != nil { 580 return err 581 } 582 583 // We only want one PUT on given version for given data to prevent interleaved PUTs. 584 putMutex := ctx.Mutex() 585 putMutex.Lock() 586 defer putMutex.Unlock() 587 588 d.MinZ = math.MaxInt32 589 d.MaxZ = math.MinInt32 590 if err := datastore.SaveDataByVersion(ctx.VersionID(), d); err != nil { 591 return fmt.Errorf("error in trying to save repo on roi extent change: %v", err) 592 } 593 594 // Delete all spans for this ROI for just this version by tombstoning. 595 begIndex := indexRLE{ 596 start: dvid.MinIndexZYX, 597 span: 0, 598 } 599 begTk := storage.NewTKey(keyROI, begIndex.Bytes()) 600 endIndex := indexRLE{ 601 start: dvid.MaxIndexZYX, 602 span: math.MaxUint32, 603 } 604 endTk := storage.NewTKey(keyROI, endIndex.Bytes()) 605 return db.DeleteRange(ctx, begTk, endTk) 606 } 607 608 // PutSpans saves a slice of spans representing an ROI into the datastore. 609 // If the init parameter is true, all previous spans of this ROI are deleted before 610 // writing these spans. 611 func (d *Data) PutSpans(versionID dvid.VersionID, spans []dvid.Span, init bool) error { 612 ctx := datastore.NewVersionedCtx(d, versionID) 613 db, err := datastore.GetOrderedKeyValueDB(d) 614 if err != nil { 615 return err 616 } 617 d.StartUpdate() 618 defer d.StopUpdate() 619 620 d.Lock() 621 defer d.Unlock() 622 623 // Delete the old key/values 624 if init { 625 if err := d.Delete(ctx); err != nil { 626 return err 627 } 628 } 629 630 // Make sure our small data store can do batching. 631 batcher, ok := db.(storage.KeyValueBatcher) 632 if !ok { 633 return fmt.Errorf("Unable to store ROI: small data store can't do batching!") 634 } 635 636 // We only want one PUT on given version for given data to prevent interleaved PUTs. 637 putMutex := ctx.Mutex() 638 putMutex.Lock() 639 640 // Save new extents after finished. 641 defer func() { 642 err := datastore.SaveDataByVersion(ctx.VersionID(), d) 643 if err != nil { 644 dvid.Errorf("Error in trying to save repo on roi extent change: %v\n", err) 645 } 646 putMutex.Unlock() 647 }() 648 649 // Put the new key/values 650 const BATCH_SIZE = 10000 651 batch := batcher.NewBatch(ctx) 652 for i, span := range spans { 653 if span[0] < d.MinZ { 654 d.MinZ = span[0] 655 } 656 if span[0] > d.MaxZ { 657 d.MaxZ = span[0] 658 } 659 if span[3] < span[2] { 660 return fmt.Errorf("Got weird span %v. span[3] (X1) < span[2] (X0)", span) 661 } 662 index := indexRLE{ 663 start: dvid.IndexZYX{span[2], span[1], span[0]}, 664 span: uint32(span[3] - span[2] + 1), 665 } 666 tk := storage.NewTKey(keyROI, index.Bytes()) 667 batch.Put(tk, dvid.EmptyValue()) 668 if (i+1)%BATCH_SIZE == 0 { 669 if err := batch.Commit(); err != nil { 670 return fmt.Errorf("Error on batch PUT at span %d: %v\n", i, err) 671 } 672 batch = batcher.NewBatch(ctx) 673 } 674 } 675 if len(spans)%BATCH_SIZE != 0 { 676 if err := batch.Commit(); err != nil { 677 return fmt.Errorf("Error on last batch PUT: %v\n", err) 678 } 679 } 680 return nil 681 } 682 683 // PutJSON saves JSON-encoded data representing an ROI into the datastore. 684 func (d *Data) PutJSON(v dvid.VersionID, jsonBytes []byte) error { 685 spans := []dvid.Span{} 686 err := json.Unmarshal(jsonBytes, &spans) 687 if err != nil { 688 return fmt.Errorf("Error trying to parse POSTed JSON: %v", err) 689 } 690 if err := d.PutSpans(v, spans, true); err != nil { 691 return err 692 } 693 return nil 694 } 695 696 // Returns the voxel range normalized to begVoxel offset and constrained by block span. 697 func voxelRange(blockSize, begBlock, endBlock, begVoxel, endVoxel int32) (int32, int32) { 698 v0 := begBlock * blockSize 699 if v0 < begVoxel { 700 v0 = begVoxel 701 } 702 v1 := (endBlock+1)*blockSize - 1 703 if v1 > endVoxel { 704 v1 = endVoxel 705 } 706 v0 -= begVoxel 707 v1 -= begVoxel 708 return v0, v1 709 } 710 711 // GetMask returns a binary volume of subvol size where each element is 1 if inside the ROI 712 // and 0 if outside the ROI. 713 func (d *Data) GetMask(ctx *datastore.VersionedCtx, subvol *dvid.Subvolume) ([]byte, error) { 714 pt0 := subvol.StartPoint() 715 pt1 := subvol.EndPoint() 716 minBlockZ := pt0.Value(2) / d.BlockSize[2] 717 maxBlockZ := pt1.Value(2) / d.BlockSize[2] 718 minBlockY := pt0.Value(1) / d.BlockSize[1] 719 maxBlockY := pt1.Value(1) / d.BlockSize[1] 720 minBlockX := pt0.Value(0) / d.BlockSize[0] 721 maxBlockX := pt1.Value(0) / d.BlockSize[0] 722 723 minIndex := minIndexByBlockZ(minBlockZ) 724 maxIndex := maxIndexByBlockZ(maxBlockZ) 725 726 spans, err := getSpans(ctx, minIndex, maxIndex) 727 if err != nil { 728 return nil, err 729 } 730 731 // Allocate the mask volume. 732 data := make([]uint8, subvol.NumVoxels()) 733 size := subvol.Size() 734 nx := size.Value(0) 735 nxy := size.Value(1) * nx 736 737 // Fill the mask volume 738 for _, span := range spans { 739 // Handle out of range blocks 740 if span[0] < minBlockZ { 741 continue 742 } 743 if span[0] > maxBlockZ { 744 break 745 } 746 if span[1] < minBlockY || span[1] > maxBlockY { 747 continue 748 } 749 if span[3] < minBlockX || span[2] > maxBlockX { 750 continue 751 } 752 753 // Get the voxel range for this span, including limits based on subvolume. 754 x0, x1 := voxelRange(d.BlockSize[0], span[2], span[3], pt0.Value(0), pt1.Value(0)) 755 y0, y1 := voxelRange(d.BlockSize[1], span[1], span[1], pt0.Value(1), pt1.Value(1)) 756 z0, z1 := voxelRange(d.BlockSize[2], span[0], span[0], pt0.Value(2), pt1.Value(2)) 757 758 // Write the mask 759 for z := z0; z <= z1; z++ { 760 for y := y0; y <= y1; y++ { 761 i := z*nxy + y*nx + x0 762 for x := x0; x <= x1; x++ { 763 data[i] = 1 764 i++ 765 } 766 } 767 } 768 } 769 return data, nil 770 } 771 772 // Returns the current span index and whether given point is included in span. 773 func seekSpan(pt dvid.ChunkPoint3d, spans []dvid.Span, curSpanI int) (int, bool) { 774 numSpans := len(spans) 775 if curSpanI >= numSpans { 776 return curSpanI, false 777 } 778 779 // Keep going through spans until we are equal to or past the chunk point. 780 for { 781 curSpan := spans[curSpanI] 782 if curSpan.LessChunkPoint3d(pt) { 783 curSpanI++ 784 } else { 785 if curSpan.Includes(pt) { 786 return curSpanI, true 787 } else { 788 return curSpanI, false 789 } 790 } 791 if curSpanI >= numSpans { 792 return curSpanI, false 793 } 794 } 795 } 796 797 // PointQuery checks if a JSON-encoded list of voxel points are within an ROI. 798 // It returns a JSON list of bools, each corresponding to the original list of points. 799 func (d *Data) PointQuery(ctx *datastore.VersionedCtx, jsonBytes []byte) ([]byte, error) { 800 list, err := dvid.ListChunkPoint3dFromVoxels(jsonBytes, d.BlockSize) 801 if err != nil { 802 return nil, err 803 } 804 sort.Sort((*dvid.ByZYX)(list)) 805 806 // Get the ROI. The spans are ordered in z, y, then x0. 807 spans, err := GetSpans(ctx) 808 if err != nil { 809 return nil, err 810 } 811 812 // Iterate through each query point, using the ordering to make the search more efficient. 813 inclusions := make([]bool, len(list.Points)) 814 var included bool 815 curSpan := 0 816 for i, pt := range list.Points { 817 origIndex := list.Indices[i] 818 curSpan, included = seekSpan(pt, spans, curSpan) 819 inclusions[origIndex] = included 820 } 821 822 // Convert to JSON 823 inclusionsJSON, err := json.Marshal(inclusions) 824 if err != nil { 825 return nil, err 826 } 827 return inclusionsJSON, nil 828 } 829 830 type subvolumesT struct { 831 NumTotalBlocks uint64 832 NumActiveBlocks uint64 833 NumSubvolumes int32 834 ROI dvid.ChunkExtents3d 835 Subvolumes []subvolumeT 836 } 837 838 type subvolumeT struct { 839 dvid.Extents3d 840 dvid.ChunkExtents3d 841 TotalBlocks uint64 842 ActiveBlocks uint64 843 } 844 845 type layerT struct { 846 activeBlocks []*indexRLE 847 minX, maxX int32 848 minY, maxY int32 849 minZ, maxZ int32 850 } 851 852 func (d *Data) newLayer(z0, z1 int32) *layerT { 853 return &layerT{ 854 []*indexRLE{}, 855 math.MaxInt32, math.MinInt32, 856 math.MaxInt32, math.MinInt32, 857 z0, z1, 858 } 859 } 860 861 func (layer *layerT) extend(rle *indexRLE) { 862 layer.activeBlocks = append(layer.activeBlocks, rle) 863 864 y := rle.start.Value(1) 865 x0 := rle.start.Value(0) 866 x1 := x0 + int32(rle.span) - 1 867 868 if layer.minX > x0 { 869 layer.minX = x0 870 } 871 if layer.maxX < x1 { 872 layer.maxX = x1 873 } 874 if layer.minY > y { 875 layer.minY = y 876 } 877 if layer.maxY < y { 878 layer.maxY = y 879 } 880 } 881 882 func getPadding(x0, x1, batchsize int32) (leftPad, rightPad int32) { 883 var padding int32 884 overage := (x1 - x0 + 1) % batchsize 885 if overage == 0 { 886 padding = 0 887 } else { 888 padding = batchsize - overage 889 } 890 leftPad = padding / 2 891 rightPad = padding - leftPad 892 return 893 } 894 895 // For a slice of RLEs, return the min and max block Y coordinate 896 func getYRange(blocks []*indexRLE) (minY, maxY int32, found bool) { 897 minY = math.MaxInt32 898 maxY = math.MinInt32 899 for _, rle := range blocks { 900 if rle.start[1] < minY { 901 minY = rle.start[1] 902 } 903 if rle.start[1] > maxY { 904 maxY = rle.start[1] 905 } 906 found = true 907 } 908 return 909 } 910 911 // Return range of x for all spans within the given range of y 912 func getXRange(blocks []*indexRLE, minY, maxY int32) (minX, maxX int32, actives []*indexRLE) { 913 minX = math.MaxInt32 914 maxX = math.MinInt32 915 actives = []*indexRLE{} 916 for i, rle := range blocks { 917 if rle.start[1] >= minY && rle.start[1] <= maxY { 918 if rle.start[0] < minX { 919 minX = rle.start[0] 920 } 921 x1 := rle.start[0] + int32(rle.span) - 1 922 if x1 > maxX { 923 maxX = x1 924 } 925 actives = append(actives, blocks[i]) 926 } 927 } 928 return 929 } 930 931 func findActives(blocks []*indexRLE, minX, maxX int32) uint64 { 932 var numActive uint64 933 for _, rle := range blocks { 934 spanBeg := rle.start[0] 935 if spanBeg > maxX { 936 continue 937 } 938 spanEnd := spanBeg + int32(rle.span) - 1 939 if spanEnd < minX { 940 continue 941 } 942 x0 := dvid.MaxInt32(minX, spanBeg) 943 x1 := dvid.MinInt32(maxX, spanEnd) 944 numActive += uint64(x1 - x0 + 1) 945 } 946 return numActive 947 } 948 949 func findXHoles(blocks []*indexRLE, minX, maxX int32) (bestBeg, bestEnd int32, found bool) { 950 nx := maxX - minX + 1 951 used := make([]bool, nx, nx) 952 953 for _, rle := range blocks { 954 spanBeg := rle.start[0] 955 if spanBeg > maxX { 956 continue 957 } 958 spanEnd := spanBeg + int32(rle.span) - 1 959 if spanEnd < minX { 960 continue 961 } 962 x0 := dvid.MaxInt32(minX, spanBeg) 963 x1 := dvid.MinInt32(maxX, spanEnd) 964 965 for x := x0; x <= x1; x++ { 966 i := x - minX 967 used[i] = true 968 } 969 } 970 971 // See if there are holes. 972 var holeSize, bestSize int32 973 var holeBeg, holeEnd int32 974 var inHole bool 975 for x := minX; x <= maxX; x++ { 976 i := x - minX 977 if !used[i] { 978 if inHole { 979 holeEnd = x 980 holeSize++ 981 } else { 982 inHole = true 983 holeBeg = x 984 holeEnd = x 985 holeSize = 1 986 } 987 } else { 988 inHole = false 989 } 990 if holeSize > bestSize { 991 bestSize = holeSize 992 bestBeg = holeBeg 993 bestEnd = holeEnd 994 } 995 } 996 if bestSize > 0 { 997 found = true 998 } 999 return 1000 } 1001 1002 func totalBlocks(minCorner, maxCorner dvid.ChunkPoint3d) uint64 { 1003 dx := uint64(maxCorner[0] - minCorner[0] + 1) 1004 dy := uint64(maxCorner[1] - minCorner[1] + 1) 1005 dz := uint64(maxCorner[2] - minCorner[2] + 1) 1006 return dx * dy * dz 1007 } 1008 1009 // Adds subvolumes based on given extents for a layer. 1010 func (d *Data) addSubvolumes(layer *layerT, subvolumes *subvolumesT, batchsize int32, merge bool) { 1011 // mergeThreshold := batchsize * batchsize * batchsize / 10 1012 minY, maxY, found := getYRange(layer.activeBlocks) 1013 if !found { 1014 return 1015 } 1016 subvolumes.ROI.ExtendDim(1, minY) 1017 subvolumes.ROI.ExtendDim(1, maxY) 1018 dy := maxY - minY + 1 1019 yleft := dy % batchsize 1020 1021 begY := minY 1022 for { 1023 if begY > maxY { 1024 break 1025 } 1026 endY := begY + batchsize - 1 1027 if yleft > 0 { 1028 endY++ 1029 yleft-- 1030 } 1031 minX, maxX, actives := getXRange(layer.activeBlocks, begY, endY) 1032 if len(actives) > 0 { 1033 subvolumes.ROI.ExtendDim(0, minX) 1034 subvolumes.ROI.ExtendDim(0, maxX) 1035 1036 dx := maxX - minX + 1 1037 xleft := dx % batchsize 1038 1039 // Create subvolumes along this row. 1040 begX := minX 1041 for { 1042 if begX > maxX { 1043 break 1044 } 1045 endX := begX + batchsize - 1 1046 if xleft > 0 { 1047 endX++ 1048 xleft-- 1049 } 1050 minCorner := dvid.ChunkPoint3d{begX, begY, layer.minZ} 1051 maxCorner := dvid.ChunkPoint3d{endX, endY, layer.maxZ} 1052 holeBeg, holeEnd, found := findXHoles(actives, begX, endX) 1053 var numActive, numTotal uint64 1054 if found && merge { 1055 // MinCorner stays same since we are extended in X 1056 if holeBeg-1 >= begX { 1057 lastI := len(subvolumes.Subvolumes) - 1 1058 subvolume := subvolumes.Subvolumes[lastI] 1059 lastCorner := dvid.ChunkPoint3d{holeBeg - 1, endY, layer.maxZ} 1060 subvolume.MaxPoint = lastCorner.MinPoint(d.BlockSize).(dvid.Point3d) 1061 subvolume.MaxChunk = lastCorner 1062 numTotal = totalBlocks(minCorner, lastCorner) 1063 numActive = findActives(actives, begX, holeBeg-1) 1064 subvolume.TotalBlocks += numTotal 1065 subvolume.ActiveBlocks += numActive 1066 subvolumes.Subvolumes[lastI] = subvolume 1067 } 1068 begX = holeEnd + 1 1069 } else { 1070 numTotal = totalBlocks(minCorner, maxCorner) 1071 numActive = findActives(actives, begX, endX) 1072 subvolume := subvolumeT{ 1073 Extents3d: dvid.Extents3d{ 1074 minCorner.MinPoint(d.BlockSize).(dvid.Point3d), 1075 maxCorner.MaxPoint(d.BlockSize).(dvid.Point3d), 1076 }, 1077 ChunkExtents3d: dvid.ChunkExtents3d{minCorner, maxCorner}, 1078 TotalBlocks: numTotal, 1079 ActiveBlocks: numActive, 1080 } 1081 subvolumes.Subvolumes = append(subvolumes.Subvolumes, subvolume) 1082 begX = endX + 1 1083 } 1084 subvolumes.NumActiveBlocks += numActive 1085 subvolumes.NumTotalBlocks += numTotal 1086 } 1087 } 1088 begY = endY + 1 1089 } 1090 } 1091 1092 // Partition returns JSON of differently sized subvolumes that attempt to distribute 1093 // the number of active blocks per subvolume. 1094 func (d *Data) Partition(ctx storage.Context, batchsize int32) ([]byte, error) { 1095 // Partition Z as perfectly as we can. 1096 dz := d.MaxZ - d.MinZ + 1 1097 zleft := dz % batchsize 1098 1099 // Adjust Z range 1100 layerBegZ := d.MinZ 1101 layerEndZ := layerBegZ + batchsize - 1 1102 1103 // Iterate through blocks in ascending Z, calculating active extents and subvolume coverage. 1104 // Keep track of current layer = batchsize of blocks in Z. 1105 var subvolumes subvolumesT 1106 subvolumes.Subvolumes = []subvolumeT{} 1107 subvolumes.ROI.MinChunk[2] = d.MinZ 1108 subvolumes.ROI.MaxChunk[2] = d.MaxZ 1109 1110 layer := d.newLayer(layerBegZ, layerEndZ) 1111 1112 db, err := datastore.GetOrderedKeyValueDB(d) 1113 if err != nil { 1114 return nil, err 1115 } 1116 merge := true 1117 var f storage.ChunkFunc = func(chunk *storage.Chunk) error { 1118 ibytes, err := chunk.K.ClassBytes(keyROI) 1119 if err != nil { 1120 return err 1121 } 1122 index := new(indexRLE) 1123 if err = index.IndexFromBytes(ibytes); err != nil { 1124 return fmt.Errorf("Unable to get indexRLE out of []byte encoding: %v\n", err) 1125 } 1126 1127 // If we are in new layer, process last one. 1128 z := index.start.Value(2) 1129 if z > layerEndZ { 1130 // Process last layer 1131 dvid.Debugf("Computing subvolumes in layer with Z %d -> %d (dz %d)\n", 1132 layer.minZ, layer.maxZ, layer.maxZ-layer.minZ+1) 1133 d.addSubvolumes(layer, &subvolumes, batchsize, merge) 1134 1135 // Init variables for next layer 1136 layerBegZ = layerEndZ + 1 1137 layerEndZ += batchsize 1138 if zleft > 0 { 1139 layerEndZ++ 1140 zleft-- 1141 } 1142 layer = d.newLayer(layerBegZ, layerEndZ) 1143 } 1144 1145 // Check this block against current layer extents 1146 layer.extend(index) 1147 return nil 1148 } 1149 mintk := storage.MinTKey(keyROI) 1150 maxtk := storage.MaxTKey(keyROI) 1151 err = db.ProcessRange(ctx, mintk, maxtk, &storage.ChunkOp{}, f) 1152 if err != nil { 1153 return nil, err 1154 } 1155 1156 // Process last incomplete layer if there is one. 1157 if len(layer.activeBlocks) > 0 { 1158 dvid.Debugf("Computing subvolumes for final layer Z %d -> %d (dz %d)\n", 1159 layer.minZ, layer.maxZ, layer.maxZ-layer.minZ+1) 1160 d.addSubvolumes(layer, &subvolumes, batchsize, merge) 1161 } 1162 subvolumes.NumSubvolumes = int32(len(subvolumes.Subvolumes)) 1163 1164 // Encode as JSON 1165 jsonBytes, err := json.MarshalIndent(subvolumes, "", " ") 1166 if err != nil { 1167 return nil, err 1168 } 1169 return jsonBytes, err 1170 } 1171 1172 // Adds subvolumes using simple algorithm centers fixed-sized subvolumes across active blocks. 1173 func (d *Data) addSubvolumesGrid(layer *layerT, subvolumes *subvolumesT, batchsize int32) { 1174 minY, maxY, found := getYRange(layer.activeBlocks) 1175 if !found { 1176 return 1177 } 1178 subvolumes.ROI.ExtendDim(1, minY) 1179 subvolumes.ROI.ExtendDim(1, maxY) 1180 dy := maxY - minY + 1 1181 yleft := dy % batchsize 1182 1183 addYtoBeg := yleft / 2 1184 addYtoEnd := yleft - addYtoBeg 1185 begY := minY - addYtoBeg 1186 endY := maxY + addYtoEnd 1187 1188 // Iterate through Y, block by block, and determine the X range. Then center subvolumes 1189 // along that X. 1190 for y0 := begY; y0 <= endY; y0 += batchsize { 1191 y1 := y0 + batchsize - 1 1192 minX, maxX, actives := getXRange(layer.activeBlocks, y0, y1) 1193 if len(actives) == 0 { 1194 continue 1195 } 1196 subvolumes.ROI.ExtendDim(0, minX) 1197 subvolumes.ROI.ExtendDim(0, maxX) 1198 1199 // For this row of subvolumes along X, position them to encompass the X range. 1200 dx := maxX - minX + 1 1201 xleft := dx % batchsize 1202 1203 addXtoBeg := xleft / 2 1204 addXtoEnd := xleft - addXtoBeg 1205 begX := minX - addXtoBeg 1206 endX := maxX + addXtoEnd 1207 1208 // Create the subvolumes along X 1209 for x0 := begX; x0 <= endX; x0 += batchsize { 1210 x1 := x0 + batchsize - 1 1211 minCorner := dvid.ChunkPoint3d{x0, y0, layer.minZ} 1212 maxCorner := dvid.ChunkPoint3d{x1, y1, layer.maxZ} 1213 1214 numTotal := totalBlocks(minCorner, maxCorner) 1215 numActive := findActives(actives, x0, x1) 1216 if numActive > 0 { 1217 subvolume := subvolumeT{ 1218 Extents3d: dvid.Extents3d{ 1219 minCorner.MinPoint(d.BlockSize).(dvid.Point3d), 1220 maxCorner.MaxPoint(d.BlockSize).(dvid.Point3d), 1221 }, 1222 ChunkExtents3d: dvid.ChunkExtents3d{minCorner, maxCorner}, 1223 TotalBlocks: numTotal, 1224 ActiveBlocks: numActive, 1225 } 1226 subvolumes.Subvolumes = append(subvolumes.Subvolumes, subvolume) 1227 } 1228 subvolumes.NumActiveBlocks += numActive 1229 subvolumes.NumTotalBlocks += numTotal 1230 } 1231 } 1232 } 1233 1234 // SimplePartition returns JSON of identically sized subvolumes arranged over ROI 1235 func (d *Data) SimplePartition(ctx storage.Context, batchsize int32) ([]byte, error) { 1236 // Partition Z as perfectly as we can. 1237 dz := d.MaxZ - d.MinZ + 1 1238 zleft := dz % batchsize 1239 1240 // Adjust Z range 1241 addZtoTop := zleft / 2 1242 layerBegZ := d.MinZ - addZtoTop 1243 layerEndZ := layerBegZ + batchsize - 1 1244 1245 // Iterate through blocks in ascending Z, calculating active extents and subvolume coverage. 1246 // Keep track of current layer = batchsize of blocks in Z. 1247 var subvolumes subvolumesT 1248 subvolumes.Subvolumes = []subvolumeT{} 1249 subvolumes.ROI.MinChunk[2] = d.MinZ 1250 subvolumes.ROI.MaxChunk[2] = d.MaxZ 1251 1252 layer := d.newLayer(layerBegZ, layerEndZ) 1253 1254 db, err := datastore.GetOrderedKeyValueDB(d) 1255 if err != nil { 1256 return nil, err 1257 } 1258 var f storage.ChunkFunc = func(chunk *storage.Chunk) error { 1259 ibytes, err := chunk.K.ClassBytes(keyROI) 1260 if err != nil { 1261 return err 1262 } 1263 index := new(indexRLE) 1264 if err = index.IndexFromBytes(ibytes); err != nil { 1265 return fmt.Errorf("Unable to get indexRLE out of []byte encoding: %v\n", err) 1266 } 1267 1268 // If we are in new layer, process last one. 1269 z := index.start.Value(2) 1270 if z > layerEndZ { 1271 // Process last layer 1272 dvid.Debugf("Computing subvolumes in layer with Z %d -> %d (dz %d)\n", layer.minZ, layer.maxZ, layer.maxZ-layer.minZ+1) 1273 d.addSubvolumesGrid(layer, &subvolumes, batchsize) 1274 1275 // Init variables for next layer 1276 layerBegZ = layerEndZ + 1 1277 layerEndZ += batchsize 1278 layer = d.newLayer(layerBegZ, layerEndZ) 1279 } 1280 1281 // Extend current layer by the current block 1282 layer.extend(index) 1283 return nil 1284 } 1285 mintk := storage.MinTKey(keyROI) 1286 maxtk := storage.MaxTKey(keyROI) 1287 err = db.ProcessRange(ctx, mintk, maxtk, &storage.ChunkOp{}, f) 1288 if err != nil { 1289 return nil, err 1290 } 1291 1292 // Process last incomplete layer if there is one. 1293 if len(layer.activeBlocks) > 0 { 1294 dvid.Debugf("Computing subvolumes for final layer Z %d -> %d (dz %d)\n", 1295 layer.minZ, layer.maxZ, layer.maxZ-layer.minZ+1) 1296 d.addSubvolumesGrid(layer, &subvolumes, batchsize) 1297 } 1298 subvolumes.NumSubvolumes = int32(len(subvolumes.Subvolumes)) 1299 1300 // Encode as JSON 1301 jsonBytes, err := json.MarshalIndent(subvolumes, "", " ") 1302 if err != nil { 1303 return nil, err 1304 } 1305 return jsonBytes, err 1306 } 1307 1308 // --- DataService interface --- 1309 1310 func (d *Data) Help() string { 1311 return fmt.Sprintf(HelpMessage, DefaultBlockSize) 1312 } 1313 1314 // DoRPC acts as a switchboard for RPC commands. 1315 func (d *Data) DoRPC(request datastore.Request, reply *datastore.Response) error { 1316 return fmt.Errorf("Unknown command. Data '%s' [%s] does not support '%s' command.", 1317 d.DataName(), d.TypeName(), request.TypeCommand()) 1318 } 1319 1320 // ServeHTTP handles all incoming HTTP requests for this data. 1321 func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) (activity map[string]interface{}) { 1322 timedLog := dvid.NewTimeLog() 1323 1324 // Break URL request into arguments 1325 url := r.URL.Path[len(server.WebAPIPath):] 1326 parts := strings.Split(url, "/") 1327 if len(parts[len(parts)-1]) == 0 { 1328 parts = parts[:len(parts)-1] 1329 } 1330 1331 if len(parts) < 4 { 1332 server.BadAPIRequest(w, r, d) 1333 return 1334 } 1335 1336 // Process help and info. 1337 switch parts[3] { 1338 case "help": 1339 w.Header().Set("Content-Type", "text/plain") 1340 fmt.Fprintln(w, d.Help()) 1341 return 1342 case "info": 1343 jsonBytes, err := d.MarshalJSON() 1344 if err != nil { 1345 server.BadRequest(w, r, err) 1346 return 1347 } 1348 w.Header().Set("Content-Type", "application/json") 1349 fmt.Fprintf(w, string(jsonBytes)) 1350 return 1351 default: 1352 } 1353 1354 // Get the key and process request 1355 var comment string 1356 command := parts[3] 1357 method := strings.ToLower(r.Method) 1358 switch command { 1359 case "roi": 1360 switch method { 1361 case "get": 1362 d.RLock() 1363 jsonBytes, err := Get(ctx) 1364 d.RUnlock() 1365 if err != nil { 1366 server.BadRequest(w, r, err) 1367 return 1368 } 1369 w.Header().Set("Content-Type", "application/json") 1370 fmt.Fprintf(w, string(jsonBytes)) 1371 comment = fmt.Sprintf("HTTP GET ROI %q: %d bytes", d.DataName(), len(jsonBytes)) 1372 case "post": 1373 data, err := ioutil.ReadAll(r.Body) 1374 if err != nil { 1375 server.BadRequest(w, r, err) 1376 return 1377 } 1378 err = d.PutJSON(ctx.VersionID(), data) 1379 if err != nil { 1380 server.BadRequest(w, r, err) 1381 return 1382 } 1383 comment = fmt.Sprintf("HTTP POST ROI %q: %d bytes", d.DataName(), len(data)) 1384 case "delete": 1385 if err := d.Delete(ctx); err != nil { 1386 server.BadRequest(w, r, err) 1387 return 1388 } 1389 comment = fmt.Sprintf("HTTP DELETE ROI %q", d.DataName()) 1390 } 1391 case "mask": 1392 if method != "get" { 1393 server.BadRequest(w, r, "ROI mask only supports GET") 1394 return 1395 } 1396 if len(parts) < 7 { 1397 server.BadRequest(w, r, "%q must be followed by shape/size/offset", command) 1398 return 1399 } 1400 shapeStr, sizeStr, offsetStr := parts[4], parts[5], parts[6] 1401 planeStr := dvid.DataShapeString(shapeStr) 1402 plane, err := planeStr.DataShape() 1403 if err != nil { 1404 server.BadRequest(w, r, err) 1405 return 1406 } 1407 switch plane.ShapeDimensions() { 1408 case 3: 1409 subvol, err := dvid.NewSubvolumeFromStrings(offsetStr, sizeStr, "_") 1410 if err != nil { 1411 server.BadRequest(w, r, err) 1412 return 1413 } 1414 data, err := d.GetMask(ctx, subvol) 1415 if err != nil { 1416 server.BadRequest(w, r, err) 1417 return 1418 } 1419 w.Header().Set("Content-type", "application/octet-stream") 1420 _, err = w.Write(data) 1421 if err != nil { 1422 server.BadRequest(w, r, err) 1423 return 1424 } 1425 default: 1426 server.BadRequest(w, r, "Currently only 3d masks ('0_1_2' shape) is supported") 1427 return 1428 } 1429 case "ptquery": 1430 switch method { 1431 case "get": 1432 server.BadRequest(w, r, "ptquery requires POST with list of points") 1433 return 1434 case "post": 1435 data, err := ioutil.ReadAll(r.Body) 1436 if err != nil { 1437 server.BadRequest(w, r, err) 1438 return 1439 } 1440 jsonBytes, err := d.PointQuery(ctx, data) 1441 if err != nil { 1442 server.BadRequest(w, r, err) 1443 return 1444 } 1445 w.Header().Set("Content-Type", "application/json") 1446 fmt.Fprintf(w, string(jsonBytes)) 1447 comment = fmt.Sprintf("HTTP POST ptquery '%s'", d.DataName()) 1448 } 1449 case "partition": 1450 if method != "get" { 1451 server.BadRequest(w, r, "partition only supports GET request") 1452 return 1453 } 1454 queryStrings := r.URL.Query() 1455 batchsizeStr := queryStrings.Get("batchsize") 1456 batchsize, err := strconv.Atoi(batchsizeStr) 1457 if err != nil { 1458 server.BadRequest(w, r, fmt.Sprintf("Error reading batchsize query string: %v", err)) 1459 return 1460 } 1461 1462 var jsonBytes []byte 1463 optimizedStr := queryStrings.Get("optimized") 1464 dvid.Infof("queryvalues = %v\n", queryStrings) 1465 if optimizedStr == "true" || optimizedStr == "on" { 1466 dvid.Infof("Perform optimized partitioning into subvolumes using batchsize %d\n", batchsize) 1467 jsonBytes, err = d.Partition(ctx, int32(batchsize)) 1468 } else { 1469 dvid.Infof("Performing simple partitioning into subvolumes using batchsize %d\n", batchsize) 1470 jsonBytes, err = d.SimplePartition(ctx, int32(batchsize)) 1471 } 1472 if err != nil { 1473 server.BadRequest(w, r, err) 1474 return 1475 } 1476 w.Header().Set("Content-Type", "application/json") 1477 fmt.Fprintf(w, string(jsonBytes)) 1478 comment = fmt.Sprintf("HTTP partition '%s' with batch size %d", d.DataName(), batchsize) 1479 default: 1480 server.BadAPIRequest(w, r, d) 1481 return 1482 } 1483 1484 timedLog.Infof(comment) 1485 return 1486 }