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