github.com/janelia-flyem/dvid@v1.0.0/datatype/imageblk/imageblk.go (about) 1 /* 2 Package imageblk implements DVID support for image blocks of various formats (uint8, uint16, rgba8). 3 For label data, use labelblk. 4 */ 5 package imageblk 6 7 import ( 8 "bytes" 9 "encoding/binary" 10 "encoding/gob" 11 "encoding/json" 12 "fmt" 13 "image" 14 "image/jpeg" 15 "io/ioutil" 16 "net/http" 17 "os" 18 "reflect" 19 "strconv" 20 "strings" 21 "sync" 22 23 "github.com/janelia-flyem/dvid/datastore" 24 "github.com/janelia-flyem/dvid/datatype/roi" 25 "github.com/janelia-flyem/dvid/dvid" 26 "github.com/janelia-flyem/dvid/server" 27 "github.com/janelia-flyem/dvid/storage" 28 ) 29 30 const ( 31 Version = "0.2" 32 RepoURL = "github.com/janelia-flyem/dvid/datatype/imageblk" 33 ) 34 35 const helpMessage = ` 36 API for image block datatype (github.com/janelia-flyem/dvid/datatype/imageblk) 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 ----- 50 51 Different data types are available: 52 53 uint8blk 54 rgba8blk 55 56 Command-line: 57 58 $ dvid repo <UUID> new imageblk <data name> <settings...> 59 60 Adds newly named data of the 'type name' to repo with specified UUID. 61 62 Example (note anisotropic resolution specified instead of default 8 nm isotropic): 63 64 $ dvid repo 3f8c new uint8blk mygrayscale BlockSize=32,32,32 Res=3.2,3.2,40.0 65 66 Arguments: 67 68 UUID Hexadecimal string with enough characters to uniquely identify a version node. 69 type name Data type name, e.g., "uint8" 70 data name Name of data to create, e.g., "mygrayscale" 71 settings Configuration settings in "key=value" format separated by spaces. 72 73 Configuration Settings (case-insensitive keys) 74 75 BlockSize Size in pixels (default: %d) 76 VoxelSize Resolution of voxels (default: %f) 77 VoxelUnits Resolution units (default: "nanometers") 78 Background Integer value that signifies background in any element (default: 0) 79 GridStore Store designation that gives neuroglancer precomputed specification 80 ScaleLevel Used if GridStore set. Specifies scale level (int) of resolution. 81 82 $ dvid node <UUID> <data name> load <offset> <image glob> 83 84 Initializes version node to a set of XY images described by glob of filenames. The 85 DVID server must have access to the named files. Currently, XY images are required. 86 87 Example: 88 89 $ dvid node 3f8c mygrayscale load 0,0,100 data/*.png 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 images, e.g., foo-xy-*.png 97 98 $ dvid node <UUID> <data name> put local <plane> <offset> <image glob> 99 $ dvid node <UUID> <data name> put remote <plane> <offset> <image glob> 100 101 Adds image data to a version node when the server can see the local files ("local") 102 or when the server must be sent the files via rpc ("remote"). If possible, use the 103 "load" command instead because it is much more efficient. 104 105 Example: 106 107 $ dvid node 3f8c mygrayscale put local xy 0,0,100 data/*.png 108 109 Arguments: 110 111 UUID Hexadecimal string with enough characters to uniquely identify a version node. 112 data name Name of data to add. 113 dims The axes of data extraction in form "i,j,k,..." Example: "0,2" can be XZ. 114 Slice strings ("xy", "xz", or "yz") are also accepted. 115 offset 3d coordinate in the format "x,y,z". Gives coordinate of top upper left voxel. 116 image glob Filenames of images, e.g., foo-xy-*.png 117 118 119 $ dvid node <UUID> <data name> roi <new roi data name> <background values separated by comma> 120 121 Creates a ROI consisting of all voxel blocks that are non-background. 122 123 Example: 124 125 $ dvid node 3f8c mygrayscale roi grayscale_roi 0,255 126 127 128 ------------------ 129 130 HTTP API (Level 2 REST): 131 132 GET <api URL>/node/<UUID>/<data name>/help 133 134 Returns data-specific help message. 135 136 137 GET <api URL>/node/<UUID>/<data name>/info 138 139 Retrieves or DVID-specific data properties for these voxels. 140 141 Example: 142 143 GET <api URL>/node/3f8c/grayscale/info 144 145 Returns JSON with configuration settings that include location in DVID space and 146 min/max block indices. 147 148 Arguments: 149 150 UUID Hexadecimal string with enough characters to uniquely identify a version node. 151 data name Name of voxels data. 152 153 154 GET <api URL>/node/<UUID>/<data name>/metadata 155 156 Retrieves a JSON schema (application/vnd.dvid-nd-data+json) that describes the layout 157 of bytes returned for n-d images. 158 159 POST <api URL>/node/<UUID>/<data name>/extents 160 161 Sets the extents for the image volume. This is primarily used when POSTing from multiple 162 DVID servers not sharing common metadata to a shared backend. 163 164 Extents should be in JSON in the following format: 165 { 166 "MinPoint": [0,0,0], 167 "MaxPoint": [300,400,500] 168 } 169 170 POST <api URL>/node/<UUID>/<data name>/resolution 171 172 Sets the resolution for the image volume. 173 174 Extents should be in JSON in the following format: 175 [8,8,8] 176 177 GET <api URL>/node/<UUID>/<data name>/rawkey?x=<block x>&y=<block y>&z=<block z> 178 179 Returns JSON describing hex-encoded binary key used to store a block of data at the given block coordinate: 180 181 { 182 "Key": "FF3801AD78BBD4829A3" 183 } 184 185 The query options for block x, y, and z must be supplied or this request will return an error. 186 187 GET <api URL>/node/<UUID>/<data name>/isotropic/<dims>/<size>/<offset>[/<format>][?queryopts] 188 189 Retrieves either 2d images (PNG by default) or 3d binary data, depending on the dims parameter. 190 If the underlying data is float32, then the little-endian four byte format is written as RGBA. 191 The 3d binary data response has "Content-type" set to "application/octet-stream" and is an array of 192 voxel values in ZYX order (X iterates most rapidly). 193 194 Example: 195 196 GET <api URL>/node/3f8c/grayscale/isotropic/0_1/512_256/0_0_100/jpg:80 197 198 Returns an isotropic XY slice (0th and 1st dimensions) with width (x) of 512 voxels and 199 height (y) of 256 voxels with offset (0,0,100) in JPG format with quality 80. 200 Additional processing is applied based on voxel resolutions to make sure the retrieved image 201 has isotropic pixels. For example, if an XZ image is requested and the image volume has 202 X resolution 3 nm and Z resolution 40 nm, the returned image's height will be magnified 40/3 203 relative to the raw data. 204 The example offset assumes the "grayscale" data in version node "3f8c" is 3d. 205 The "Content-type" of the HTTP response should agree with the requested format. 206 For example, returned PNGs will have "Content-type" of "image/png", and returned 207 nD data will be "application/octet-stream". 208 209 Arguments: 210 211 UUID Hexadecimal string with enough characters to uniquely identify a version node. 212 data name Name of data to add. 213 dims The axes of data extraction in form "i_j_k,..." Example: "0_2" can be XZ. 214 Slice strings ("xy", "xz", or "yz") are also accepted. 215 size Size in voxels along each dimension specified in <dims>. 216 offset Gives coordinate of first voxel using dimensionality of data. 217 format Valid formats depend on the dimensionality of the request and formats 218 available in server implementation. 219 2D: "png", "jpg" (default: "png") 220 jpg allows lossy quality setting, e.g., "jpg:80" 221 nD: uses default "octet-stream". 222 223 Query-string Options: 224 225 throttle Only works for 3d data requests. If "true", makes sure only N compute-intense operation 226 (all API calls that can be throttled) are handled. If the server can't initiate the API 227 call right away, a 503 (Service Unavailable) status code is returned. 228 229 GET <api URL>/node/<UUID>/<data name>/specificblocks[?queryopts] 230 231 Retrieves blocks corresponding to those specified in the query string. This interface 232 is useful if the blocks retrieved are not consecutive or if the backend in non ordered. 233 234 TODO: enable arbitrary compression to be specified 235 236 Example: 237 238 GET <api URL>/node/3f8c/grayscale/specificblocks?blocks=x1,y1,z2,x2,y2,z2,x3,y3,z3 239 240 This will fetch blocks at position (x1,y1,z1), (x2,y2,z2), and (x3,y3,z3). 241 The returned byte stream has a list of blocks with a leading block 242 coordinate (3 x int32) plus int32 giving the # of bytes in this block, and then the 243 bytes for the value. If blocks are unset within the span, they will not appear in the stream, 244 so the returned data will be equal to or less than spanX blocks worth of data. 245 246 The returned data format has the following format where int32 is in little endian and the bytes of 247 block data have been compressed in JPEG format. 248 249 int32 Block 1 coordinate X (Note that this may not be starting block coordinate if it is unset.) 250 int32 Block 1 coordinate Y 251 int32 Block 1 coordinate Z 252 int32 # bytes for first block (N1) 253 byte0 Bytes of block data in jpeg-compressed format. 254 byte1 255 ... 256 byteN1 257 258 int32 Block 2 coordinate X 259 int32 Block 2 coordinate Y 260 int32 Block 2 coordinate Z 261 int32 # bytes for second block (N2) 262 byte0 Bytes of block data in jpeg-compressed format. 263 byte1 264 ... 265 byteN2 266 267 ... 268 269 If no data is available for given block span, nothing is returned. 270 271 Arguments: 272 273 UUID Hexadecimal string with enough characters to uniquely identify a version node. 274 data name Name of data to add. 275 276 Query-string Options: 277 278 compression Allows retrieval of block data in default storage or as "uncompressed". 279 blocks x,y,z... block string 280 prefetch ("on" or "true") Do not actually send data, non-blocking (default "off") 281 282 283 GET <api URL>/node/<UUID>/<data name>/subvolblocks/<size>/<offset>[?queryopts] 284 285 Retrieves blocks corresponding to the extents specified by the size and offset. The 286 subvolume request must be block aligned. This is the most server-efficient way of 287 retrieving imagelblk data, where data read from the underlying storage engine 288 is written directly to the HTTP connection. 289 290 Example: 291 292 GET <api URL>/node/3f8c/segmentation/subvolblocks/64_64_64/0_0_0 293 294 If block size is 32x32x32, this call retrieves up to 8 blocks where the first potential 295 block is at 0, 0, 0. The returned byte stream has a list of blocks with a leading block 296 coordinate (3 x int32) plus int32 giving the # of bytes in this block, and then the 297 bytes for the value. If blocks are unset within the span, they will not appear in the stream, 298 so the returned data will be equal to or less than spanX blocks worth of data. 299 300 The returned data format has the following format where int32 is in little endian and the bytes of 301 block data have been compressed in JPEG format. 302 303 int32 Block 1 coordinate X (Note that this may not be starting block coordinate if it is unset.) 304 int32 Block 1 coordinate Y 305 int32 Block 1 coordinate Z 306 int32 # bytes for first block (N1) 307 byte0 Bytes of block data in jpeg-compressed format. 308 byte1 309 ... 310 byteN1 311 312 int32 Block 2 coordinate X 313 int32 Block 2 coordinate Y 314 int32 Block 2 coordinate Z 315 int32 # bytes for second block (N2) 316 byte0 Bytes of block data in jpeg-compressed format. 317 byte1 318 ... 319 byteN2 320 321 ... 322 323 If no data is available for given block span, nothing is returned. 324 325 Arguments: 326 327 UUID Hexadecimal string with enough characters to uniquely identify a version node. 328 data name Name of data to add. 329 size Size in voxels along each dimension specified in <dims>. 330 offset Gives coordinate of first voxel using dimensionality of data. 331 332 Query-string Options: 333 334 compression Allows retrieval of block data in "jpeg" (default) or "uncompressed". 335 Note that if the data isn't stored as JPEG, it cannot be requested. 336 throttle If "true", makes sure only N compute-intense operation (all API calls that can be throttled) 337 are handled. If the server can't initiate the API call right away, a 503 (Service Unavailable) 338 status code is returned. 339 340 341 342 343 344 GET <api URL>/node/<UUID>/<data name>/raw/<dims>/<size>/<offset>[/<format>][?queryopts] 345 346 Retrieves either 2d images (PNG by default) or 3d binary data, depending on the dims parameter. 347 If the underlying data is float32, then the little-endian four byte format is written as RGBA. 348 The 3d binary data response has "Content-type" set to "application/octet-stream" and is an array of 349 voxel values in ZYX order (X iterates most rapidly). 350 351 Example: 352 353 GET <api URL>/node/3f8c/grayscale/raw/0_1/512_256/0_0_100/jpg:80 354 355 Returns a raw XY slice (0th and 1st dimensions) with width (x) of 512 voxels and 356 height (y) of 256 voxels with offset (0,0,100) in JPG format with quality 80. 357 By "raw", we mean that no additional processing is applied based on voxel 358 resolutions to make sure the retrieved image has isotropic pixels. 359 The example offset assumes the "grayscale" data in version node "3f8c" is 3d. 360 The "Content-type" of the HTTP response should agree with the requested format. 361 For example, returned PNGs will have "Content-type" of "image/png", and returned 362 nD data will be "application/octet-stream". 363 364 Arguments: 365 366 UUID Hexadecimal string with enough characters to uniquely identify a version node. 367 data name Name of data to add. 368 dims The axes of data extraction in form "i_j_k,..." 369 Slice strings ("xy", "xz", or "yz") are also accepted. 370 Example: "0_2" is XZ, and "0_1_2" is a 3d subvolume. 371 size Size in voxels along each dimension specified in <dims>. 372 offset Gives coordinate of first voxel using dimensionality of data. 373 format Valid formats depend on the dimensionality of the request and formats 374 available in server implementation. 375 2D: "png", "jpg" (default: "png") 376 jpg allows lossy quality setting, e.g., "jpg:80" 377 3D: uses default "octet-stream". 378 379 Query-string Options: 380 381 roi Name of roi data instance used to mask the requested data. 382 attenuation For attenuation n, this reduces the intensity of voxels outside ROI by 2^n. 383 Valid range is n = 1 to n = 7. Currently only implemented for 8-bit voxels. 384 Default is to zero out voxels outside ROI. 385 throttle Only works for 3d data requests. If "true", makes sure only N compute-intense operation 386 (all API calls that can be throttled) are handled. If the server can't initiate the API 387 call right away, a 503 (Service Unavailable) status code is returned. 388 389 POST <api URL>/node/<UUID>/<data name>/raw/0_1_2/<size>/<offset>[?queryopts] 390 391 Puts block-aligned voxel data using the block sizes defined for this data instance. 392 For example, if the BlockSize = 32, offset and size must be multiples of 32. 393 394 Example: 395 396 POST <api URL>/node/3f8c/grayscale/raw/0_1_2/512_256_128/0_0_32 397 398 Throttling can be enabled by passing a "throttle=true" query string. Throttling makes sure 399 only one compute-intense operation (all API calls that can be throttled) is handled. 400 If the server can't initiate the API call right away, a 503 (Service Unavailable) status 401 code is returned. 402 403 Arguments: 404 405 UUID Hexadecimal string with enough characters to uniquely identify a version node. 406 data name Name of data to add. 407 size Size in voxels along each dimension specified in <dims>. 408 offset Gives coordinate of first voxel using dimensionality of data. 409 410 Query-string Options: 411 412 roi Name of roi data instance used to mask the requested data. 413 mutate Default "false" corresponds to ingestion, i.e., the first write of the given block. 414 Use "true" to indicate the POST is a mutation of prior data, which allows any 415 synced data instance to cleanup prior denormalizations. If "mutate=true", the 416 POST operations will be slower due to a required GET to retrieve past data. 417 throttle If "true", makes sure only N compute-intense operation 418 (all API calls that can be throttled) are handled. If the server can't initiate the API 419 call right away, a 503 (Service Unavailable) status code is returned. 420 421 GET <api URL>/node/<UUID>/<data name>/arb/<top left>/<top right>/<bottom left>/<res>[/<format>][?queryopts] 422 423 Retrieves non-orthogonal (arbitrarily oriented planar) image data of named 3d data 424 within a version node. Returns an image where the top left pixel corresponds to the 425 real world coordinate (not in voxel space but in space defined by resolution, e.g., 426 nanometer space). The real world coordinates are specified in "x_y_z" format, e.g., "20.3_11.8_109.4". 427 The resolution is used to determine the # pixels in the returned image. 428 429 Example: 430 431 GET <api URL>/node/3f8c/grayscale/arb/100.2_90_80.7/200.2_90_80.7/100.2_190.0_80.7/10.0/jpg:80 432 433 Arguments: 434 435 UUID Hexadecimal string with enough characters to uniquely identify a version node. 436 data name Name of data to add. 437 top left Real world coordinate (in nanometers) of top left pixel in returned image. 438 top right Real world coordinate of top right pixel. 439 bottom left Real world coordinate of bottom left pixel. 440 res The resolution/pixel that is used to calculate the returned image size in pixels. 441 format "png", "jpg" (default: "png") 442 jpg allows lossy quality setting, e.g., "jpg:80" 443 444 Query-string Options: 445 446 throttle If "true", makes sure only N compute-intense operation 447 (all API calls that can be throttled) are handled. If the server can't initiate the API 448 call right away, a 503 (Service Unavailable) status code is returned. 449 450 GET <api URL>/node/<UUID>/<data name>/blocks/<block coord>/<spanX> 451 POST <api URL>/node/<UUID>/<data name>/blocks/<block coord>/<spanX> 452 453 Retrieves or puts "spanX" blocks of uncompressed voxel data along X starting from given block coordinate. 454 455 Example: 456 457 GET <api URL>/node/3f8c/grayscale/blocks/10_20_30/8 458 459 Returns blocks where first block has given block coordinate and number 460 of blocks returned along x axis is "spanX". The data is sent in the following format: 461 462 <block 0 byte array> 463 <block 1 byte array> 464 ... 465 <block N byte array> 466 467 Each byte array iterates in X, then Y, then Z for that block. 468 469 Arguments: 470 471 UUID Hexadecimal string with enough characters to uniquely identify a version node. 472 data name Name of data to add. 473 block coord The block coordinate of the first block in X_Y_Z format. Block coordinates 474 can be derived from voxel coordinates by dividing voxel coordinates by 475 the block size for a data type. 476 ` 477 478 var ( 479 // DefaultBlockSize specifies the default size for each block of this data type. 480 DefaultBlockSize int32 = 32 481 482 DefaultRes float32 = 8 483 484 DefaultUnits = "nanometers" 485 ) 486 487 func init() { 488 // Need to register types that will be used to fulfill interfaces. 489 gob.Register(&Type{}) 490 gob.Register(&Data{}) 491 gob.Register(binary.LittleEndian) 492 gob.Register(binary.BigEndian) 493 } 494 495 // Type embeds the datastore's Type to create a unique type with voxel functions. 496 // Refinements of general voxel types can be implemented by embedding this type, 497 // choosing appropriate # of values and bytes/value, overriding functions as needed, 498 // and calling datastore.Register(). 499 // Note that these fields are invariant for all instances of this type. Fields 500 // that can change depending on the type of data (e.g., resolution) should be 501 // in the Data type. 502 type Type struct { 503 datastore.Type 504 505 // values describes the data type/label for each value within a voxel. 506 values dvid.DataValues 507 508 // can these values be interpolated? 509 interpolable bool 510 } 511 512 // NewType returns a pointer to a new imageblk Type with default values set. 513 func NewType(values dvid.DataValues, interpolable bool) Type { 514 dtype := Type{ 515 Type: datastore.Type{ 516 Requirements: &storage.Requirements{Batcher: true}, 517 }, 518 values: values, 519 interpolable: interpolable, 520 } 521 return dtype 522 } 523 524 // NewData returns a pointer to a new Voxels with default values. 525 func (dtype *Type) NewData(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (*Data, error) { 526 dvid.Infof("NewData on name %q\n", name) 527 basedata, err := datastore.NewDataService(dtype, uuid, id, name, c) 528 if err != nil { 529 return nil, err 530 } 531 var p Properties 532 p.setDefault(dtype.values, dtype.interpolable) 533 if err := p.setByConfig(c); err != nil { 534 return nil, err 535 } 536 if p.GridStore != "" { 537 gridProps, err := getGridProperties(p.GridStore, p.ScaleLevel) 538 if err != nil { 539 return nil, err 540 } 541 dvid.Infof("Got properties for scale %d of GridStore %q: %v\n", p.ScaleLevel, p.GridStore, gridProps) 542 p.MinPoint = dvid.Point3d{0, 0, 0} 543 p.MaxPoint = gridProps.VolumeSize 544 p.Resolution.Set3dNanometersFloat(gridProps.Resolution) 545 p.BlockSize = gridProps.ChunkSize 546 } 547 data := &Data{ 548 Data: basedata, 549 Properties: p, 550 } 551 dvid.Infof("Data props: %v\n", p) 552 553 return data, nil 554 } 555 556 // --- TypeService interface --- 557 558 // NewDataService returns a pointer to a new Voxels with default values. 559 func (dtype *Type) NewDataService(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (datastore.DataService, error) { 560 return dtype.NewData(uuid, id, name, c) 561 } 562 563 func (dtype *Type) Help() string { 564 return fmt.Sprintf(helpMessage, DefaultBlockSize, DefaultRes) 565 } 566 567 type bulkLoadInfo struct { 568 filenames []string 569 versionID dvid.VersionID 570 offset dvid.Point 571 extentChanged dvid.Bool 572 } 573 574 // Voxels represents subvolumes or slices and implements the ExtData interface. 575 type Voxels struct { 576 dvid.Geometry 577 578 values dvid.DataValues 579 580 // The data itself 581 data []byte 582 583 // The stride for 2d iteration in bytes between vertically adjacent pixels. 584 // For 3d subvolumes, we don't reuse standard Go images but maintain fully 585 // packed data slices, so stride isn't necessary. 586 stride int32 587 } 588 589 func NewVoxels(geom dvid.Geometry, values dvid.DataValues, data []byte, stride int32) *Voxels { 590 return &Voxels{geom, values, data, stride} 591 } 592 593 func (v *Voxels) String() string { 594 size := v.Size() 595 return fmt.Sprintf("%s of size %s @ %s", v.DataShape(), size, v.StartPoint()) 596 } 597 598 func (v *Voxels) Values() dvid.DataValues { 599 return v.values 600 } 601 602 func (v *Voxels) Data() []byte { 603 return v.data 604 } 605 606 func (v *Voxels) Stride() int32 { 607 return v.stride 608 } 609 610 func (v *Voxels) BytesPerVoxel() int32 { 611 return v.values.BytesPerElement() 612 } 613 614 func (v *Voxels) SetGeometry(geom dvid.Geometry) { 615 v.Geometry = geom 616 } 617 618 func (v *Voxels) SetValues(values dvid.DataValues) { 619 v.values = values 620 } 621 622 func (v *Voxels) SetStride(stride int32) { 623 v.stride = stride 624 } 625 626 func (v *Voxels) SetData(data []byte) { 627 v.data = data 628 } 629 630 // ------- ExtData interface implementation ------------- 631 632 func (v *Voxels) NewChunkIndex() dvid.ChunkIndexer { 633 return &dvid.IndexZYX{} 634 } 635 636 func (v *Voxels) Interpolable() bool { 637 return true 638 } 639 640 // DownRes downsamples 2d Voxels data by averaging where the down-magnification are 641 // integers. If the source image size in Voxels is not an integral multiple of the 642 // reduction factor, the edge voxels on the right and bottom side are truncated. 643 // This function modifies the Voxels data. An error is returned if a non-2d Voxels 644 // receiver is used. 645 func (v *Voxels) DownRes(magnification dvid.Point) error { 646 if v.DataShape().ShapeDimensions() != 2 { 647 return fmt.Errorf("ImageDownres() only supports 2d images at this time.") 648 } 649 // Calculate new dimensions and allocate. 650 srcW := v.Size().Value(0) 651 srcH := v.Size().Value(1) 652 reduceW, reduceH, err := v.DataShape().GetSize2D(magnification) 653 if err != nil { 654 return err 655 } 656 dstW := srcW / reduceW 657 dstH := srcH / reduceH 658 659 // Reduce the image. 660 img, err := v.GetImage2d() 661 if err != nil { 662 return err 663 } 664 img, err = img.ScaleImage(int(dstW), int(dstH)) 665 if err != nil { 666 return err 667 } 668 669 // Set data and dimensions to downres data. 670 v.data = []byte(img.Data()) 671 geom, err := dvid.NewOrthogSlice(v.DataShape(), v.StartPoint(), dvid.Point2d{dstW, dstH}) 672 if err != nil { 673 return err 674 } 675 v.Geometry = geom 676 v.stride = dstW * v.values.BytesPerElement() 677 return nil 678 } 679 680 // NewIndexIterator returns an iterator that can move across the voxel geometry, 681 // generating indices or index spans. 682 func (v *Voxels) NewIndexIterator(chunkSize dvid.Point) (dvid.IndexIterator, error) { 683 // Setup traversal 684 begVoxel, ok := v.StartPoint().(dvid.Chunkable) 685 if !ok { 686 return nil, fmt.Errorf("ExtData StartPoint() cannot handle Chunkable points.") 687 } 688 endVoxel, ok := v.EndPoint().(dvid.Chunkable) 689 if !ok { 690 return nil, fmt.Errorf("ExtData EndPoint() cannot handle Chunkable points.") 691 } 692 begBlock := begVoxel.Chunk(chunkSize).(dvid.ChunkPoint3d) 693 endBlock := endVoxel.Chunk(chunkSize).(dvid.ChunkPoint3d) 694 695 return dvid.NewIndexZYXIterator(begBlock, endBlock), nil 696 } 697 698 // GetImage2d returns a 2d image suitable for use external to DVID. 699 // TODO -- Create more comprehensive handling of endianness and encoding of 700 // multibytes/voxel data into appropriate images. 701 func (v *Voxels) GetImage2d() (*dvid.Image, error) { 702 // Make sure each value has same # of bytes or else we can't generate a go image. 703 // If so, we need to make another ExtData that knows how to convert the varying 704 // values into an appropriate go image. 705 valuesPerVoxel := int32(len(v.values)) 706 if valuesPerVoxel < 1 || valuesPerVoxel > 4 { 707 return nil, fmt.Errorf("Standard voxels type can't convert %d values/voxel into image.", 708 valuesPerVoxel) 709 } 710 bytesPerValue := v.values.ValueBytes(0) 711 for _, dataValue := range v.values { 712 if dvid.DataTypeBytes(dataValue.T) != bytesPerValue { 713 return nil, fmt.Errorf("Standard voxels type can't handle varying sized values per voxel.") 714 } 715 } 716 717 unsupported := func() error { 718 return fmt.Errorf("DVID doesn't support images for %d channels and %d bytes/channel", 719 valuesPerVoxel, bytesPerValue) 720 } 721 722 var img image.Image 723 width := v.Size().Value(0) 724 height := v.Size().Value(1) 725 sliceBytes := width * height * valuesPerVoxel * bytesPerValue 726 beg := int32(0) 727 end := beg + sliceBytes 728 data := v.Data() 729 if int(end) > len(data) { 730 return nil, fmt.Errorf("Voxels %s has insufficient amount of data to return an image.", v) 731 } 732 r := image.Rect(0, 0, int(width), int(height)) 733 switch valuesPerVoxel { 734 case 1: 735 switch bytesPerValue { 736 case 1: 737 img = &image.Gray{data[beg:end], 1 * r.Dx(), r} 738 case 2: 739 bigendian, err := v.littleToBigEndian(data[beg:end]) 740 if err != nil { 741 return nil, err 742 } 743 img = &image.Gray16{bigendian, 2 * r.Dx(), r} 744 case 4: 745 img = &image.NRGBA{data[beg:end], 4 * r.Dx(), r} 746 case 8: 747 img = &image.NRGBA64{data[beg:end], 8 * r.Dx(), r} 748 default: 749 return nil, unsupported() 750 } 751 case 4: 752 switch bytesPerValue { 753 case 1: 754 img = &image.NRGBA{data[beg:end], 4 * r.Dx(), r} 755 case 2: 756 img = &image.NRGBA64{data[beg:end], 8 * r.Dx(), r} 757 default: 758 return nil, unsupported() 759 } 760 default: 761 return nil, unsupported() 762 } 763 764 return dvid.ImageFromGoImage(img, v.Values(), v.Interpolable()) 765 } 766 767 // Properties are additional properties for image block data instances beyond those 768 // in standard datastore.Data. These will be persisted to metadata storage. 769 type Properties struct { 770 // Values describes the data type/label for each value within a voxel. 771 Values dvid.DataValues 772 773 // Interpolable is true if voxels can be interpolated when resizing. 774 Interpolable bool 775 776 // Block size for this repo 777 BlockSize dvid.Point 778 779 dvid.Resolution 780 781 // leave field in metadata but no longer updated!! 782 dvid.Extents 783 784 // Background value for data 785 Background uint8 786 787 // GridStore designates store to be used for immutable data access 788 GridStore storage.Alias 789 790 // ScaleLevel designates resolution from 0 (high-res) to an int N with 2^N down-res 791 ScaleLevel int 792 } 793 794 // getGridProperties returns the properties of a GridStore 795 func getGridProperties(storeName storage.Alias, scale int) (props storage.GridProps, err error) { 796 if storeName == "" { 797 err = fmt.Errorf("cannot get GridStore properties for a blank name") 798 return 799 } 800 var store dvid.Store 801 store, err = storage.GetStoreByAlias(storeName) 802 if err != nil { 803 return 804 } 805 gridStore, ok := store.(storage.GridStoreGetter) 806 if !ok { 807 err = fmt.Errorf("GridStore %q is not valid", storeName) 808 return 809 } 810 return gridStore.GridProperties(scale) 811 } 812 813 // gridStoreGetter either returns a gridStore or (okvDB, kvDB) as fallback, not both. 814 func (d *Data) gridStoreGetter() (gridStore storage.GridStoreGetter, okvDB storage.OrderedKeyValueDB, kvDB storage.KeyValueDB, err error) { 815 if d.GridStore != "" { 816 var store dvid.Store 817 store, err = storage.GetStoreByAlias(d.GridStore) 818 if err == nil { 819 var ok bool 820 gridStore, ok = store.(storage.GridStoreGetter) 821 if ok { 822 return 823 } 824 gridStore = nil 825 dvid.Infof("Found gridstore %q but was not a GridStoreGetter, defaulting...\n", d.GridStore) 826 } 827 } 828 okvDB, err = datastore.GetOrderedKeyValueDB(d) 829 if err != nil { 830 return 831 } 832 kvDB, err = datastore.GetKeyValueDB(d) 833 if err != nil { 834 return 835 } 836 return 837 } 838 839 func (d *Data) PropertiesWithExtents(ctx *datastore.VersionedCtx) (props Properties, err error) { 840 var verExtents dvid.Extents 841 verExtents, err = d.GetExtents(ctx) 842 if err != nil { 843 return 844 } 845 props.Values = d.Properties.Values 846 props.Interpolable = d.Properties.Interpolable 847 props.BlockSize = d.Properties.BlockSize 848 props.Resolution = d.Properties.Resolution 849 850 props.Extents.MinPoint = verExtents.MinPoint 851 props.Extents.MaxPoint = verExtents.MaxPoint 852 props.Extents.MinIndex = verExtents.MinIndex 853 props.Extents.MaxIndex = verExtents.MaxIndex 854 props.Background = d.Properties.Background 855 props.GridStore = d.Properties.GridStore 856 props.ScaleLevel = d.Properties.ScaleLevel 857 return 858 } 859 860 // CopyPropertiesFrom copies the data instance-specific properties from a given 861 // data instance into the receiver's properties. Fulfills the datastore.PropertyCopier interface. 862 func (d *Data) CopyPropertiesFrom(src datastore.DataService, fs storage.FilterSpec) error { 863 d2, ok := src.(*Data) 864 if !ok { 865 return fmt.Errorf("unable to copy properties from non-imageblk data %q", src.DataName()) 866 } 867 d.Properties.copyImmutable(&(d2.Properties)) 868 869 // TODO -- Extents are no longer used so this should be refactored 870 d.Properties.Extents = d2.Properties.Extents.Duplicate() 871 return nil 872 } 873 874 func (p *Properties) copyImmutable(p2 *Properties) { 875 p.Values = make([]dvid.DataValue, len(p2.Values)) 876 copy(p.Values, p2.Values) 877 p.Interpolable = p2.Interpolable 878 879 p.BlockSize = p2.BlockSize.Duplicate() 880 881 p.Resolution.VoxelSize = make(dvid.NdFloat32, 3) 882 copy(p.Resolution.VoxelSize, p2.Resolution.VoxelSize) 883 p.Resolution.VoxelUnits = make(dvid.NdString, 3) 884 copy(p.Resolution.VoxelUnits, p2.Resolution.VoxelUnits) 885 886 p.Background = p2.Background 887 p.GridStore = p2.GridStore 888 p.ScaleLevel = p2.ScaleLevel 889 } 890 891 // setDefault sets Voxels properties to default values. 892 func (p *Properties) setDefault(values dvid.DataValues, interpolable bool) error { 893 p.Values = make([]dvid.DataValue, len(values)) 894 copy(p.Values, values) 895 p.Interpolable = interpolable 896 897 dimensions := 3 898 size := make([]int32, dimensions) 899 for d := 0; d < dimensions; d++ { 900 size[d] = DefaultBlockSize 901 } 902 var err error 903 p.BlockSize, err = dvid.NewPoint(size) 904 if err != nil { 905 return err 906 } 907 p.Resolution.VoxelSize = make(dvid.NdFloat32, dimensions) 908 for d := 0; d < dimensions; d++ { 909 p.Resolution.VoxelSize[d] = DefaultRes 910 } 911 p.Resolution.VoxelUnits = make(dvid.NdString, dimensions) 912 for d := 0; d < dimensions; d++ { 913 p.Resolution.VoxelUnits[d] = DefaultUnits 914 } 915 return nil 916 } 917 918 // setByConfig sets Voxels properties based on type-specific keywords in the configuration. 919 // Any property not described in the config is left as is. See the Voxels help for listing 920 // of configurations. 921 func (p *Properties) setByConfig(config dvid.Config) error { 922 s, found, err := config.GetString("BlockSize") 923 if err != nil { 924 return err 925 } 926 if found { 927 p.BlockSize, err = dvid.StringToPoint(s, ",") 928 if err != nil { 929 return err 930 } 931 } 932 s, found, err = config.GetString("VoxelSize") 933 if err != nil { 934 return err 935 } 936 if found { 937 dvid.Infof("Changing resolution of voxels to %s\n", s) 938 p.Resolution.VoxelSize, err = dvid.StringToNdFloat32(s, ",") 939 if err != nil { 940 return err 941 } 942 } 943 s, found, err = config.GetString("VoxelUnits") 944 if err != nil { 945 return err 946 } 947 if found { 948 p.Resolution.VoxelUnits, err = dvid.StringToNdString(s, ",") 949 if err != nil { 950 return err 951 } 952 } 953 s, found, err = config.GetString("MinPoint") 954 if err != nil { 955 return err 956 } 957 if found { 958 p.MinPoint, err = dvid.StringToPoint(s, ",") 959 if err != nil { 960 return err 961 } 962 } 963 s, found, err = config.GetString("MaxPoint") 964 if err != nil { 965 return err 966 } 967 if found { 968 p.MaxPoint, err = dvid.StringToPoint(s, ",") 969 if err != nil { 970 return err 971 } 972 } 973 s, found, err = config.GetString("Background") 974 if err != nil { 975 return err 976 } 977 if found { 978 background, err := strconv.ParseUint(s, 10, 8) 979 if err != nil { 980 return err 981 } 982 p.Background = uint8(background) 983 } 984 s, found, err = config.GetString("GridStore") 985 if err != nil { 986 return err 987 } 988 if found { 989 gridStore := storage.Alias(s) 990 if _, err := storage.GetStoreByAlias(gridStore); err != nil { 991 return fmt.Errorf("bad store (%s) designated for GridStore: %v", s, err) 992 } 993 p.GridStore = gridStore 994 } 995 s, found, err = config.GetString("ScaleLevel") 996 if err != nil { 997 return err 998 } 999 if found { 1000 scale, err := strconv.Atoi(s) 1001 if err != nil { 1002 return err 1003 } 1004 p.ScaleLevel = scale 1005 } 1006 return nil 1007 } 1008 1009 type metadataT struct { 1010 Axes []axisT 1011 Properties Properties 1012 } 1013 1014 type axisT struct { 1015 Label string 1016 Resolution float32 1017 Units string 1018 Size int32 1019 Offset int32 1020 } 1021 1022 // NdDataSchema returns the metadata in JSON for this Data 1023 func (d *Data) NdDataMetadata(ctx *datastore.VersionedCtx) (string, error) { 1024 var err error 1025 var size, offset dvid.Point 1026 1027 dims := int(d.BlockSize().NumDims()) 1028 extents, err := d.GetExtents(ctx) 1029 if err != nil { 1030 return "", err 1031 } 1032 if extents.MinPoint == nil || extents.MaxPoint == nil { 1033 zeroPt := make([]int32, dims) 1034 size, err = dvid.NewPoint(zeroPt) 1035 if err != nil { 1036 return "", err 1037 } 1038 offset = size 1039 } else { 1040 size = extents.MaxPoint.Sub(extents.MinPoint).AddScalar(1) 1041 offset = extents.MinPoint 1042 } 1043 1044 var axesName = []string{"X", "Y", "Z", "t", "c"} 1045 var metadata metadataT 1046 metadata.Axes = []axisT{} 1047 for dim := 0; dim < dims; dim++ { 1048 metadata.Axes = append(metadata.Axes, axisT{ 1049 Label: axesName[dim], 1050 Resolution: d.Properties.Resolution.VoxelSize[dim], 1051 Units: d.Properties.Resolution.VoxelUnits[dim], 1052 Size: size.Value(uint8(dim)), 1053 Offset: offset.Value(uint8(dim)), 1054 }) 1055 } 1056 metadata.Properties, err = d.PropertiesWithExtents(ctx) 1057 if err != nil { 1058 return "", err 1059 } 1060 1061 m, err := json.Marshal(metadata) 1062 if err != nil { 1063 return "", err 1064 } 1065 return string(m), nil 1066 } 1067 1068 // serializeExtents takes extents structure and serializes and compresses it 1069 func (d *Data) serializeExtents(extents ExtentsJSON) ([]byte, error) { 1070 1071 jsonbytes, err := json.Marshal(extents) 1072 if err != nil { 1073 return nil, err 1074 } 1075 1076 compression, _ := dvid.NewCompression(dvid.Uncompressed, dvid.DefaultCompression) 1077 return dvid.SerializeData(jsonbytes, compression, dvid.NoChecksum) 1078 1079 } 1080 1081 // serializeExtents takes extents structure and serializes and compresses it 1082 func (d *Data) deserializeExtents(serdata []byte) (ExtentsJSON, error) { 1083 1084 // return blank extents if nothing set 1085 var extents ExtentsJSON 1086 if serdata == nil || len(serdata) == 0 { 1087 return extents, nil 1088 } 1089 1090 // deserialize 1091 data, _, err := dvid.DeserializeData(serdata, true) 1092 if err != nil { 1093 return extents, err 1094 } 1095 1096 // unmarshal (hardcode to 3D for now!) 1097 var extentstemp extents3D 1098 if err = json.Unmarshal(data, &extentstemp); err != nil { 1099 return extents, err 1100 } 1101 extents.MinPoint = extentstemp.MinPoint 1102 extents.MaxPoint = extentstemp.MaxPoint 1103 1104 return extents, nil 1105 } 1106 1107 // SetExtents saves JSON data giving MinPoint and MaxPoint (put operation) 1108 func (d *Data) SetExtents(ctx *datastore.VersionedCtx, uuid dvid.UUID, jsonBytes []byte) error { 1109 // unmarshal (hardcode to 3D for now!) 1110 var config extents3D 1111 var config2 ExtentsJSON 1112 1113 if err := json.Unmarshal(jsonBytes, &config); err != nil { 1114 return err 1115 } 1116 1117 // call serialize 1118 config2.MinPoint = config.MinPoint 1119 config2.MaxPoint = config.MaxPoint 1120 data, err := d.serializeExtents(config2) 1121 if err != nil { 1122 return err 1123 } 1124 1125 // put (last one wins) 1126 store, err := datastore.GetKeyValueDB(d) 1127 if err != nil { 1128 return err 1129 } 1130 return store.Put(ctx, MetaTKey(), data) 1131 } 1132 1133 // SetResolution loads JSON data giving Resolution. 1134 func (d *Data) SetResolution(uuid dvid.UUID, jsonBytes []byte) error { 1135 config := make(dvid.NdFloat32, 3) 1136 if err := json.Unmarshal(jsonBytes, &config); err != nil { 1137 return err 1138 } 1139 d.Properties.VoxelSize = config 1140 if err := datastore.SaveDataByUUID(uuid, d); err != nil { 1141 return err 1142 } 1143 return nil 1144 } 1145 1146 // ExtentsJSON is extents encoding for imageblk 1147 type ExtentsJSON struct { 1148 MinPoint dvid.Point 1149 MaxPoint dvid.Point 1150 } 1151 1152 type extents3D struct { 1153 MinPoint dvid.Point3d 1154 MaxPoint dvid.Point3d 1155 } 1156 1157 // Data embeds the datastore's Data and extends it with voxel-specific properties. 1158 type Data struct { 1159 *datastore.Data 1160 Properties 1161 sync.Mutex // to protect extent updates 1162 } 1163 1164 func (d *Data) Equals(d2 *Data) bool { 1165 if !d.Data.Equals(d2.Data) { 1166 return false 1167 } 1168 return reflect.DeepEqual(d.Properties, d2.Properties) 1169 } 1170 1171 // BlankImage initializes a blank image of appropriate size and depth for the 1172 // current data values. Returns an error if the geometry is not 2d. 1173 func (d *Data) BlankImage(dstW, dstH int32) (*dvid.Image, error) { 1174 // Make sure values for this data can be converted into an image. 1175 valuesPerVoxel := int32(len(d.Properties.Values)) 1176 if valuesPerVoxel < 1 || valuesPerVoxel > 4 { 1177 return nil, fmt.Errorf("Standard voxels type can't convert %d values/voxel into image.", 1178 valuesPerVoxel) 1179 } 1180 bytesPerValue, err := d.Properties.Values.BytesPerValue() 1181 if err != nil { 1182 return nil, err 1183 } 1184 1185 unsupported := func() error { 1186 return fmt.Errorf("DVID doesn't support images for %d channels and %d bytes/channel", 1187 valuesPerVoxel, bytesPerValue) 1188 } 1189 1190 var img image.Image 1191 stride := int(dstW * valuesPerVoxel * bytesPerValue) 1192 r := image.Rect(0, 0, int(dstW), int(dstH)) 1193 imageBytes := int(dstH) * stride 1194 data := make([]uint8, imageBytes, imageBytes) 1195 switch valuesPerVoxel { 1196 case 1: 1197 switch bytesPerValue { 1198 case 1: 1199 img = &image.Gray{ 1200 Stride: stride, 1201 Rect: r, 1202 Pix: data, 1203 } 1204 case 2: 1205 img = &image.Gray16{ 1206 Stride: stride, 1207 Rect: r, 1208 Pix: data, 1209 } 1210 case 4: 1211 img = &image.NRGBA{ 1212 Stride: stride, 1213 Rect: r, 1214 Pix: data, 1215 } 1216 case 8: 1217 img = &image.NRGBA64{ 1218 Stride: stride, 1219 Rect: r, 1220 Pix: data, 1221 } 1222 default: 1223 return nil, unsupported() 1224 } 1225 case 4: 1226 switch bytesPerValue { 1227 case 1: 1228 img = &image.NRGBA{ 1229 Stride: stride, 1230 Rect: r, 1231 Pix: data, 1232 } 1233 case 2: 1234 img = &image.NRGBA64{ 1235 Stride: stride, 1236 Rect: r, 1237 Pix: data, 1238 } 1239 default: 1240 return nil, unsupported() 1241 } 1242 default: 1243 return nil, unsupported() 1244 } 1245 1246 return dvid.ImageFromGoImage(img, d.Properties.Values, d.Properties.Interpolable) 1247 } 1248 1249 // PutLocal adds image data to a version node, altering underlying blocks if the image 1250 // intersects the block. 1251 // 1252 // The image filename glob MUST BE absolute file paths that are visible to the server. 1253 // This function is meant for mass ingestion of large data files, and it is inappropriate 1254 // to read gigabytes of data just to send it over the network to a local DVID. 1255 func (d *Data) PutLocal(request datastore.Request, reply *datastore.Response) error { 1256 timedLog := dvid.NewTimeLog() 1257 1258 // Parse the request 1259 var uuidStr, dataName, cmdStr, sourceStr, planeStr, offsetStr string 1260 filenames := request.CommandArgs(1, &uuidStr, &dataName, &cmdStr, &sourceStr, 1261 &planeStr, &offsetStr) 1262 if len(filenames) == 0 { 1263 return fmt.Errorf("Need to include at least one file to add: %s", request) 1264 } 1265 1266 // Get offset 1267 offset, err := dvid.StringToPoint(offsetStr, ",") 1268 if err != nil { 1269 return fmt.Errorf("Illegal offset specification: %s: %v", offsetStr, err) 1270 } 1271 1272 // Get list of files to add 1273 var addedFiles string 1274 if len(filenames) == 1 { 1275 addedFiles = filenames[0] 1276 } else { 1277 addedFiles = fmt.Sprintf("filenames: %s [%d more]", filenames[0], len(filenames)-1) 1278 } 1279 dvid.Debugf(addedFiles + "\n") 1280 1281 // Get plane 1282 plane, err := dvid.DataShapeString(planeStr).DataShape() 1283 if err != nil { 1284 return err 1285 } 1286 1287 // Get Repo and IDs 1288 uuid, versionID, err := datastore.MatchingUUID(uuidStr) 1289 if err != nil { 1290 return err 1291 } 1292 1293 // Load and put each image. 1294 numSuccessful := 0 1295 for _, filename := range filenames { 1296 sliceLog := dvid.NewTimeLog() 1297 img, _, err := dvid.GoImageFromFile(filename) 1298 if err != nil { 1299 return fmt.Errorf("Error after %d images successfully added: %v", numSuccessful, err) 1300 } 1301 slice, err := dvid.NewOrthogSlice(plane, offset, dvid.RectSize(img.Bounds())) 1302 if err != nil { 1303 return fmt.Errorf("Unable to determine slice: %v", err) 1304 } 1305 1306 vox, err := d.NewVoxels(slice, img) 1307 if err != nil { 1308 return err 1309 } 1310 storage.FileBytesRead <- len(vox.Data()) 1311 if err = d.IngestVoxels(versionID, d.NewMutationID(), vox, ""); err != nil { 1312 return err 1313 } 1314 sliceLog.Debugf("%s put local %s", d.DataName(), slice) 1315 numSuccessful++ 1316 offset = offset.Add(dvid.Point3d{0, 0, 1}) 1317 } 1318 1319 if err := datastore.AddToNodeLog(uuid, []string{request.Command.String()}); err != nil { 1320 return err 1321 } 1322 timedLog.Infof("RPC put local (%s) completed", addedFiles) 1323 return nil 1324 } 1325 1326 // NewVoxels returns Voxels with given geometry and optional image data. 1327 // If img is passed in, the function will initialize the Voxels with data from the image. 1328 // Otherwise, it will allocate a zero buffer of appropriate size. 1329 func (d *Data) NewVoxels(geom dvid.Geometry, img interface{}) (*Voxels, error) { 1330 bytesPerVoxel := d.Properties.Values.BytesPerElement() 1331 stride := geom.Size().Value(0) * bytesPerVoxel 1332 1333 voxels := &Voxels{ 1334 Geometry: geom, 1335 values: d.Properties.Values, 1336 stride: stride, 1337 } 1338 1339 if img == nil { 1340 numVoxels := geom.NumVoxels() 1341 if numVoxels <= 0 { 1342 return nil, fmt.Errorf("Illegal geometry requested: %s", geom) 1343 } 1344 requestSize := int64(bytesPerVoxel) * numVoxels 1345 if requestSize > server.MaxDataRequest { 1346 return nil, fmt.Errorf("Requested payload (%d bytes) exceeds this DVID server's set limit (%d)", 1347 requestSize, server.MaxDataRequest) 1348 } 1349 voxels.data = make([]uint8, requestSize) 1350 } else { 1351 switch t := img.(type) { 1352 case image.Image: 1353 var actualStride int32 1354 var err error 1355 voxels.data, _, actualStride, err = dvid.ImageData(t) 1356 if err != nil { 1357 return nil, err 1358 } 1359 if actualStride < stride { 1360 return nil, fmt.Errorf("Too little data in input image (expected stride %d)", stride) 1361 } 1362 voxels.stride = actualStride 1363 case []byte: 1364 voxels.data = t 1365 actualLen := int64(len(voxels.data)) 1366 expectedLen := int64(bytesPerVoxel) * geom.NumVoxels() 1367 if actualLen != expectedLen { 1368 return nil, fmt.Errorf("voxels data was %d bytes, expected %d bytes for %s", 1369 actualLen, expectedLen, geom) 1370 } 1371 default: 1372 return nil, fmt.Errorf("Unexpected image type given to NewVoxels(): %T", t) 1373 } 1374 } 1375 return voxels, nil 1376 } 1377 1378 func (d *Data) BlockSize() dvid.Point { 1379 return d.Properties.BlockSize 1380 } 1381 1382 // GetExtents retrieves current extent (and updates extents cache) 1383 // TODO -- refactor return since MinIndex / MaxIndex not used so should use extents3d. 1384 func (d *Data) GetExtents(ctx *datastore.VersionedCtx) (dvidextents dvid.Extents, err error) { 1385 var extents ExtentsJSON 1386 // actually fetch extents from datatype storage 1387 var store storage.KeyValueDB 1388 if store, err = datastore.GetKeyValueDB(d); err != nil { 1389 return 1390 } 1391 var serialization []byte 1392 if serialization, err = store.Get(ctx, MetaTKey()); err != nil { 1393 return 1394 } 1395 if extents, err = d.deserializeExtents(serialization); err != nil { 1396 return 1397 } 1398 if extents.MinPoint == nil || extents.MaxPoint == nil { 1399 // assume this is old dataset and try to use values under Properties. 1400 if d.Properties.MinPoint == nil || d.Properties.MaxPoint == nil { 1401 return 1402 } 1403 dvidextents.MinPoint = d.Properties.MinPoint 1404 dvidextents.MaxPoint = d.Properties.MaxPoint 1405 extents.MinPoint = d.Properties.MinPoint 1406 extents.MaxPoint = d.Properties.MaxPoint 1407 } else { 1408 dvidextents.MinPoint = extents.MinPoint 1409 dvidextents.MaxPoint = extents.MaxPoint 1410 } 1411 1412 // derive corresponding block coordinate 1413 blockSize, ok := d.BlockSize().(dvid.Point3d) 1414 if !ok { 1415 err = fmt.Errorf("can't get extents for data instance %q when block size %s isn't 3d", d.DataName(), d.BlockSize()) 1416 return 1417 } 1418 minPoint, ok := extents.MinPoint.(dvid.Point3d) 1419 if !ok { 1420 err = fmt.Errorf("can't get 3d point %s for data %q", extents.MinPoint, d.DataName()) 1421 return 1422 } 1423 maxPoint, ok := extents.MaxPoint.(dvid.Point3d) 1424 if !ok { 1425 err = fmt.Errorf("can't get 3d point %s for data %q", extents.MaxPoint, d.DataName()) 1426 return 1427 } 1428 dvidextents.MinIndex = minPoint.ChunkIndexer(blockSize) 1429 dvidextents.MaxIndex = maxPoint.ChunkIndexer(blockSize) 1430 return 1431 } 1432 1433 // PostExtents updates extents with the new points (always growing) 1434 func (d *Data) PostExtents(ctx *datastore.VersionedCtx, start dvid.Point, end dvid.Point) error { 1435 store, err := datastore.GetOrderedKeyValueDB(d) 1436 if err != nil { 1437 return err 1438 } 1439 // lock datatype to protect get/put 1440 d.Lock() 1441 defer d.Unlock() 1442 1443 // retrieve extents 1444 data, err := store.Get(ctx, MetaTKey()) 1445 if err != nil { 1446 return err 1447 } 1448 1449 extentsjson, err := d.deserializeExtents(data) 1450 if err != nil { 1451 return err 1452 } 1453 1454 // update extents if necessary 1455 var extents dvid.Extents 1456 extents.MinPoint = extentsjson.MinPoint 1457 extents.MaxPoint = extentsjson.MaxPoint 1458 1459 if mod := extents.AdjustPoints(start, end); mod { 1460 // serialize extents 1461 extentsjson.MinPoint = extents.MinPoint 1462 extentsjson.MaxPoint = extents.MaxPoint 1463 ser_extents, err := d.serializeExtents(extentsjson) 1464 if err != nil { 1465 return err 1466 } 1467 1468 // !! update extents only if a non-distributed dvid 1469 // TODO: remove this 1470 d.Extents = extents 1471 err = datastore.SaveDataByVersion(ctx.VersionID(), d) 1472 if err != nil { 1473 dvid.Infof("Error in trying to save repo on change: %v\n", err) 1474 } 1475 1476 // post actual extents 1477 return store.Put(ctx, MetaTKey(), ser_extents) 1478 } 1479 return nil 1480 } 1481 1482 func (d *Data) Resolution() dvid.Resolution { 1483 return d.Properties.Resolution 1484 } 1485 1486 func (d *Data) String() string { 1487 return string(d.DataName()) 1488 } 1489 1490 // MarshalJSON returns a JSON representation of the data assuming that any 1491 // extents are for the master branch leaf. 1492 func (d *Data) MarshalJSON() ([]byte, error) { 1493 vctx, err := datastore.NewVersionedCtxMasterLeaf(d) 1494 if err != nil { 1495 return json.Marshal(struct { 1496 Base *datastore.Data 1497 Extended Properties 1498 }{ 1499 d.Data, 1500 d.Properties, 1501 }) 1502 } 1503 return d.MarshalJSONExtents(vctx) 1504 } 1505 1506 func (d *Data) MarshalJSONExtents(ctx *datastore.VersionedCtx) ([]byte, error) { 1507 // grab extent property and load 1508 extents, err := d.GetExtents(ctx) 1509 if err != nil { 1510 return nil, err 1511 } 1512 1513 var extentsJSON ExtentsJSON 1514 extentsJSON.MinPoint = extents.MinPoint 1515 extentsJSON.MaxPoint = extents.MaxPoint 1516 1517 props, err := d.PropertiesWithExtents(ctx) 1518 if err != nil { 1519 return nil, err 1520 } 1521 return json.Marshal(struct { 1522 Base *datastore.Data 1523 Extended Properties 1524 Extents ExtentsJSON 1525 }{ 1526 d.Data, 1527 props, 1528 extentsJSON, 1529 }) 1530 } 1531 1532 func (d *Data) GobDecode(b []byte) error { 1533 buf := bytes.NewBuffer(b) 1534 dec := gob.NewDecoder(buf) 1535 if err := dec.Decode(&(d.Data)); err != nil { 1536 return err 1537 } 1538 if err := dec.Decode(&(d.Properties)); err != nil { 1539 return err 1540 } 1541 return nil 1542 } 1543 1544 func (d *Data) GobEncode() ([]byte, error) { 1545 var buf bytes.Buffer 1546 enc := gob.NewEncoder(&buf) 1547 if err := enc.Encode(d.Data); err != nil { 1548 return nil, err 1549 } 1550 if err := enc.Encode(d.Properties); err != nil { 1551 return nil, err 1552 } 1553 return buf.Bytes(), nil 1554 } 1555 1556 // --- DataService interface --- 1557 1558 func (d *Data) Help() string { 1559 return fmt.Sprintf(helpMessage, DefaultBlockSize, DefaultRes) 1560 } 1561 1562 func (d *Data) ModifyConfig(config dvid.Config) error { 1563 p := &(d.Properties) 1564 if err := p.setByConfig(config); err != nil { 1565 return err 1566 } 1567 return nil 1568 } 1569 1570 // ForegroundROI creates a new ROI by determining all non-background blocks. 1571 func (d *Data) ForegroundROI(req datastore.Request, reply *datastore.Response) error { 1572 if d.Values.BytesPerElement() != 1 { 1573 return fmt.Errorf("Foreground ROI command only implemented for 1 byte/voxel data!") 1574 } 1575 1576 // Parse the request 1577 var uuidStr, dataName, cmdStr, destName, backgroundStr string 1578 req.CommandArgs(1, &uuidStr, &dataName, &cmdStr, &destName, &backgroundStr) 1579 1580 // Get the version and repo 1581 uuid, versionID, err := datastore.MatchingUUID(uuidStr) 1582 if err != nil { 1583 return err 1584 } 1585 if err = datastore.AddToNodeLog(uuid, []string{req.Command.String()}); err != nil { 1586 return err 1587 } 1588 1589 // Use existing destination data or a new ROI data. 1590 var dest *roi.Data 1591 dest, err = roi.GetByUUIDName(uuid, dvid.InstanceName(destName)) 1592 if err != nil { 1593 config := dvid.NewConfig() 1594 typeservice, err := datastore.TypeServiceByName("roi") 1595 if err != nil { 1596 return err 1597 } 1598 dataservice, err := datastore.NewData(uuid, typeservice, dvid.InstanceName(destName), config) 1599 if err != nil { 1600 return err 1601 } 1602 var ok bool 1603 dest, ok = dataservice.(*roi.Data) 1604 if !ok { 1605 return fmt.Errorf("Could not create ROI data instance") 1606 } 1607 } 1608 1609 // Asynchronously process the voxels. 1610 background, err := dvid.StringToPointNd(backgroundStr, ",") 1611 if err != nil { 1612 return err 1613 } 1614 go d.foregroundROI(versionID, dest, background) 1615 1616 return nil 1617 } 1618 1619 func (d *Data) foregroundROI(v dvid.VersionID, dest *roi.Data, background dvid.PointNd) { 1620 store, err := datastore.GetOrderedKeyValueDB(d) 1621 if err != nil { 1622 dvid.Criticalf("Data type imageblk had error initializing store: %v\n", err) 1623 return 1624 } 1625 1626 timedLog := dvid.NewTimeLog() 1627 timedLog.Infof("Starting foreground ROI %q for %s", dest.DataName(), d.DataName()) 1628 dest.StartUpdate() 1629 defer dest.StopUpdate() 1630 1631 // Iterate through all voxel blocks, loading and then checking blocks 1632 // for any foreground voxels. 1633 ctx := datastore.NewVersionedCtx(d, v) 1634 1635 backgroundBytes := make([]byte, len(background)) 1636 for i, b := range background { 1637 backgroundBytes[i] = byte(b) 1638 } 1639 1640 const BATCH_SIZE = 1000 1641 var numBatches int 1642 var span *dvid.Span 1643 spans := []dvid.Span{} 1644 1645 var f storage.ChunkFunc = func(chunk *storage.Chunk) error { 1646 if chunk == nil || chunk.V == nil { 1647 return nil 1648 } 1649 data, _, err := dvid.DeserializeData(chunk.V, true) 1650 if err != nil { 1651 return fmt.Errorf("Error decoding block: %v\n", err) 1652 } 1653 numVoxels := d.BlockSize().Prod() 1654 var foreground bool 1655 for i := int64(0); i < numVoxels; i++ { 1656 isBackground := false 1657 for _, b := range backgroundBytes { 1658 if data[i] == b { 1659 isBackground = true 1660 break 1661 } 1662 } 1663 if !isBackground { 1664 foreground = true 1665 break 1666 } 1667 } 1668 if foreground { 1669 indexZYX, err := DecodeTKey(chunk.K) 1670 if err != nil { 1671 return fmt.Errorf("Error decoding voxel block key: %v\n", err) 1672 } 1673 x, y, z := indexZYX.Unpack() 1674 if span == nil { 1675 span = &dvid.Span{z, y, x, x} 1676 } else if !span.Extends(x, y, z) { 1677 spans = append(spans, *span) 1678 if len(spans) >= BATCH_SIZE { 1679 init := (numBatches == 0) 1680 numBatches++ 1681 go func(spans []dvid.Span) { 1682 if err := dest.PutSpans(v, spans, init); err != nil { 1683 dvid.Errorf("Error in storing ROI: %v\n", err) 1684 } else { 1685 timedLog.Debugf("-- Wrote batch %d of spans for foreground ROI %q", numBatches, dest.DataName()) 1686 } 1687 }(spans) 1688 spans = []dvid.Span{} 1689 } 1690 span = &dvid.Span{z, y, x, x} 1691 } 1692 } 1693 server.BlockOnInteractiveRequests("voxels [compute foreground ROI]") 1694 return nil 1695 } 1696 1697 minTKey := storage.MinTKey(keyImageBlock) 1698 maxTKey := storage.MaxTKey(keyImageBlock) 1699 1700 err = store.ProcessRange(ctx, minTKey, maxTKey, &storage.ChunkOp{}, f) 1701 if err != nil { 1702 dvid.Errorf("Error in processing chunks in ROI: %v\n", err) 1703 return 1704 } 1705 if span != nil { 1706 spans = append(spans, *span) 1707 } 1708 1709 // Save new ROI 1710 if len(spans) > 0 { 1711 if err := dest.PutSpans(v, spans, numBatches == 0); err != nil { 1712 dvid.Errorf("Error in storing ROI: %v\n", err) 1713 return 1714 } 1715 } 1716 timedLog.Infof("Created foreground ROI %q for %s", dest.DataName(), d.DataName()) 1717 } 1718 1719 // DoRPC acts as a switchboard for RPC commands. 1720 func (d *Data) DoRPC(req datastore.Request, reply *datastore.Response) error { 1721 switch req.TypeCommand() { 1722 case "load": 1723 if len(req.Command) < 5 { 1724 return fmt.Errorf("Poorly formatted load command. See command-line help.") 1725 } 1726 // Parse the request 1727 var uuidStr, dataName, cmdStr, offsetStr string 1728 filenames, err := req.FilenameArgs(1, &uuidStr, &dataName, &cmdStr, &offsetStr) 1729 if err != nil { 1730 return err 1731 } 1732 if len(filenames) == 0 { 1733 hostname, _ := os.Hostname() 1734 return fmt.Errorf("Couldn't find any files to add. Are they visible to DVID server on %s?", 1735 hostname) 1736 } 1737 1738 // Get offset 1739 offset, err := dvid.StringToPoint(offsetStr, ",") 1740 if err != nil { 1741 return fmt.Errorf("Illegal offset specification: %s: %v", offsetStr, err) 1742 } 1743 1744 // Get list of files to add 1745 var addedFiles string 1746 if len(filenames) == 1 { 1747 addedFiles = filenames[0] 1748 } else { 1749 addedFiles = fmt.Sprintf("filenames: %s [%d more]", filenames[0], len(filenames)-1) 1750 } 1751 dvid.Debugf(addedFiles + "\n") 1752 1753 uuid, versionID, err := datastore.MatchingUUID(uuidStr) 1754 if err != nil { 1755 return err 1756 } 1757 if err = datastore.AddToNodeLog(uuid, []string{req.Command.String()}); err != nil { 1758 return err 1759 } 1760 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) 1761 go func() { 1762 err := d.LoadImages(versionID, offset, filenames) 1763 if err != nil { 1764 dvid.Errorf("Cannot load images into data instance %q @ node %s: %v\n", dataName, uuidStr, err) 1765 } 1766 }() 1767 1768 case "put": 1769 if len(req.Command) < 7 { 1770 return fmt.Errorf("Poorly formatted put command. See command-line help.") 1771 } 1772 source := req.Command[4] 1773 switch source { 1774 case "local": 1775 return d.PutLocal(req, reply) 1776 case "remote": 1777 return fmt.Errorf("put remote not yet implemented") 1778 default: 1779 return fmt.Errorf("Unknown command. Data instance '%s' [%s] does not support '%s' command.", 1780 d.DataName(), d.TypeName(), req.TypeCommand()) 1781 } 1782 1783 case "roi": 1784 if len(req.Command) < 6 { 1785 return fmt.Errorf("Poorly formatted roi command. See command-line help.") 1786 } 1787 return d.ForegroundROI(req, reply) 1788 1789 default: 1790 return fmt.Errorf("Unknown command. Data instance '%s' [%s] does not support '%s' command.", 1791 d.DataName(), d.TypeName(), req.TypeCommand()) 1792 } 1793 return nil 1794 } 1795 1796 // Prints RGBA of first n x n pixels of image with header string. 1797 func debugData(img image.Image, message string) { 1798 data, _, stride, _ := dvid.ImageData(img) 1799 var n = 3 // neighborhood to write 1800 fmt.Printf("%s>\n", message) 1801 for y := 0; y < n; y++ { 1802 for x := 0; x < n; x++ { 1803 i := y*int(stride) + x*4 1804 fmt.Printf("[%3d %3d %3d %3d] ", data[i], data[i+1], data[i+2], data[i+3]) 1805 } 1806 fmt.Printf("\n") 1807 } 1808 } 1809 1810 // SendUnserializedBlock writes a raw data block to the writer with given compression. 1811 func (d *Data) SendUnserializedBlock(w http.ResponseWriter, x, y, z int32, v []byte, compression string) error { 1812 var data []byte 1813 switch compression { 1814 case "uncompressed": 1815 b := bytes.NewBuffer(v) 1816 imgdata, err := jpeg.Decode(b) 1817 if err != nil { 1818 return err 1819 } 1820 1821 data2 := imgdata.(*image.Gray) 1822 data = data2.Pix 1823 default: // JPEG by default 1824 data = v 1825 } 1826 1827 // Send block coordinate and size of data. 1828 if err := binary.Write(w, binary.LittleEndian, x); err != nil { 1829 return err 1830 } 1831 if err := binary.Write(w, binary.LittleEndian, y); err != nil { 1832 return err 1833 } 1834 if err := binary.Write(w, binary.LittleEndian, z); err != nil { 1835 return err 1836 } 1837 1838 n := len(data) 1839 if err := binary.Write(w, binary.LittleEndian, int32(n)); err != nil { 1840 return err 1841 } 1842 copydata := make([]byte, len(data)) 1843 copy(copydata, data) 1844 if written, err := w.Write(data); err != nil || written != n { 1845 if err != nil { 1846 return err 1847 } 1848 return fmt.Errorf("could not write %d bytes of value: only %d bytes written", n, written) 1849 } 1850 return nil 1851 } 1852 1853 // SendSerializedBlock writes a serialized data block to the writer with given compression. 1854 func (d *Data) SendSerializedBlock(w http.ResponseWriter, x, y, z int32, v []byte, compression string) error { 1855 // Check internal format and see if it's valid with compression choice. 1856 format, checksum := dvid.DecodeSerializationFormat(dvid.SerializationFormat(v[0])) 1857 1858 if (compression == "jpeg") && format != dvid.JPEG { 1859 return fmt.Errorf("can't encode JPEG: expected internal block data to be JPEG, was %s instead", format) 1860 } 1861 1862 // Send block coordinate and size of data. 1863 if err := binary.Write(w, binary.LittleEndian, x); err != nil { 1864 return err 1865 } 1866 if err := binary.Write(w, binary.LittleEndian, y); err != nil { 1867 return err 1868 } 1869 if err := binary.Write(w, binary.LittleEndian, z); err != nil { 1870 return err 1871 } 1872 1873 // ignore first byte 1874 start := 1 1875 if checksum == dvid.CRC32 { 1876 start += 4 1877 } 1878 1879 // Do any adjustment of sent data based on compression request 1880 var data []byte 1881 if compression == "uncompressed" { 1882 var err error 1883 data, _, err = dvid.DeserializeData(v, true) 1884 if err != nil { 1885 return err 1886 } 1887 } else { 1888 data = v[start:] 1889 } 1890 n := len(data) 1891 if err := binary.Write(w, binary.LittleEndian, int32(n)); err != nil { 1892 return err 1893 } 1894 1895 // Send data itself, skipping the first byte for internal format and next 4 for uncompressed length. 1896 if written, err := w.Write(data); err != nil || written != n { 1897 if err != nil { 1898 return err 1899 } 1900 return fmt.Errorf("could not write %d bytes of value: only %d bytes written", n, written) 1901 } 1902 return nil 1903 } 1904 1905 // SendBlocksSpecific writes data to the blocks specified -- best for non-ordered backend 1906 func (d *Data) SendBlocksSpecific(ctx *datastore.VersionedCtx, w http.ResponseWriter, compression string, blockstring string, isprefetch bool) (numBlocks int, err error) { 1907 w.Header().Set("Content-type", "application/octet-stream") 1908 1909 if compression != "uncompressed" && compression != "jpeg" && compression != "" { 1910 err = fmt.Errorf("don't understand 'compression' query string value: %s", compression) 1911 return 1912 } 1913 timedLog := dvid.NewTimeLog() 1914 defer timedLog.Infof("SendBlocks Specific ") 1915 1916 // extract query string 1917 if blockstring == "" { 1918 return 1919 } 1920 coordarray := strings.Split(blockstring, ",") 1921 if len(coordarray)%3 != 0 { 1922 err = fmt.Errorf("block query string should be three coordinates per block") 1923 return 1924 } 1925 numBlocks = len(coordarray) / 3 1926 1927 // make a finished queue 1928 finishedRequests := make(chan error, len(coordarray)/3) 1929 var mutex sync.Mutex 1930 1931 // get store for data 1932 var gridStore storage.GridStoreGetter 1933 var kvDB storage.KeyValueDB 1934 gridStore, _, kvDB, err = d.gridStoreGetter() 1935 if err != nil { 1936 return 1937 } 1938 1939 // iterate through each block and query 1940 for i := 0; i < len(coordarray); i += 3 { 1941 var xloc, yloc, zloc int 1942 xloc, err = strconv.Atoi(coordarray[i]) 1943 if err != nil { 1944 return 1945 } 1946 yloc, err = strconv.Atoi(coordarray[i+1]) 1947 if err != nil { 1948 return 1949 } 1950 zloc, err = strconv.Atoi(coordarray[i+2]) 1951 if err != nil { 1952 return 1953 } 1954 1955 go func(xloc, yloc, zloc int32, isprefetch bool, finishedRequests chan error) { 1956 var err error 1957 if !isprefetch { 1958 defer func() { 1959 finishedRequests <- err 1960 }() 1961 } 1962 chunkPt := dvid.ChunkPoint3d{xloc, yloc, zloc} 1963 1964 var value []byte 1965 if gridStore != nil { 1966 value, err = gridStore.GridGet(d.ScaleLevel, chunkPt) 1967 if err != nil { 1968 dvid.Infof("gridStore GET on scale %d, chunk %s had err: %v", d.ScaleLevel, chunkPt, err) 1969 err = nil 1970 return 1971 } 1972 if value == nil { 1973 dvid.Infof("gridStore GET on scale %d, chunk %s had nil value\n", d.ScaleLevel, chunkPt) 1974 return 1975 } 1976 mutex.Lock() 1977 defer mutex.Unlock() 1978 d.SendUnserializedBlock(w, xloc, yloc, zloc, value, compression) 1979 return 1980 } 1981 idx := dvid.IndexZYX(chunkPt) 1982 key := NewTKey(&idx) 1983 value, err = kvDB.Get(ctx, key) 1984 if err != nil { 1985 return 1986 } 1987 if len(value) > 0 { 1988 if !isprefetch { 1989 // lock shared resource 1990 mutex.Lock() 1991 defer mutex.Unlock() 1992 d.SendSerializedBlock(w, xloc, yloc, zloc, value, compression) 1993 } 1994 } 1995 }(int32(xloc), int32(yloc), int32(zloc), isprefetch, finishedRequests) 1996 } 1997 1998 if !isprefetch { 1999 // wait for everything to finish if not prefetching 2000 for i := 0; i < len(coordarray); i += 3 { 2001 errjob := <-finishedRequests 2002 if errjob != nil { 2003 err = errjob 2004 } 2005 } 2006 } 2007 return 2008 } 2009 2010 // SendBlocks returns a slice of bytes corresponding to all the blocks along a span in X 2011 func (d *Data) SendBlocks(ctx *datastore.VersionedCtx, w http.ResponseWriter, subvol *dvid.Subvolume, compression string) error { 2012 w.Header().Set("Content-type", "application/octet-stream") 2013 2014 if compression != "uncompressed" && compression != "jpeg" && compression != "" { 2015 return fmt.Errorf("don't understand 'compression' query string value: %s", compression) 2016 } 2017 2018 // convert x,y,z coordinates to block coordinates 2019 blocksize := subvol.Size().Div(d.BlockSize()) 2020 blockoffset := subvol.StartPoint().Div(d.BlockSize()) 2021 2022 timedLog := dvid.NewTimeLog() 2023 defer timedLog.Infof("SendBlocks %s, span x %d, span y %d, span z %d", blockoffset, blocksize.Value(0), blocksize.Value(1), blocksize.Value(2)) 2024 2025 // get store for data 2026 gridStore, okvDB, _, err := d.gridStoreGetter() 2027 if err != nil { 2028 return fmt.Errorf("cannot get suitable data store for imageblk %q: %v", d.DataName(), err) 2029 } 2030 2031 // if only one block is requested, avoid the range query 2032 if blocksize.Value(0) == int32(1) && blocksize.Value(1) == int32(1) && blocksize.Value(2) == int32(1) { 2033 blockCoord := dvid.ChunkPoint3d{blockoffset.Value(0), blockoffset.Value(1), blockoffset.Value(2)} 2034 2035 var value []byte 2036 switch { 2037 case gridStore != nil: 2038 if value, err = gridStore.GridGet(d.ScaleLevel, blockCoord); err != nil { 2039 return err 2040 } 2041 if len(value) > 0 { 2042 return d.SendUnserializedBlock(w, blockCoord[0], blockCoord[1], blockCoord[2], value, compression) 2043 } 2044 case okvDB != nil: 2045 indexBeg := dvid.IndexZYX(blockCoord) 2046 keyBeg := NewTKey(&indexBeg) 2047 if value, err = okvDB.Get(ctx, keyBeg); err != nil { 2048 return err 2049 } 2050 if len(value) > 0 { 2051 return d.SendSerializedBlock(w, blockCoord[0], blockCoord[1], blockCoord[2], value, compression) 2052 } 2053 default: 2054 } 2055 if err != nil { 2056 return err 2057 } 2058 return nil 2059 } 2060 2061 // only do one request at a time, although each request can start many goroutines. 2062 if subvol.NumVoxels() > 256*256*256 { 2063 server.LargeMutationMutex.Lock() 2064 defer server.LargeMutationMutex.Unlock() 2065 } 2066 2067 if gridStore != nil { 2068 ordered := false 2069 minBlock, maxBlock, err := subvol.BoundingChunks(d.BlockSize()) 2070 if err != nil { 2071 return err 2072 } 2073 return gridStore.GridGetVolume(d.ScaleLevel, minBlock, maxBlock, ordered, &storage.BlockOp{}, func(b *storage.Block) error { 2074 if b.Value != nil { 2075 if err := d.SendUnserializedBlock(w, b.Coord[0], b.Coord[1], b.Coord[2], b.Value, compression); err != nil { 2076 return err 2077 } 2078 } 2079 return nil 2080 }) 2081 } 2082 2083 okv := okvDB.(storage.BufferableOps) 2084 // extract buffer interface 2085 req, hasbuffer := okv.(storage.KeyValueRequester) 2086 if hasbuffer { 2087 okv = req.NewBuffer(ctx) 2088 } 2089 2090 for ziter := int32(0); ziter < blocksize.Value(2); ziter++ { 2091 for yiter := int32(0); yiter < blocksize.Value(1); yiter++ { 2092 if !hasbuffer { 2093 beginPoint := dvid.ChunkPoint3d{blockoffset.Value(0), blockoffset.Value(1) + yiter, blockoffset.Value(2) + ziter} 2094 endPoint := dvid.ChunkPoint3d{blockoffset.Value(0) + blocksize.Value(0) - 1, blockoffset.Value(1) + yiter, blockoffset.Value(2) + ziter} 2095 indexBeg := dvid.IndexZYX(beginPoint) 2096 sx, sy, sz := indexBeg.Unpack() 2097 begTKey := NewTKey(&indexBeg) 2098 indexEnd := dvid.IndexZYX(endPoint) 2099 endTKey := NewTKey(&indexEnd) 2100 2101 // Send the entire range of key-value pairs to chunk processor 2102 err = okv.ProcessRange(ctx, begTKey, endTKey, &storage.ChunkOp{}, func(c *storage.Chunk) error { 2103 if c == nil || c.TKeyValue == nil { 2104 return nil 2105 } 2106 kv := c.TKeyValue 2107 if kv.V == nil { 2108 return nil 2109 } 2110 2111 // Determine which block this is. 2112 indexZYX, err := DecodeTKey(kv.K) 2113 if err != nil { 2114 return err 2115 } 2116 x, y, z := indexZYX.Unpack() 2117 if z != sz || y != sy || x < sx || x >= sx+int32(blocksize.Value(0)) { 2118 return nil 2119 } 2120 if err := d.SendSerializedBlock(w, x, y, z, kv.V, compression); err != nil { 2121 return err 2122 } 2123 return nil 2124 }) 2125 2126 if err != nil { 2127 return fmt.Errorf("Unable to GET data %s: %v", ctx, err) 2128 } 2129 } else { 2130 tkeys := make([]storage.TKey, 0) 2131 for xiter := int32(0); xiter < blocksize.Value(0); xiter++ { 2132 currPoint := dvid.ChunkPoint3d{blockoffset.Value(0) + xiter, blockoffset.Value(1) + yiter, blockoffset.Value(2) + ziter} 2133 currPoint2 := dvid.IndexZYX(currPoint) 2134 currTKey := NewTKey(&currPoint2) 2135 tkeys = append(tkeys, currTKey) 2136 } 2137 // Send the entire range of key-value pairs to chunk processor 2138 err = okv.(storage.RequestBuffer).ProcessList(ctx, tkeys, &storage.ChunkOp{}, func(c *storage.Chunk) error { 2139 if c == nil || c.TKeyValue == nil { 2140 return nil 2141 } 2142 kv := c.TKeyValue 2143 if kv.V == nil { 2144 return nil 2145 } 2146 2147 // Determine which block this is. 2148 indexZYX, err := DecodeTKey(kv.K) 2149 if err != nil { 2150 return err 2151 } 2152 x, y, z := indexZYX.Unpack() 2153 2154 if err := d.SendSerializedBlock(w, x, y, z, kv.V, compression); err != nil { 2155 return err 2156 } 2157 return nil 2158 }) 2159 2160 if err != nil { 2161 return fmt.Errorf("Unable to GET data %s: %v", ctx, err) 2162 } 2163 } 2164 } 2165 } 2166 2167 if hasbuffer { 2168 // submit the entire buffer to the DB 2169 err = okv.(storage.RequestBuffer).Flush() 2170 2171 if err != nil { 2172 return fmt.Errorf("Unable to GET data %s: %v", ctx, err) 2173 2174 } 2175 } 2176 2177 return err 2178 } 2179 2180 // ServeHTTP handles all incoming HTTP requests for this data. 2181 func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) (activity map[string]interface{}) { 2182 timedLog := dvid.NewTimeLog() 2183 2184 // Get the action (GET, POST) 2185 action := strings.ToLower(r.Method) 2186 switch action { 2187 case "get": 2188 case "post": 2189 default: 2190 server.BadRequest(w, r, "Data %q can only handle GET or POST HTTP verbs", d.DataName()) 2191 return 2192 } 2193 2194 // Break URL request into arguments 2195 url := r.URL.Path[len(server.WebAPIPath):] 2196 parts := strings.Split(url, "/") 2197 if len(parts[len(parts)-1]) == 0 { 2198 parts = parts[:len(parts)-1] 2199 } 2200 if len(parts) < 4 { 2201 server.BadRequest(w, r, "Incomplete API request") 2202 return 2203 } 2204 2205 // Get query strings and possible roi 2206 var roiptr *ROI 2207 queryStrings := r.URL.Query() 2208 roiname := dvid.InstanceName(queryStrings.Get("roi")) 2209 if len(roiname) != 0 { 2210 roiptr = new(ROI) 2211 attenuationStr := queryStrings.Get("attenuation") 2212 if len(attenuationStr) != 0 { 2213 attenuation, err := strconv.Atoi(attenuationStr) 2214 if err != nil { 2215 server.BadRequest(w, r, err) 2216 return 2217 } 2218 if attenuation < 1 || attenuation > 7 { 2219 server.BadRequest(w, r, "Attenuation should be from 1 to 7 (divides by 2^n)") 2220 return 2221 } 2222 roiptr.attenuation = uint8(attenuation) 2223 } 2224 } 2225 2226 // Process help and info. 2227 switch parts[3] { 2228 case "help": 2229 w.Header().Set("Content-Type", "text/plain") 2230 fmt.Fprintln(w, d.Help()) 2231 return 2232 2233 case "metadata": 2234 jsonStr, err := d.NdDataMetadata(ctx) 2235 if err != nil { 2236 server.BadRequest(w, r, err) 2237 return 2238 } 2239 w.Header().Set("Content-Type", "application/vnd.dvid-nd-data+json") 2240 fmt.Fprintln(w, jsonStr) 2241 return 2242 2243 case "extents": 2244 if action != "post" { 2245 server.BadRequest(w, r, "extents endpoint only supports POST HTTP verb") 2246 return 2247 } 2248 jsonBytes, err := ioutil.ReadAll(r.Body) 2249 if err != nil { 2250 server.BadRequest(w, r, err) 2251 return 2252 } 2253 if err := d.SetExtents(ctx, uuid, jsonBytes); err != nil { 2254 server.BadRequest(w, r, err) 2255 return 2256 } 2257 2258 case "resolution": 2259 if action != "post" { 2260 server.BadRequest(w, r, "resolution endpoint only supports POST HTTP verb") 2261 return 2262 } 2263 jsonBytes, err := ioutil.ReadAll(r.Body) 2264 if err != nil { 2265 server.BadRequest(w, r, err) 2266 return 2267 } 2268 if err := d.SetResolution(uuid, jsonBytes); err != nil { 2269 server.BadRequest(w, r, err) 2270 return 2271 } 2272 2273 case "info": 2274 if action != "get" { 2275 server.BadRequest(w, r, "info endpoint only supports GET HTTP verb") 2276 } else { 2277 jsonBytes, err := d.MarshalJSONExtents(ctx) 2278 if err != nil { 2279 server.BadRequest(w, r, err) 2280 return 2281 } 2282 w.Header().Set("Content-Type", "application/json") 2283 fmt.Fprint(w, string(jsonBytes)) 2284 } 2285 return 2286 2287 case "rawkey": 2288 // GET <api URL>/node/<UUID>/<data name>/rawkey?x=<block x>&y=<block y>&z=<block z> 2289 if len(parts) != 4 { 2290 server.BadRequest(w, r, "rawkey endpoint should be followed by query strings (x, y, and z) giving block coord") 2291 return 2292 } 2293 2294 case "specificblocks": 2295 // GET <api URL>/node/<UUID>/<data name>/specificblocks?compression=gzip&prefetch=false&blocks=x,y,z,x,y,z... 2296 compression := queryStrings.Get("compression") 2297 blocklist := queryStrings.Get("blocks") 2298 isprefetch := false 2299 if prefetch := queryStrings.Get("prefetch"); prefetch == "on" || prefetch == "true" { 2300 isprefetch = true 2301 } 2302 2303 if action == "get" { 2304 numBlocks, err := d.SendBlocksSpecific(ctx, w, compression, blocklist, isprefetch) 2305 if err != nil { 2306 server.BadRequest(w, r, err) 2307 return 2308 } 2309 timedLog.Infof("HTTP %s: %s", r.Method, r.URL) 2310 activity = map[string]interface{}{ 2311 "num_blocks": numBlocks, 2312 } 2313 } else { 2314 server.BadRequest(w, r, "DVID does not accept the %s action on the 'specificblocks' endpoint", action) 2315 return 2316 } 2317 2318 case "subvolblocks": 2319 // GET <api URL>/node/<UUID>/<data name>/subvolblocks/<size>/<offset>[?compression=...] 2320 sizeStr, offsetStr := parts[4], parts[5] 2321 2322 if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" { 2323 if server.ThrottledHTTP(w) { 2324 return 2325 } 2326 defer server.ThrottledOpDone() 2327 } 2328 compression := queryStrings.Get("compression") 2329 subvol, err := dvid.NewSubvolumeFromStrings(offsetStr, sizeStr, "_") 2330 if err != nil { 2331 server.BadRequest(w, r, err) 2332 return 2333 } 2334 2335 if subvol.StartPoint().NumDims() != 3 || subvol.Size().NumDims() != 3 { 2336 server.BadRequest(w, r, "must specify 3D subvolumes", subvol.StartPoint(), subvol.EndPoint()) 2337 return 2338 } 2339 2340 // Make sure subvolume gets align with blocks 2341 if !dvid.BlockAligned(subvol, d.BlockSize()) { 2342 server.BadRequest(w, r, "cannot use labels via 'block' endpoint in non-block aligned geometry %s -> %s", subvol.StartPoint(), subvol.EndPoint()) 2343 return 2344 } 2345 2346 if action == "get" { 2347 if err := d.SendBlocks(ctx, w, subvol, compression); err != nil { 2348 server.BadRequest(w, r, err) 2349 return 2350 } 2351 timedLog.Infof("HTTP %s: %s (%s)", r.Method, subvol, r.URL) 2352 } else { 2353 server.BadRequest(w, r, "DVID does not accept the %s action on the 'blocks' endpoint", action) 2354 return 2355 } 2356 2357 case "blocks": 2358 // GET <api URL>/node/<UUID>/<data name>/blocks/<block coord>/<spanX> 2359 // POST <api URL>/node/<UUID>/<data name>/blocks/<block coord>/<spanX> 2360 if len(parts) < 6 { 2361 server.BadRequest(w, r, "%q must be followed by block-coord/span-x", parts[3]) 2362 return 2363 } 2364 bcoord, err := dvid.StringToChunkPoint3d(parts[4], "_") 2365 if err != nil { 2366 server.BadRequest(w, r, err) 2367 return 2368 } 2369 span, err := strconv.Atoi(parts[5]) 2370 if err != nil { 2371 server.BadRequest(w, r, err) 2372 return 2373 } 2374 if action == "get" { 2375 data, err := d.GetBlocks(ctx.VersionID(), bcoord, int32(span)) 2376 if err != nil { 2377 server.BadRequest(w, r, err) 2378 return 2379 } 2380 w.Header().Set("Content-type", "application/octet-stream") 2381 _, err = w.Write(data) 2382 if err != nil { 2383 server.BadRequest(w, r, err) 2384 return 2385 } 2386 } else { 2387 // TODO -- relax this once GridStore implementations allow mutation 2388 if d.GridStore != "" { 2389 server.BadRequest(w, r, "Data %q uses an immutable GridStore so cannot received POSTs", d.DataName()) 2390 return 2391 } 2392 mutID := d.NewMutationID() 2393 mutate := (queryStrings.Get("mutate") == "true") 2394 if err := d.PutBlocks(ctx.VersionID(), mutID, bcoord, span, r.Body, mutate); err != nil { 2395 server.BadRequest(w, r, err) 2396 return 2397 } 2398 } 2399 timedLog.Infof("HTTP %s: Blocks (%s)", r.Method, r.URL) 2400 2401 case "arb": 2402 // GET <api URL>/node/<UUID>/<data name>/arb/<top left>/<top right>/<bottom left>/<res>[/<format>] 2403 if len(parts) < 8 { 2404 server.BadRequest(w, r, "%q must be followed by top-left/top-right/bottom-left/res", parts[3]) 2405 return 2406 } 2407 if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" { 2408 if server.ThrottledHTTP(w) { 2409 return 2410 } 2411 defer server.ThrottledOpDone() 2412 } 2413 img, err := d.GetArbitraryImage(ctx, parts[4], parts[5], parts[6], parts[7]) 2414 if err != nil { 2415 server.BadRequest(w, r, err) 2416 return 2417 } 2418 var formatStr string 2419 if len(parts) >= 9 { 2420 formatStr = parts[8] 2421 } 2422 err = dvid.WriteImageHttp(w, img.Get(), formatStr) 2423 if err != nil { 2424 server.BadRequest(w, r, err) 2425 return 2426 } 2427 timedLog.Infof("HTTP %s: Arbitrary image (%s)", r.Method, r.URL) 2428 2429 case "raw", "isotropic": 2430 // GET <api URL>/node/<UUID>/<data name>/isotropic/<dims>/<size>/<offset>[/<format>] 2431 if len(parts) < 7 { 2432 server.BadRequest(w, r, "%q must be followed by shape/size/offset", parts[3]) 2433 return 2434 } 2435 var isotropic bool = (parts[3] == "isotropic") 2436 shapeStr, sizeStr, offsetStr := parts[4], parts[5], parts[6] 2437 planeStr := dvid.DataShapeString(shapeStr) 2438 plane, err := planeStr.DataShape() 2439 if err != nil { 2440 server.BadRequest(w, r, err) 2441 return 2442 } 2443 switch plane.ShapeDimensions() { 2444 case 2: 2445 slice, err := dvid.NewSliceFromStrings(planeStr, offsetStr, sizeStr, "_") 2446 if err != nil { 2447 server.BadRequest(w, r, err) 2448 return 2449 } 2450 if action != "get" { 2451 server.BadRequest(w, r, "DVID does not permit 2d mutations, only 3d block-aligned stores") 2452 return 2453 } 2454 rawSlice, err := dvid.Isotropy2D(d.Properties.VoxelSize, slice, isotropic) 2455 if err != nil { 2456 server.BadRequest(w, r, err) 2457 return 2458 } 2459 vox, err := d.NewVoxels(rawSlice, nil) 2460 if err != nil { 2461 server.BadRequest(w, r, err) 2462 return 2463 } 2464 img, err := d.GetImage(ctx.VersionID(), vox, roiname) 2465 if err != nil { 2466 server.BadRequest(w, r, err) 2467 return 2468 } 2469 if isotropic { 2470 dstW := int(slice.Size().Value(0)) 2471 dstH := int(slice.Size().Value(1)) 2472 img, err = img.ScaleImage(dstW, dstH) 2473 if err != nil { 2474 server.BadRequest(w, r, err) 2475 return 2476 } 2477 } 2478 var formatStr string 2479 if len(parts) >= 8 { 2480 formatStr = parts[7] 2481 } 2482 err = dvid.WriteImageHttp(w, img.Get(), formatStr) 2483 if err != nil { 2484 server.BadRequest(w, r, err) 2485 return 2486 } 2487 timedLog.Infof("HTTP %s: %s (%s)", r.Method, plane, r.URL) 2488 case 3: 2489 if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" { 2490 if server.ThrottledHTTP(w) { 2491 return 2492 } 2493 defer server.ThrottledOpDone() 2494 } 2495 subvol, err := dvid.NewSubvolumeFromStrings(offsetStr, sizeStr, "_") 2496 if err != nil { 2497 server.BadRequest(w, r, err) 2498 return 2499 } 2500 if action == "get" { 2501 vox, err := d.NewVoxels(subvol, nil) 2502 if err != nil { 2503 server.BadRequest(w, r, err) 2504 return 2505 } 2506 2507 if len(parts) >= 8 && (parts[7] == "jpeg" || parts[7] == "jpg") { 2508 2509 // extract volume 2510 if err := d.GetVoxels(ctx.VersionID(), vox, roiname); err != nil { 2511 server.BadRequest(w, r, err) 2512 return 2513 } 2514 2515 // convert 3D volume to an 2D image 2516 size3d := vox.Geometry.Size() 2517 size2d := dvid.Point2d{size3d.Value(0), size3d.Value(1) * size3d.Value(2)} 2518 geo2d, err := dvid.NewOrthogSlice(dvid.XY, vox.Geometry.StartPoint(), size2d) 2519 if err != nil { 2520 server.BadRequest(w, r, err) 2521 return 2522 } 2523 vox.Geometry = geo2d 2524 2525 img, err := vox.GetImage2d() 2526 if err != nil { 2527 server.BadRequest(w, r, err) 2528 return 2529 } 2530 2531 formatStr := parts[7] 2532 err = dvid.WriteImageHttp(w, img.Get(), formatStr) 2533 if err != nil { 2534 server.BadRequest(w, r, err) 2535 return 2536 } 2537 } else { 2538 2539 data, err := d.GetVolume(ctx.VersionID(), vox, roiname) 2540 if err != nil { 2541 server.BadRequest(w, r, err) 2542 return 2543 } 2544 w.Header().Set("Content-type", "application/octet-stream") 2545 _, err = w.Write(data) 2546 if err != nil { 2547 server.BadRequest(w, r, err) 2548 return 2549 } 2550 } 2551 } else { 2552 if isotropic { 2553 err := fmt.Errorf("can only POST 'raw' not 'isotropic' images") 2554 server.BadRequest(w, r, err) 2555 return 2556 } 2557 if d.GridStore != "" { 2558 server.BadRequest(w, r, "Data %q uses an immutable GridStore so cannot received POSTs", d.DataName()) 2559 return 2560 } 2561 data, err := ioutil.ReadAll(r.Body) 2562 if err != nil { 2563 server.BadRequest(w, r, err) 2564 return 2565 } 2566 vox, err := d.NewVoxels(subvol, data) 2567 if err != nil { 2568 server.BadRequest(w, r, err) 2569 return 2570 } 2571 mutID := d.NewMutationID() 2572 mutate := (queryStrings.Get("mutate") == "true") 2573 if err = d.PutVoxels(ctx.VersionID(), mutID, vox, roiname, mutate); err != nil { 2574 server.BadRequest(w, r, err) 2575 return 2576 } 2577 } 2578 timedLog.Infof("HTTP %s: %s (%s)", r.Method, subvol, r.URL) 2579 default: 2580 server.BadRequest(w, r, "DVID currently supports shapes of only 2 and 3 dimensions") 2581 } 2582 default: 2583 server.BadAPIRequest(w, r, d) 2584 } 2585 return 2586 }