github.com/janelia-flyem/dvid@v1.0.0/datatype/imagetile/imagetile.go (about) 1 /* 2 Package imagetile implements DVID support for imagetiles in XY, XZ, and YZ orientation. 3 All raw tiles are stored as PNG images that are by default gzipped. This allows raw 4 tile gets to be already compressed at the cost of more expensive uncompression to 5 retrieve arbitrary image sizes. 6 */ 7 package imagetile 8 9 import ( 10 "bytes" 11 "encoding/gob" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "image" 16 "image/draw" 17 "image/jpeg" 18 "image/png" 19 "io/ioutil" 20 "math" 21 "net/http" 22 "sort" 23 "strconv" 24 "strings" 25 "sync" 26 27 "github.com/janelia-flyem/dvid/datastore" 28 "github.com/janelia-flyem/dvid/datatype/imageblk" 29 "github.com/janelia-flyem/dvid/dvid" 30 "github.com/janelia-flyem/dvid/server" 31 "github.com/janelia-flyem/dvid/storage" 32 ) 33 34 const ( 35 Version = "0.1" 36 RepoURL = "github.com/janelia-flyem/dvid/datatype/imagetile" 37 TypeName = "imagetile" 38 ) 39 40 const helpMessage = ` 41 API for datatypes derived from imagetile (github.com/janelia-flyem/dvid/datatype/imagetile) 42 ===================================================================================== 43 44 Note: UUIDs referenced below are strings that may either be a unique prefix of a 45 hexadecimal UUID string (e.g., 3FA22) or a branch leaf specification that adds 46 a colon (":") followed by the case-dependent branch name. In the case of a 47 branch leaf specification, the unique UUID prefix just identifies the repo of 48 the branch, and the UUID referenced is really the leaf of the branch name. 49 For example, if we have a DAG with root A -> B -> C where C is the current 50 HEAD or leaf of the "master" (default) branch, then asking for "B:master" is 51 the same as asking for "C". If we add another version so A -> B -> C -> D, then 52 references to "B:master" now return the data from "D". 53 54 Command-line: 55 56 $ dvid repo <UUID> new imagetile <data name> <settings...> 57 58 Adds multiresolution XY, XZ, and YZ imagetile from Source to repo with specified UUID. 59 60 Example: 61 62 $ dvid repo 3f8c new imagetile myimagetile source=mygrayscale format=jpg 63 64 Arguments: 65 66 UUID Hexadecimal string with enough characters to uniquely identify a version node. 67 data name Name of data to create, e.g., "mygrayscale" 68 settings Configuration settings in "key=value" format separated by spaces. 69 70 Configuration Settings (case-insensitive keys) 71 72 Format "lz4", "jpg", or "png" (default). In the case of "lz4", decoding/encoding is done at 73 tile request time and is a better choice if you primarily ask for arbitrary sized images 74 (via GET .../raw/... or .../isotropic/...) instead of tiles (via GET .../tile/...) 75 Versioned "true" or "false" (default) 76 Source Name of uint8blk data instance if using the tile "generate" command below. 77 Placeholder Bool ("false", "true", "0", or "1"). Return placeholder tile if missing. 78 79 80 $ dvid node <UUID> <data name> generate [settings] 81 $ dvid -stdin node <UUID> <data name> generate [settings] < config.json 82 83 Generates multiresolution XY, XZ, and YZ imagetile from Source to repo with specified UUID. 84 The resolutions at each scale and the dimensions of the tiles are passed in the configuration 85 JSON. Only integral multiplications of original resolutions are allowed for scale. If you 86 want more sophisticated processing, post the imagetile tiles directly via HTTP. Note that 87 the generated tiles are aligned in a grid having (0,0,0) as a top left corner of a tile, not 88 tiles that start from the corner of present data since the data can expand. 89 90 If not tile spec file is used, a default tile spec is generated that will cover the 91 extents of the source data. 92 93 Example: 94 95 $ dvid repo 3f8c myimagetile generate /path/to/config.json 96 $ dvid -stdin repo 3f8c myimagetile generate xrange=100,700 planes="yz;0,1" < /path/to/config.json 97 98 Arguments: 99 100 UUID Hexadecimal string with enough characters to uniquely identify a version node. 101 data name Name of data to create, e.g., "mygrayscale". 102 settings Optional file name of tile specifications for tile generation. 103 104 Configuration Settings (case-insensitive keys) 105 106 planes List of one or more planes separated by semicolon. Each plane can be 107 designated using either axis number ("0,1") or xyz nomenclature ("xy"). 108 Example: planes="0,1;yz" 109 zrange Only render XY tiles containing pixels between this minimum and maximum in z. 110 yrange Only render XZ tiles containing pixels between this minimum and maximum in y. 111 xrange Only render YZ tiles containing pixels between this minimum and maximum in x. 112 filename Filename of JSON file specifying multiscale tile resolutions as below. 113 114 Sample config.json: 115 116 { 117 "0": { "Resolution": [10.0, 10.0, 10.0], "TileSize": [512, 512, 512] }, 118 "1": { "Resolution": [20.0, 20.0, 20.0], "TileSize": [512, 512, 512] }, 119 "2": { "Resolution": [40.0, 40.0, 40.0], "TileSize": [512, 512, 512] }, 120 "3": { "Resolution": [80.0, 80.0, 80.0], "TileSize": [512, 512, 512] } 121 } 122 123 $ dvid repo <UUID> push <remote DVID address> <settings...> 124 125 Push tiles to remote DVID. 126 127 where <settings> are optional "key=value" strings: 128 129 data=<data1>[,<data2>[,<data3>...]] 130 131 If supplied, the transmitted data will be limited to the listed 132 data instance names. 133 134 filter=roi:<roiname>,<uuid>/tile:<plane>,<plane> 135 136 Example: filter=roi:seven_column,38af/tile:xy,xz 137 138 There are two usable filters for imagetile: 139 The "roi" filter is followed by an roiname and a UUID for that ROI. 140 The "tile" filter is followed by one or more plane specifications (xy, xz, yz). 141 If omitted all planes are pushed. 142 143 transmit=[all | branch | flatten] 144 145 The default transmit "all" sends all versions necessary to 146 make the remote equivalent or a superset of the local repo. 147 148 A transmit "flatten" will send just the version specified and 149 flatten the key/values so there is no history. 150 151 A transmit "branch" will send just the ancestor path of the 152 version specified. 153 154 155 ------------------ 156 157 HTTP API (Level 2 REST): 158 159 GET <api URL>/node/<UUID>/<data name>/help 160 161 Returns data-specific help message. 162 163 164 GET <api URL>/node/<UUID>/<data name>/info 165 166 Retrieves characteristics of this tile data like the tile size and number of scales present. 167 168 Example: 169 170 GET <api URL>/node/3f8c/myimagetile/info 171 172 Arguments: 173 174 UUID Hexadecimal string with enough characters to uniquely identify a version node. 175 data name Name of imagetile data. 176 177 178 GET <api URL>/node/<UUID>/<data name>/metadata 179 180 Gets the resolution and expected tile sizes for stored tiles. See the POST action on this endpoint 181 for further documentation. 182 183 POST <api URL>/node/<UUID>/<data name>/metadata 184 185 Sets the resolution and expected tile sizes for stored tiles. This should be used in conjunction 186 with POST to the tile endpoints to populate an imagetile data instance with externally generated 187 data. For example POST payload, see the sample config.json above in "generate" command line. 188 189 Note that until metadata is set, any call to the "raw" or "isotropic" endpoints will return 190 a status code 400 (Bad Request) and a message that the metadata needs to be set. 191 192 Metadata should be JSON in the following format: 193 { 194 "MinTileCoord": [0, 0, 0], 195 "MaxTileCoord": [5, 5, 4], 196 "Levels": { 197 "0": { "Resolution": [10.0, 10.0, 10.0], "TileSize": [512, 512, 512] }, 198 "1": { "Resolution": [20.0, 20.0, 20.0], "TileSize": [512, 512, 512] }, 199 "2": { "Resolution": [40.0, 40.0, 40.0], "TileSize": [512, 512, 512] }, 200 "3": { "Resolution": [80.0, 80.0, 80.0], "TileSize": [512, 512, 512] } 201 } 202 } 203 204 where "MinTileCoord" and "MaxTileCoord" are the minimum and maximum tile coordinates, 205 thereby defining the extent of the tiled volume when coupled with level "0" tile sizes. 206 207 208 GET <api URL>/node/<UUID>/<data name>/tile/<dims>/<scaling>/<tile coord>[?noblanks=true] 209 POST 210 Retrieves or adds tile of named data within a version node. This GET call should be the fastest 211 way to retrieve image data since internally it has already been stored in a pre-computed, optionally 212 compression format, whereas arbitrary geometry calls require the DVID server to stitch images 213 together. 214 215 The returned image format is dictated by the imagetile encoding. PNG tiles are returned 216 if internal encoding is either lz4 or png. JPG tiles are returned if internal encoding is JPG. 217 The only reason to use lz4 for internal encoding is if the majority of endpoint use for 218 the data instance is via the "raw" endpoint where many tiles need to be stitched before 219 sending the requested image back. 220 221 Note on POSTs: The data of the body in the POST is assumed to match the data instance's 222 chosen compression and tile sizes. Currently, no checks are performed to make sure the 223 POSTed data meets the specification. 224 225 Example: 226 227 GET <api URL>/node/3f8c/myimagetile/tile/xy/0/10_10_20 228 POST <api URL>/node/3f8c/myimagetile/tile/xy/0/10_10_20 229 230 Arguments: 231 232 UUID Hexadecimal string with enough characters to uniquely identify a version node. 233 data name Name of data to add. 234 dims The axes of data extraction in form "i_j_k,..." Example: "0_2" can be XZ. 235 Slice strings ("xy", "xz", or "yz") are also accepted. 236 scaling Value from 0 (original resolution) to N where each step is downres by 2. 237 tile coord The tile coordinate in "x_y_z" format. See discussion of scaling above. 238 239 Query-string options: 240 241 noblanks (only GET) If true, any tile request for tiles outside the currently stored extents 242 will return a blank image. 243 244 245 GET <api URL>/node/<UUID>/<data name>/tilekey/<dims>/<scaling>/<tile coord> 246 247 Retrieves the internal key for a tile of named data within a version node. 248 This lets external systems bypass DVID and store tiles directly into immutable stores. 249 250 Returns JSON with "key" key and a hexadecimal string giving binary key. 251 252 Example: 253 254 GET <api URL>/node/3f8c/myimagetile/tilekey/xy/0/10_10_20 255 256 Returns: 257 { "key": <hexadecimal string of key> } 258 259 Arguments: 260 261 UUID Hexadecimal string with enough characters to uniquely identify a version node. 262 data name Name of data to add. 263 dims The axes of data extraction in form "i_j_k,..." Example: "0_2" can be XZ. 264 Slice strings ("xy", "xz", or "yz") are also accepted. 265 scaling Value from 0 (original resolution) to N where each step is downres by 2. 266 tile coord The tile coordinate in "x_y_z" format. See discussion of scaling above. 267 268 269 GET <api URL>/node/<UUID>/<data name>/raw/<dims>/<size>/<offset>[/<format>] 270 271 Retrieves raw image of named data within a version node using the precomputed imagetile. 272 By "raw", we mean that no additional processing is applied based on voxel resolutions 273 to make sure the retrieved image has isotropic pixels. For example, if an XZ image 274 is requested and the image volume has X resolution 3 nm and Z resolution 40 nm, the 275 returned image will be heavily anisotropic and should be scaled by 40/3 in Y by client. 276 277 Example: 278 279 GET <api URL>/node/3f8c/myimagetile/raw/xy/512_256/0_0_100/jpg:80 280 281 Arguments: 282 283 UUID Hexadecimal string with enough characters to uniquely identify a version node. 284 data name Name of data to add. 285 dims The axes of data extraction in form i_j. Example: "0_2" can be XZ. 286 Slice strings ("xy", "xz", or "yz") are also accepted. 287 Note that only 2d images are returned for imagetiles. 288 size Size in voxels along each dimension specified in <dims>. 289 offset Gives coordinate of first voxel using dimensionality of data. 290 format "png", "jpg" (default: "png") 291 jpg allows lossy quality setting, e.g., "jpg:80" 292 293 GET <api URL>/node/<UUID>/<data name>/isotropic/<dims>/<size>/<offset>[/<format>] 294 295 Retrieves isotropic image of named data within a version node using the precomputed imagetile. 296 Additional processing is applied based on voxel resolutions to make sure the retrieved image 297 has isotropic pixels. For example, if an XZ image is requested and the image volume has 298 X resolution 3 nm and Z resolution 40 nm, the returned image's height will be magnified 40/3 299 relative to the raw data. 300 301 Example: 302 303 GET <api URL>/node/3f8c/myimagetile/isotropic/xy/512_256/0_0_100/jpg:80 304 305 Arguments: 306 307 UUID Hexadecimal string with enough characters to uniquely identify a version node. 308 data name Name of data to add. 309 dims The axes of data extraction in form i_j. Example: "0_2" can be XZ. 310 Slice strings ("xy", "xz", or "yz") are also accepted. 311 Note that only 2d images are returned for imagetiles. 312 size Size in voxels along each dimension specified in <dims>. 313 offset Gives coordinate of first voxel using dimensionality of data. 314 format "png", "jpg" (default: "png") 315 jpg allows lossy quality setting, e.g., "jpg:80" 316 317 ` 318 319 var ( 320 ErrNoMetadataSet = errors.New("Tile metadata has not been POSTed yet. GET requests require metadata to be POST.") 321 ) 322 323 func init() { 324 datastore.Register(NewType()) 325 326 // Need to register types that will be used to fulfill interfaces. 327 gob.Register(&Type{}) 328 gob.Register(&Data{}) 329 } 330 331 // Type embeds the datastore's Type to create a unique type with tile functions. 332 // Refinements of general tile types can be implemented by embedding this type, 333 // choosing appropriate # of channels and bytes/voxel, overriding functions as 334 // needed, and calling datastore.Register(). 335 // Note that these fields are invariant for all instances of this type. Fields 336 // that can change depending on the type of data (e.g., resolution) should be 337 // in the Data type. 338 type Type struct { 339 datastore.Type 340 } 341 342 // NewDatatype returns a pointer to a new voxels Datatype with default values set. 343 func NewType() *Type { 344 return &Type{ 345 datastore.Type{ 346 Name: "imagetile", 347 URL: "github.com/janelia-flyem/dvid/datatype/imagetile", 348 Version: "0.1", 349 Requirements: &storage.Requirements{ 350 Batcher: true, 351 }, 352 }, 353 } 354 } 355 356 // --- TypeService interface --- 357 358 // NewData returns a pointer to new tile data with default values. 359 func (dtype *Type) NewDataService(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (datastore.DataService, error) { 360 // See if we have a valid DataService source 361 sourcename, found, err := c.GetString("Source") 362 if err != nil { 363 return nil, err 364 } 365 366 // See if we want placeholder imagetile. 367 placeholder, found, err := c.GetBool("Placeholder") 368 if err != nil { 369 return nil, err 370 } 371 372 // Determine encoding for tile storage and this dictates what kind of compression we use. 373 encoding, found, err := c.GetString("Format") 374 if err != nil { 375 return nil, err 376 } 377 format := PNG 378 if found { 379 switch strings.ToLower(encoding) { 380 case "lz4": 381 format = LZ4 382 case "png": 383 format = PNG 384 case "jpg": 385 format = JPG 386 default: 387 return nil, fmt.Errorf("Unknown encoding specified: '%s' (should be 'lz4', 'png', or 'jpg'", encoding) 388 } 389 } 390 391 // Compression is determined by encoding. Inform user if there's a discrepancy. 392 var compression string 393 switch format { 394 case LZ4: 395 compression = "lz4" 396 case PNG: 397 compression = "none" 398 case JPG: 399 compression = "none" 400 } 401 compressConfig, found, err := c.GetString("Compression") 402 if err != nil { 403 return nil, err 404 } 405 if found && strings.ToLower(compressConfig) != compression { 406 return nil, fmt.Errorf("Conflict between specified compression '%s' and format '%s'. Suggest not dictating compression.", 407 compressConfig, encoding) 408 } 409 c.Set("Compression", compression) 410 411 // Initialize the imagetile data 412 basedata, err := datastore.NewDataService(dtype, uuid, id, name, c) 413 if err != nil { 414 return nil, err 415 } 416 data := &Data{ 417 Data: basedata, 418 Properties: Properties{ 419 Source: dvid.InstanceName(sourcename), 420 Placeholder: placeholder, 421 Encoding: format, 422 }, 423 } 424 return data, nil 425 } 426 427 func (dtype *Type) Help() string { 428 return helpMessage 429 } 430 431 // Scaling describes the scale level where 0 = original data resolution and 432 // higher levels have been downsampled. 433 type Scaling uint8 434 435 type LevelSpec struct { 436 Resolution dvid.NdFloat32 437 TileSize dvid.Point3d 438 } 439 440 func (spec LevelSpec) Duplicate() LevelSpec { 441 var out LevelSpec 442 out.Resolution = make(dvid.NdFloat32, 3) 443 copy(out.Resolution, spec.Resolution) 444 out.TileSize = spec.TileSize 445 return out 446 } 447 448 // TileScaleSpec is a slice of tile resolution & size for each dimensions. 449 type TileScaleSpec struct { 450 LevelSpec 451 452 levelMag dvid.Point3d // Magnification from this one to the next level. 453 } 454 455 // TileSpec specifies the resolution & size of each dimension at each scale level. 456 type TileSpec map[Scaling]TileScaleSpec 457 458 // MarshalJSON returns the JSON of the imagetile specifications for each scale level. 459 func (tileSpec TileSpec) MarshalJSON() ([]byte, error) { 460 serializable := make(specJSON, len(tileSpec)) 461 for scaling, levelSpec := range tileSpec { 462 key := fmt.Sprintf("%d", scaling) 463 serializable[key] = LevelSpec{levelSpec.Resolution, levelSpec.TileSize} 464 } 465 return json.Marshal(serializable) 466 } 467 468 type specJSON map[string]LevelSpec 469 470 // LoadTileSpec loads a TileSpec from JSON data. 471 // JSON data should look like: 472 // { 473 // "0": { "Resolution": [3.1, 3.1, 40.0], "TileSize": [512, 512, 40] }, 474 // "1": { "Resolution": [6.2, 6.2, 40.0], "TileSize": [512, 512, 80] }, 475 // ... 476 // } 477 // Each line is a scale with a n-D resolution/voxel and a n-D tile size in voxels. 478 func LoadTileSpec(jsonBytes []byte) (TileSpec, error) { 479 var config specJSON 480 err := json.Unmarshal(jsonBytes, &config) 481 if err != nil { 482 return nil, err 483 } 484 return parseTileSpec(config) 485 } 486 487 type metadataJSON struct { 488 MinTileCoord dvid.Point3d 489 MaxTileCoord dvid.Point3d 490 Levels specJSON 491 } 492 493 // SetMetadata loads JSON data giving MinTileCoord, MaxTileCoord, and tile level specifications. 494 func (d *Data) SetMetadata(uuid dvid.UUID, jsonBytes []byte) error { 495 var config metadataJSON 496 if err := json.Unmarshal(jsonBytes, &config); err != nil { 497 return err 498 } 499 tileSpec, err := parseTileSpec(config.Levels) 500 if err != nil { 501 return err 502 } 503 504 d.Levels = tileSpec 505 d.MinTileCoord = config.MinTileCoord 506 d.MaxTileCoord = config.MaxTileCoord 507 if err := datastore.SaveDataByUUID(uuid, d); err != nil { 508 return err 509 } 510 return nil 511 } 512 513 func parseTileSpec(config specJSON) (TileSpec, error) { 514 // Allocate the tile specs 515 numLevels := len(config) 516 specs := make(TileSpec, numLevels) 517 dvid.Infof("Found %d scaling levels for imagetile specification.\n", numLevels) 518 519 // Store resolution and tile sizes per level. 520 var hires, lores float64 521 for scaleStr, levelSpec := range config { 522 dvid.Infof("scale %s, levelSpec %v\n", scaleStr, levelSpec) 523 scaleLevel, err := strconv.Atoi(scaleStr) 524 if err != nil { 525 return nil, fmt.Errorf("Scaling '%s' needs to be a number for the scale level.", scaleStr) 526 } 527 if scaleLevel >= numLevels { 528 return nil, fmt.Errorf("Tile levels must be consecutive integers from [0,Max]: Got scale level %d > # levels (%d)\n", 529 scaleLevel, numLevels) 530 } 531 specs[Scaling(scaleLevel)] = TileScaleSpec{LevelSpec: levelSpec} 532 } 533 534 // Compute the magnification between each level. 535 for scaling := Scaling(0); scaling < Scaling(numLevels-1); scaling++ { 536 levelSpec, found := specs[scaling] 537 if !found { 538 return nil, fmt.Errorf("Could not find tile spec for level %d", scaling) 539 } 540 nextSpec, found := specs[scaling+1] 541 if !found { 542 return nil, fmt.Errorf("Could not find tile spec for level %d", scaling+1) 543 } 544 var levelMag dvid.Point3d 545 for i, curRes := range levelSpec.Resolution { 546 hires = float64(curRes) 547 lores = float64(nextSpec.Resolution[i]) 548 rem := math.Remainder(lores, hires) 549 if rem > 0.001 { 550 return nil, fmt.Errorf("Resolutions between scale %d and %d aren't integral magnifications!", 551 scaling, scaling+1) 552 } 553 mag := lores / hires 554 if mag < 0.99 { 555 return nil, fmt.Errorf("A resolution between scale %d and %d actually increases!", 556 scaling, scaling+1) 557 } 558 mag += 0.5 559 levelMag[i] = int32(mag) 560 } 561 levelSpec.levelMag = levelMag 562 specs[scaling] = levelSpec 563 } 564 return specs, nil 565 } 566 567 // --- Tile Data ---- 568 569 // SourceData is the source of the tile data and should be voxels or voxels-derived data. 570 type SourceData interface{} 571 572 type Format uint8 573 574 const ( 575 LZ4 Format = iota 576 PNG 577 JPG 578 ) 579 580 func (f Format) String() string { 581 switch f { 582 case LZ4: 583 return "lz4" 584 case PNG: 585 return "png" 586 case JPG: 587 return "jpeg" 588 default: 589 return fmt.Sprintf("format %d", f) 590 } 591 } 592 593 var DefaultTileSize = dvid.Point3d{512, 512, 512} 594 595 // Properties are additional properties for keyvalue data instances beyond those 596 // in standard datastore.Data. These will be persisted to metadata storage. 597 type Properties struct { 598 // Source of the data for these imagetile. Could be blank if tiles are not generated from 599 // an associated grayscale data instance, but were loaded directly from external processing. 600 Source dvid.InstanceName 601 602 // MinTileCoord gives the minimum tile coordinate. 603 MinTileCoord dvid.Point3d 604 605 // MaxTileCoord gives the maximum tile coordinate. 606 MaxTileCoord dvid.Point3d 607 608 // Levels describe the resolution and tile sizes at each level of resolution. 609 Levels TileSpec 610 611 // Placeholder, when true (false by default), will generate fake tile images if a tile cannot 612 // be found. This is useful in testing clients. 613 Placeholder bool 614 615 // Encoding describes encoding of the stored tile. See imagetile.Format 616 Encoding Format 617 618 // Quality is optional quality of encoding for jpeg, 1-100, higher is better. 619 Quality int 620 } 621 622 // Data embeds the datastore's Data and extends it with voxel-specific properties. 623 type Data struct { 624 *datastore.Data 625 Properties 626 } 627 628 // CopyPropertiesFrom copies the data instance-specific properties from a given 629 // data instance into the receiver's properties. Fulfills the datastore.PropertyCopier interface. 630 func (d *Data) CopyPropertiesFrom(src datastore.DataService, fs storage.FilterSpec) error { 631 d2, ok := src.(*Data) 632 if !ok { 633 return fmt.Errorf("unable to copy properties from non-imagetile data %q", src.DataName()) 634 } 635 d.Properties.copyImmutable(&(d2.Properties)) 636 637 // TODO -- Handle mutable data that could be potentially altered by filter. 638 d.MinTileCoord = d2.MinTileCoord 639 d.MaxTileCoord = d2.MaxTileCoord 640 641 return nil 642 } 643 644 func (p *Properties) copyImmutable(p2 *Properties) { 645 p.Source = p2.Source 646 647 p.Levels = make(TileSpec, len(p2.Levels)) 648 for scale, spec := range p2.Levels { 649 p.Levels[scale] = TileScaleSpec{spec.LevelSpec.Duplicate(), spec.levelMag} 650 } 651 p.Placeholder = p2.Placeholder 652 p.Encoding = p2.Encoding 653 p.Quality = p2.Quality 654 } 655 656 // Returns the bounds in voxels for a given tile. 657 func (d *Data) computeVoxelBounds(tileCoord dvid.ChunkPoint3d, plane dvid.DataShape, scale Scaling) (dvid.Extents3d, error) { 658 // Get magnification at the given scale of the tile sizes. 659 mag := dvid.Point3d{1, 1, 1} 660 var tileSize dvid.Point3d 661 for s := Scaling(0); s <= scale; s++ { 662 spec, found := d.Properties.Levels[s] 663 if !found { 664 return dvid.Extents3d{}, fmt.Errorf("no tile spec for scale %d", scale) 665 } 666 tileSize = spec.TileSize.Mult(mag).(dvid.Point3d) 667 // mag = mag.Mult(spec.levelMag).(dvid.Point3d) 668 // TODO -- figure out why mag level not working for some imagetile instances. 669 mag[0] *= 2 670 mag[1] *= 2 671 mag[2] *= 2 672 } 673 return dvid.GetTileExtents(tileCoord, plane, tileSize) 674 } 675 676 // DefaultTileSpec returns the default tile spec that will fully cover the source extents and 677 // scaling 0 uses the original voxel resolutions with each subsequent scale causing a 2x zoom out. 678 func (d *Data) DefaultTileSpec(uuidStr string) (TileSpec, error) { 679 uuid, _, err := datastore.MatchingUUID(uuidStr) 680 if err != nil { 681 return nil, err 682 } 683 source, err := datastore.GetDataByUUIDName(uuid, d.Source) 684 if err != nil { 685 return nil, err 686 } 687 var ok bool 688 var src *imageblk.Data 689 src, ok = source.(*imageblk.Data) 690 if !ok { 691 return nil, fmt.Errorf("Cannot construct tile spec for non-voxels data: %s", d.Source) 692 } 693 694 // Set scaling 0 based on extents and resolution of source. 695 //extents := src.Extents() 696 resolution := src.Properties.Resolution.VoxelSize 697 698 if len(resolution) != 3 { 699 return nil, fmt.Errorf("Cannot construct tile spec for non-3d data: voxel is %d-d", 700 len(resolution)) 701 } 702 703 // Expand min and max points to coincide with full tile boundaries of highest resolution. 704 minTileCoord := src.MinPoint.(dvid.Chunkable).Chunk(DefaultTileSize) 705 maxTileCoord := src.MaxPoint.(dvid.Chunkable).Chunk(DefaultTileSize) 706 minTiledPt := minTileCoord.MinPoint(DefaultTileSize) 707 maxTiledPt := maxTileCoord.MaxPoint(DefaultTileSize) 708 sizeVolume := maxTiledPt.Sub(minTiledPt).AddScalar(1) 709 710 dvid.Infof("Creating default multiscale tile spec for volume of size %s\n", sizeVolume) 711 712 // For each dimension, calculate the number of scaling levels necessary to cover extent, 713 // assuming we use the raw resolution at scaling 0. 714 numScales := make([]int, 3) 715 var maxScales int 716 var dim uint8 717 for dim = 0; dim < 3; dim++ { 718 numPixels := float64(sizeVolume.Value(dim)) 719 tileSize := float64(DefaultTileSize.Value(dim)) 720 if numPixels <= tileSize { 721 numScales[dim] = 1 722 } else { 723 numScales[dim] = int(math.Ceil(math.Log2(numPixels/tileSize))) + 1 724 } 725 if numScales[dim] > maxScales { 726 maxScales = numScales[dim] 727 } 728 } 729 730 // Initialize the tile level specification 731 specs := make(TileSpec, maxScales) 732 curRes := resolution 733 levelMag := dvid.Point3d{2, 2, 2} 734 var scaling Scaling 735 for scaling = 0; scaling < Scaling(maxScales); scaling++ { 736 for dim = 0; dim < 3; dim++ { 737 if scaling >= Scaling(numScales[dim]) { 738 levelMag[dim] = 1 739 } 740 } 741 specs[scaling] = TileScaleSpec{ 742 LevelSpec{curRes, DefaultTileSize}, 743 levelMag, 744 } 745 curRes = curRes.MultScalar(2.0) 746 } 747 return specs, nil 748 } 749 750 func (d *Data) MarshalJSON() ([]byte, error) { 751 return json.Marshal(struct { 752 Base *datastore.Data 753 Extended Properties 754 }{ 755 d.Data, 756 d.Properties, 757 }) 758 } 759 760 func (d *Data) GobDecode(b []byte) error { 761 buf := bytes.NewBuffer(b) 762 dec := gob.NewDecoder(buf) 763 if err := dec.Decode(&(d.Data)); err != nil { 764 return err 765 } 766 if err := dec.Decode(&(d.Properties)); err != nil { 767 return err 768 } 769 return nil 770 } 771 772 func (d *Data) GobEncode() ([]byte, error) { 773 var buf bytes.Buffer 774 enc := gob.NewEncoder(&buf) 775 if err := enc.Encode(d.Data); err != nil { 776 return nil, err 777 } 778 if err := enc.Encode(d.Properties); err != nil { 779 return nil, err 780 } 781 return buf.Bytes(), nil 782 } 783 784 // --- DataService interface --- 785 786 func (d *Data) Help() string { 787 return helpMessage 788 } 789 790 // DoRPC handles the 'generate' command. 791 func (d *Data) DoRPC(request datastore.Request, reply *datastore.Response) error { 792 if request.TypeCommand() != "generate" { 793 return fmt.Errorf("Unknown command. Data instance '%s' [%s] does not support '%s' command.", 794 d.DataName(), d.TypeName(), request.TypeCommand()) 795 } 796 var uuidStr, dataName, cmdStr string 797 request.CommandArgs(1, &uuidStr, &dataName, &cmdStr) 798 799 // Get the imagetile generation configuration from a file or stdin. 800 var err error 801 var tileSpec TileSpec 802 if request.Input != nil { 803 tileSpec, err = LoadTileSpec(request.Input) 804 if err != nil { 805 return err 806 } 807 } else { 808 config := request.Settings() 809 filename, found, err := config.GetString("filename") 810 if err != nil { 811 return err 812 } 813 if found { 814 configData, err := storage.DataFromFile(filename) 815 if err != nil { 816 return err 817 } 818 tileSpec, err = LoadTileSpec(configData) 819 if err != nil { 820 return err 821 } 822 dvid.Infof("Using tile spec file: %s\n", filename) 823 } else { 824 dvid.Infof("Using default tile generation method since no tile spec file was given...\n") 825 tileSpec, err = d.DefaultTileSpec(uuidStr) 826 if err != nil { 827 return err 828 } 829 } 830 } 831 reply.Text = fmt.Sprintf("Tiling data instance %q @ node %s...\n", dataName, uuidStr) 832 go func() { 833 err := d.ConstructTiles(uuidStr, tileSpec, request) 834 if err != nil { 835 dvid.Errorf("Cannot construct tiles for data instance %q @ node %s: %v\n", dataName, uuidStr, err) 836 } 837 }() 838 return nil 839 } 840 841 // ServeHTTP handles all incoming HTTP requests for this data. 842 func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) (activity map[string]interface{}) { 843 timedLog := dvid.NewTimeLog() 844 845 action := strings.ToLower(r.Method) 846 switch action { 847 case "get", "post": 848 // Acceptable 849 default: 850 server.BadRequest(w, r, "Can only handle GET or POST HTTP verbs") 851 return 852 } 853 854 // Break URL request into arguments 855 url := r.URL.Path[len(server.WebAPIPath):] 856 parts := strings.Split(url, "/") 857 if len(parts[len(parts)-1]) == 0 { 858 parts = parts[:len(parts)-1] 859 } 860 if len(parts) < 4 { 861 server.BadRequest(w, r, "incomplete API request") 862 return 863 } 864 865 switch parts[3] { 866 case "help": 867 w.Header().Set("Content-Type", "text/plain") 868 fmt.Fprintln(w, d.Help()) 869 870 case "info": 871 jsonBytes, err := d.MarshalJSON() 872 if err != nil { 873 server.BadRequest(w, r, err) 874 return 875 } 876 w.Header().Set("Content-Type", "application/json") 877 fmt.Fprintf(w, string(jsonBytes)) 878 879 case "metadata": 880 switch action { 881 case "post": 882 jsonBytes, err := ioutil.ReadAll(r.Body) 883 if err != nil { 884 server.BadRequest(w, r, err) 885 return 886 } 887 if err := d.SetMetadata(uuid, jsonBytes); err != nil { 888 server.BadRequest(w, r, err) 889 return 890 } 891 892 case "get": 893 if d.Levels == nil || len(d.Levels) == 0 { 894 server.BadRequest(w, r, "tile metadata for imagetile %q was not set\n", d.DataName()) 895 return 896 } 897 metadata := struct { 898 MinTileCoord dvid.Point3d 899 MaxTileCoord dvid.Point3d 900 Levels TileSpec 901 }{ 902 d.MinTileCoord, 903 d.MaxTileCoord, 904 d.Levels, 905 } 906 jsonBytes, err := json.Marshal(metadata) 907 if err != nil { 908 server.BadRequest(w, r, err) 909 return 910 } 911 w.Header().Set("Content-Type", "application/json") 912 fmt.Fprintf(w, string(jsonBytes)) 913 } 914 timedLog.Infof("HTTP %s: metadata (%s)", r.Method, r.URL) 915 916 case "tile": 917 switch action { 918 case "post": 919 err := d.PostTile(ctx, w, r, parts) 920 if err != nil { 921 server.BadRequest(w, r, "Error in posting tile with URL %q: %v\n", url, err) 922 return 923 } 924 case "get": 925 if err := d.ServeTile(ctx, w, r, parts); err != nil { 926 server.BadRequest(w, r, err) 927 return 928 } 929 } 930 timedLog.Infof("HTTP %s: tile (%s)", r.Method, r.URL) 931 932 case "tilekey": 933 switch action { 934 case "get": 935 var err error 936 var hexkey string 937 if hexkey, err = d.getTileKey(ctx, w, r, parts); err != nil { 938 server.BadRequest(w, r, err) 939 return 940 } 941 w.Header().Set("Content-Type", "application/json") 942 fmt.Fprintf(w, `{"key": "%s"}`, hexkey) 943 timedLog.Infof("HTTP %s: tilekey (%s) returns %s", r.Method, r.URL, hexkey) 944 default: 945 server.BadRequest(w, r, fmt.Errorf("Cannot use HTTP %s for tilekey endpoint", action)) 946 return 947 } 948 949 case "raw", "isotropic": 950 if action == "post" { 951 server.BadRequest(w, r, "imagetile '%s' can only PUT tiles not images", d.DataName()) 952 return 953 } 954 if len(parts) < 7 { 955 server.BadRequest(w, r, "%q must be followed by shape/size/offset", parts[3]) 956 return 957 } 958 shapeStr, sizeStr, offsetStr := parts[4], parts[5], parts[6] 959 planeStr := dvid.DataShapeString(shapeStr) 960 plane, err := planeStr.DataShape() 961 if err != nil { 962 server.BadRequest(w, r, err) 963 return 964 } 965 if plane.ShapeDimensions() != 2 { 966 server.BadRequest(w, r, "Quadtrees can only return 2d images not %s", plane) 967 return 968 } 969 slice, err := dvid.NewSliceFromStrings(planeStr, offsetStr, sizeStr, "_") 970 if err != nil { 971 server.BadRequest(w, r, err) 972 return 973 } 974 source, err := datastore.GetDataByUUIDName(uuid, d.Source) 975 if err != nil { 976 server.BadRequest(w, r, err) 977 return 978 } 979 src, ok := source.(*imageblk.Data) 980 if !ok { 981 server.BadRequest(w, r, "Cannot construct imagetile for non-voxels data: %s", d.Source) 982 return 983 } 984 img, err := d.GetImage(ctx, src, slice, parts[3] == "isotropic") 985 if err != nil { 986 server.BadRequest(w, r, err) 987 return 988 } 989 var formatStr string 990 if len(parts) >= 8 { 991 formatStr = parts[7] 992 } 993 err = dvid.WriteImageHttp(w, img.Get(), formatStr) 994 if err != nil { 995 server.BadRequest(w, r, err) 996 return 997 } 998 timedLog.Infof("HTTP %s: tile-accelerated %s %s (%s)", r.Method, planeStr, parts[3], r.URL) 999 default: 1000 server.BadAPIRequest(w, r, d) 1001 } 1002 return 1003 } 1004 1005 // GetImage returns an image given a 2d orthogonal image description. Since imagetile tiles 1006 // have precomputed XY, XZ, and YZ orientations, reconstruction of the desired image should 1007 // be much faster than computing the image from voxel blocks. 1008 func (d *Data) GetImage(ctx storage.Context, src *imageblk.Data, geom dvid.Geometry, isotropic bool) (*dvid.Image, error) { 1009 // Iterate through tiles that intersect our geometry. 1010 if d.Levels == nil || len(d.Levels) == 0 { 1011 return nil, ErrNoMetadataSet 1012 } 1013 levelSpec := d.Levels[0] 1014 minSlice, err := dvid.Isotropy2D(src.VoxelSize, geom, isotropic) 1015 if err != nil { 1016 return nil, err 1017 } 1018 1019 // Create an image of appropriate size and type using source's ExtData creation. 1020 dstW := minSlice.Size().Value(0) 1021 dstH := minSlice.Size().Value(1) 1022 dst, err := src.BlankImage(dstW, dstH) 1023 if err != nil { 1024 return nil, err 1025 } 1026 1027 // Read each tile that intersects the geometry and store into final image. 1028 slice := minSlice.DataShape() 1029 tileW, tileH, err := slice.GetSize2D(levelSpec.TileSize) 1030 if err != nil { 1031 return nil, err 1032 } 1033 tileSize := dvid.Point2d{tileW, tileH} 1034 minPtX, minPtY, err := slice.GetSize2D(minSlice.StartPoint()) 1035 if err != nil { 1036 return nil, err 1037 } 1038 1039 wg := new(sync.WaitGroup) 1040 topLeftGlobal := dvid.Point2d{minPtX, minPtY} 1041 tilePt := topLeftGlobal.Chunk(tileSize) 1042 bottomRightGlobal := tilePt.MaxPoint(tileSize).(dvid.Point2d) 1043 y0 := int32(0) 1044 y1 := bottomRightGlobal[1] - minPtY + 1 1045 for y0 < dstH { 1046 x0 := int32(0) 1047 x1 := bottomRightGlobal[0] - minPtX + 1 1048 for x0 < dstW { 1049 wg.Add(1) 1050 go func(x0, y0, x1, y1 int32) { 1051 defer wg.Done() 1052 1053 // Get this tile from datastore 1054 tileCoord, err := slice.PlaneToChunkPoint3d(x0, y0, minSlice.StartPoint(), levelSpec.TileSize) 1055 goImg, err := d.getTileImage(ctx, TileReq{tileCoord, slice, 0}) 1056 if err != nil || goImg == nil { 1057 return 1058 } 1059 1060 // Get tile space coordinate for top left. 1061 curStart := dvid.Point2d{x0 + minPtX, y0 + minPtY} 1062 p := curStart.PointInChunk(tileSize) 1063 ptInTile := image.Point{int(p.Value(0)), int(p.Value(1))} 1064 1065 // Paste the pertinent rectangle from this tile into our destination. 1066 r := image.Rect(int(x0), int(y0), int(x1), int(y1)) 1067 draw.Draw(dst.GetDrawable(), r, goImg, ptInTile, draw.Src) 1068 }(x0, y0, x1, y1) 1069 x0 = x1 1070 x1 += tileW 1071 } 1072 y0 = y1 1073 y1 += tileH 1074 } 1075 wg.Wait() 1076 1077 if isotropic { 1078 dstW := int(geom.Size().Value(0)) 1079 dstH := int(geom.Size().Value(1)) 1080 dst, err = dst.ScaleImage(dstW, dstH) 1081 if err != nil { 1082 return nil, err 1083 } 1084 } 1085 return dst, nil 1086 } 1087 1088 // PostTile stores a tile. 1089 func (d *Data) PostTile(ctx storage.Context, w http.ResponseWriter, r *http.Request, parts []string) error { 1090 req, err := d.ParseTileReq(r, parts) 1091 if err != nil { 1092 return err 1093 } 1094 data, err := ioutil.ReadAll(r.Body) 1095 if err != nil { 1096 return err 1097 } 1098 db, err := datastore.GetKeyValueDB(d) 1099 if err != nil { 1100 return fmt.Errorf("Cannot open imagetile store: %v\n", err) 1101 } 1102 tk, err := NewTKeyByTileReq(req) 1103 if err != nil { 1104 return err 1105 } 1106 return db.Put(ctx, tk, data) 1107 } 1108 1109 // ServeTile returns a tile with appropriate Content-Type set. 1110 func (d *Data) ServeTile(ctx storage.Context, w http.ResponseWriter, r *http.Request, parts []string) error { 1111 1112 if d.Levels == nil || len(d.Levels) == 0 { 1113 return ErrNoMetadataSet 1114 } 1115 tileReq, err := d.ParseTileReq(r, parts) 1116 1117 queryStrings := r.URL.Query() 1118 noblanksStr := dvid.InstanceName(queryStrings.Get("noblanks")) 1119 var noblanks bool 1120 if noblanksStr == "true" { 1121 noblanks = true 1122 } 1123 var formatStr string 1124 if len(parts) >= 8 { 1125 formatStr = parts[7] 1126 } 1127 1128 data, err := d.getTileData(ctx, tileReq) 1129 if err != nil { 1130 server.BadRequest(w, r, err) 1131 return err 1132 } 1133 if len(data) == 0 { 1134 if noblanks { 1135 http.NotFound(w, r) 1136 return nil 1137 } 1138 img, err := d.getBlankTileImage(tileReq) 1139 if err != nil { 1140 return err 1141 } 1142 return dvid.WriteImageHttp(w, img, formatStr) 1143 } 1144 1145 switch d.Encoding { 1146 case LZ4: 1147 var img dvid.Image 1148 if err := img.Deserialize(data); err != nil { 1149 return err 1150 } 1151 data, err = img.GetPNG() 1152 w.Header().Set("Content-type", "image/png") 1153 case PNG: 1154 w.Header().Set("Content-type", "image/png") 1155 case JPG: 1156 w.Header().Set("Content-type", "image/jpeg") 1157 } 1158 if err != nil { 1159 server.BadRequest(w, r, err) 1160 return err 1161 } 1162 if _, err = w.Write(data); err != nil { 1163 return err 1164 } 1165 return nil 1166 } 1167 1168 // getTileKey returns the internal key as a hexadecimal string 1169 func (d *Data) getTileKey(ctx storage.Context, w http.ResponseWriter, r *http.Request, parts []string) (string, error) { 1170 req, err := d.ParseTileReq(r, parts) 1171 if err != nil { 1172 return "", err 1173 } 1174 tk, err := NewTKeyByTileReq(req) 1175 if err != nil { 1176 return "", err 1177 } 1178 key := ctx.ConstructKey(tk) 1179 return fmt.Sprintf("%x", key), nil 1180 } 1181 1182 // getTileImage returns a 2d tile image or a placeholder, useful for further stitching before 1183 // delivery of a final image. 1184 func (d *Data) getTileImage(ctx storage.Context, req TileReq) (image.Image, error) { 1185 if d.Levels == nil || len(d.Levels) == 0 { 1186 return nil, ErrNoMetadataSet 1187 } 1188 data, err := d.getTileData(ctx, req) 1189 if err != nil { 1190 return nil, err 1191 } 1192 1193 if len(data) == 0 { 1194 if d.Placeholder { 1195 if req.scale < 0 || req.scale >= Scaling(len(d.Levels)) { 1196 return nil, fmt.Errorf("Could not find tile specification at given scale %d", req.scale) 1197 } 1198 message := fmt.Sprintf("%s Tile coord %s @ scale %d", req.plane, req.tile, req.scale) 1199 return dvid.PlaceholderImage(req.plane, d.Levels[req.scale].TileSize, message) 1200 } 1201 return nil, nil // Not found 1202 } 1203 1204 var goImg image.Image 1205 switch d.Encoding { 1206 case LZ4: 1207 var img dvid.Image 1208 err = img.Deserialize(data) 1209 if err != nil { 1210 return nil, err 1211 } 1212 goImg = img.Get() 1213 case PNG: 1214 pngBuffer := bytes.NewBuffer(data) 1215 goImg, err = png.Decode(pngBuffer) 1216 case JPG: 1217 jpgBuffer := bytes.NewBuffer(data) 1218 goImg, err = jpeg.Decode(jpgBuffer) 1219 default: 1220 return nil, fmt.Errorf("Unknown tile encoding: %s", d.Encoding) 1221 } 1222 return goImg, err 1223 } 1224 1225 // getTileData returns 2d tile data straight from storage without decoding. 1226 func (d *Data) getTileData(ctx storage.Context, req TileReq) ([]byte, error) { 1227 // Don't allow negative tile coordinates for now. 1228 // TODO: Fully check negative coordinates for both tiles and voxels. 1229 if req.tile.Value(0) < 0 || req.tile.Value(1) < 0 || req.tile.Value(2) < 0 { 1230 return nil, nil 1231 } 1232 1233 db, err := datastore.GetKeyValueDB(d) 1234 if err != nil { 1235 return nil, err 1236 } 1237 1238 // Retrieve the tile data from datastore 1239 tk, err := NewTKeyByTileReq(req) 1240 if err != nil { 1241 return nil, err 1242 } 1243 data, err := db.Get(ctx, tk) 1244 if err != nil { 1245 return nil, fmt.Errorf("Error trying to GET from datastore: %v", err) 1246 } 1247 return data, nil 1248 } 1249 1250 // getBlankTileData returns zero 2d tile image. 1251 func (d *Data) getBlankTileImage(req TileReq) (image.Image, error) { 1252 levelSpec, found := d.Levels[req.scale] 1253 if !found { 1254 return nil, fmt.Errorf("Could not extract tiles for unspecified scale level %d", req.scale) 1255 } 1256 tileW, tileH, err := req.plane.GetSize2D(levelSpec.TileSize) 1257 if err != nil { 1258 return nil, err 1259 } 1260 img := image.NewGray(image.Rect(0, 0, int(tileW), int(tileH))) 1261 return img, nil 1262 } 1263 1264 // pow2 returns the power of 2 with the passed exponent. 1265 func pow2(exp uint8) int { 1266 pow := 1 1267 for i := uint8(1); i <= exp; i++ { 1268 pow *= 2 1269 } 1270 return pow 1271 } 1272 1273 // log2 returns the power of 2 necessary to cover the given value. 1274 func log2(value int32) uint8 { 1275 var exp uint8 1276 pow := int32(1) 1277 for { 1278 if pow >= value { 1279 return exp 1280 } 1281 pow *= 2 1282 exp++ 1283 } 1284 } 1285 1286 type outFunc func(TileReq, *dvid.Image) error 1287 1288 // Construct all tiles for an image with offset and send to out function. extractTiles assumes 1289 // the image and offset are in the XY plane. 1290 func (d *Data) extractTiles(v *imageblk.Voxels, offset dvid.Point, scale Scaling, outF outFunc) error { 1291 if d.Levels == nil || scale < 0 || scale >= Scaling(len(d.Levels)) { 1292 return fmt.Errorf("Bad scaling level specified: %d", scale) 1293 } 1294 levelSpec, found := d.Levels[scale] 1295 if !found { 1296 return fmt.Errorf("No scaling specs available for scaling level %d", scale) 1297 } 1298 srcW := v.Size().Value(0) 1299 srcH := v.Size().Value(1) 1300 1301 tileW, tileH, err := v.DataShape().GetSize2D(levelSpec.TileSize) 1302 if err != nil { 1303 return err 1304 } 1305 1306 // Split image into tiles and store into datastore. 1307 src, err := v.GetImage2d() 1308 if err != nil { 1309 return err 1310 } 1311 var x0, y0, x1, y1 int32 1312 y1 = tileH 1313 for y0 = 0; y0 < srcH; y0 += tileH { 1314 x1 = tileW 1315 for x0 = 0; x0 < srcW; x0 += tileW { 1316 tileRect := image.Rect(int(x0), int(y0), int(x1), int(y1)) 1317 tile, err := src.SubImage(tileRect) 1318 if err != nil { 1319 return err 1320 } 1321 tileCoord, err := v.DataShape().PlaneToChunkPoint3d(x0, y0, offset, levelSpec.TileSize) 1322 // fmt.Printf("Tile Coord: %s > %s\n", tileCoord, tileRect) 1323 req := NewTileReq(tileCoord, v.DataShape(), scale) 1324 if err = outF(req, tile); err != nil { 1325 return err 1326 } 1327 x1 += tileW 1328 } 1329 y1 += tileH 1330 } 1331 return nil 1332 } 1333 1334 // Returns function that stores a tile as an optionally compressed PNG image. 1335 func (d *Data) putTileFunc(versionID dvid.VersionID) (outFunc, error) { 1336 db, err := datastore.GetKeyValueDB(d) 1337 if err != nil { 1338 return nil, fmt.Errorf("Cannot open imagetile store: %v\n", err) 1339 } 1340 ctx := datastore.NewVersionedCtx(d, versionID) 1341 1342 return func(req TileReq, tile *dvid.Image) error { 1343 var err error 1344 var data []byte 1345 1346 switch d.Encoding { 1347 case LZ4: 1348 var compression dvid.Compression 1349 compression, err = dvid.NewCompression(dvid.LZ4, dvid.DefaultCompression) 1350 if err != nil { 1351 return err 1352 } 1353 data, err = tile.Serialize(compression, d.Checksum()) 1354 case PNG: 1355 data, err = tile.GetPNG() 1356 case JPG: 1357 data, err = tile.GetJPEG(d.Quality) 1358 } 1359 if err != nil { 1360 return err 1361 } 1362 var tk storage.TKey 1363 tk, err = NewTKeyByTileReq(req) 1364 if err != nil { 1365 return err 1366 } 1367 return db.Put(ctx, tk, data) 1368 }, nil 1369 } 1370 1371 func (d *Data) ConstructTiles(uuidStr string, tileSpec TileSpec, request datastore.Request) error { 1372 config := request.Settings() 1373 uuid, versionID, err := datastore.MatchingUUID(uuidStr) 1374 if err != nil { 1375 return err 1376 } 1377 1378 if err = datastore.AddToNodeLog(uuid, []string{request.Command.String()}); err != nil { 1379 return err 1380 } 1381 1382 source, err := datastore.GetDataByUUIDName(uuid, d.Source) 1383 if err != nil { 1384 return fmt.Errorf("Cannot get source %q for %q tile construction: %v", d.Source, d.DataName(), err) 1385 } 1386 src, ok := source.(*imageblk.Data) 1387 if !ok { 1388 return fmt.Errorf("Cannot construct imagetile for non-voxels data: %s", d.Source) 1389 } 1390 1391 // Get size of tile at lowest resolution. 1392 lastLevel := Scaling(len(tileSpec) - 1) 1393 loresSpec, found := tileSpec[lastLevel] 1394 if !found { 1395 return fmt.Errorf("Illegal tile spec. Should have levels 0 to absent %d.", lastLevel) 1396 } 1397 var loresSize [3]float64 1398 for i := 0; i < 3; i++ { 1399 loresSize[i] = float64(loresSpec.Resolution[i]) * float64(DefaultTileSize[i]) 1400 } 1401 loresMag := dvid.Point3d{1, 1, 1} 1402 for i := Scaling(0); i < lastLevel; i++ { 1403 levelMag := tileSpec[i].levelMag 1404 loresMag[0] *= levelMag[0] 1405 loresMag[1] *= levelMag[1] 1406 loresMag[2] *= levelMag[2] 1407 } 1408 1409 // Get min and max points in terms of distance. 1410 var minPtDist, maxPtDist [3]float64 1411 for i := uint8(0); i < 3; i++ { 1412 minPtDist[i] = float64(src.MinPoint.Value(i)) * float64(src.VoxelSize[i]) 1413 maxPtDist[i] = float64(src.MaxPoint.Value(i)) * float64(src.VoxelSize[i]) 1414 } 1415 1416 // Adjust min and max points for the tileable surface at lowest resolution. 1417 var minTiledPt, maxTiledPt dvid.Point3d 1418 for i := 0; i < 3; i++ { 1419 minInt, _ := math.Modf(minPtDist[i] / loresSize[i]) 1420 maxInt, _ := math.Modf(maxPtDist[i] / loresSize[i]) 1421 d.MinTileCoord[i] = int32(minInt) 1422 d.MaxTileCoord[i] = int32(maxInt) 1423 minTiledPt[i] = d.MinTileCoord[i] * DefaultTileSize[i] * loresMag[i] 1424 maxTiledPt[i] = (d.MaxTileCoord[i]+1)*DefaultTileSize[i]*loresMag[i] - 1 1425 } 1426 sizeVolume := maxTiledPt.Sub(minTiledPt).AddScalar(1) 1427 1428 // Save the current tile specification 1429 d.Levels = tileSpec 1430 if err := datastore.SaveDataByUUID(uuid, d); err != nil { 1431 return err 1432 } 1433 1434 // Setup swappable ExtData buffers (the stitched slices) so we can be generating tiles 1435 // at same time we are reading and stitching them. 1436 var bufferLock [2]sync.Mutex 1437 var sliceBuffers [2]*imageblk.Voxels 1438 var bufferNum int 1439 1440 // Get the planes we should tile. 1441 planes, err := config.GetShapes("planes", ";") 1442 if err != nil { 1443 return err 1444 } 1445 if planes == nil { 1446 // If no planes are specified, construct imagetile for 3 orthogonal planes. 1447 planes = []dvid.DataShape{dvid.XY, dvid.XZ, dvid.YZ} 1448 } 1449 1450 outF, err := d.putTileFunc(versionID) 1451 if err != nil { 1452 return err 1453 } 1454 1455 // Get bounds for tiling if specified 1456 minx, maxx, err := config.GetRange("xrange", ",") 1457 if err != nil { 1458 return err 1459 } 1460 miny, maxy, err := config.GetRange("yrange", ",") 1461 if err != nil { 1462 return err 1463 } 1464 minz, maxz, err := config.GetRange("zrange", ",") 1465 if err != nil { 1466 return err 1467 } 1468 1469 // sort the tile spec keys to iterate from highest to lowest resolution 1470 var sortedKeys []int 1471 for scaling, _ := range tileSpec { 1472 sortedKeys = append(sortedKeys, int(scaling)) 1473 } 1474 sort.Ints(sortedKeys) 1475 1476 for _, plane := range planes { 1477 timedLog := dvid.NewTimeLog() 1478 offset := minTiledPt.Duplicate() 1479 1480 switch { 1481 1482 case plane.Equals(dvid.XY): 1483 width, height, err := plane.GetSize2D(sizeVolume) 1484 if err != nil { 1485 return err 1486 } 1487 dvid.Debugf("Tiling XY image %d x %d pixels\n", width, height) 1488 z0 := src.MinPoint.Value(2) 1489 z1 := src.MaxPoint.Value(2) 1490 if minz != nil && z0 < *minz { 1491 z0 = *minz 1492 } 1493 if maxz != nil && z1 > *maxz { 1494 z1 = *maxz 1495 } 1496 for z := z0; z <= z1; z++ { 1497 server.BlockOnInteractiveRequests("imagetile.ConstructTiles [xy]") 1498 1499 sliceLog := dvid.NewTimeLog() 1500 offset = offset.Modify(map[uint8]int32{2: z}) 1501 slice, err := dvid.NewOrthogSlice(dvid.XY, offset, dvid.Point2d{width, height}) 1502 if err != nil { 1503 return err 1504 } 1505 bufferLock[bufferNum].Lock() 1506 sliceBuffers[bufferNum], err = src.NewVoxels(slice, nil) 1507 if err != nil { 1508 return err 1509 } 1510 if err = src.GetVoxels(versionID, sliceBuffers[bufferNum], ""); err != nil { 1511 return err 1512 } 1513 // Iterate through the different scales, extracting tiles at each resolution. 1514 go func(bufferNum int, offset dvid.Point) { 1515 defer bufferLock[bufferNum].Unlock() 1516 timedLog := dvid.NewTimeLog() 1517 for _, key := range sortedKeys { 1518 scaling := Scaling(key) 1519 levelSpec := tileSpec[scaling] 1520 if err != nil { 1521 dvid.Errorf("Error in tiling: %v\n", err) 1522 return 1523 } 1524 if err := d.extractTiles(sliceBuffers[bufferNum], offset, scaling, outF); err != nil { 1525 dvid.Errorf("Error in tiling: %v\n", err) 1526 return 1527 } 1528 if int(scaling) < len(tileSpec)-1 { 1529 if err := sliceBuffers[bufferNum].DownRes(levelSpec.levelMag); err != nil { 1530 dvid.Errorf("Error in tiling: %v\n", err) 1531 return 1532 } 1533 } 1534 } 1535 timedLog.Debugf("Tiled XY Tile using buffer %d", bufferNum) 1536 }(bufferNum, offset) 1537 1538 sliceLog.Infof("Read XY Tile @ Z = %d, now tiling...", z) 1539 bufferNum = (bufferNum + 1) % 2 1540 } 1541 timedLog.Infof("Total time to generate XY Tiles") 1542 1543 case plane.Equals(dvid.XZ): 1544 width, height, err := plane.GetSize2D(sizeVolume) 1545 if err != nil { 1546 return err 1547 } 1548 dvid.Debugf("Tiling XZ image %d x %d pixels\n", width, height) 1549 y0 := src.MinPoint.Value(1) 1550 y1 := src.MaxPoint.Value(1) 1551 if miny != nil && y0 < *miny { 1552 y0 = *miny 1553 } 1554 if maxy != nil && y1 > *maxy { 1555 y1 = *maxy 1556 } 1557 for y := y0; y <= y1; y++ { 1558 server.BlockOnInteractiveRequests("imagetile.ConstructTiles [xz]") 1559 1560 sliceLog := dvid.NewTimeLog() 1561 offset = offset.Modify(map[uint8]int32{1: y}) 1562 slice, err := dvid.NewOrthogSlice(dvid.XZ, offset, dvid.Point2d{width, height}) 1563 if err != nil { 1564 return err 1565 } 1566 bufferLock[bufferNum].Lock() 1567 sliceBuffers[bufferNum], err = src.NewVoxels(slice, nil) 1568 if err != nil { 1569 return err 1570 } 1571 if err = src.GetVoxels(versionID, sliceBuffers[bufferNum], ""); err != nil { 1572 return err 1573 } 1574 // Iterate through the different scales, extracting tiles at each resolution. 1575 go func(bufferNum int, offset dvid.Point) { 1576 defer bufferLock[bufferNum].Unlock() 1577 timedLog := dvid.NewTimeLog() 1578 for _, key := range sortedKeys { 1579 scaling := Scaling(key) 1580 levelSpec := tileSpec[scaling] 1581 if err != nil { 1582 dvid.Errorf("Error in tiling: %v\n", err) 1583 return 1584 } 1585 if err := d.extractTiles(sliceBuffers[bufferNum], offset, scaling, outF); err != nil { 1586 dvid.Errorf("Error in tiling: %v\n", err) 1587 return 1588 } 1589 if int(scaling) < len(tileSpec)-1 { 1590 if err := sliceBuffers[bufferNum].DownRes(levelSpec.levelMag); err != nil { 1591 dvid.Errorf("Error in tiling: %v\n", err) 1592 return 1593 } 1594 } 1595 } 1596 timedLog.Debugf("Tiled XZ Tile using buffer %d", bufferNum) 1597 }(bufferNum, offset) 1598 1599 sliceLog.Infof("Read XZ Tile @ Y = %d, now tiling...", y) 1600 bufferNum = (bufferNum + 1) % 2 1601 } 1602 timedLog.Infof("Total time to generate XZ Tiles") 1603 1604 case plane.Equals(dvid.YZ): 1605 width, height, err := plane.GetSize2D(sizeVolume) 1606 if err != nil { 1607 return err 1608 } 1609 dvid.Debugf("Tiling YZ image %d x %d pixels\n", width, height) 1610 x0 := src.MinPoint.Value(0) 1611 x1 := src.MaxPoint.Value(0) 1612 if minx != nil && x0 < *minx { 1613 x0 = *minx 1614 } 1615 if maxz != nil && x1 > *maxx { 1616 x1 = *maxx 1617 } 1618 for x := x0; x <= x1; x++ { 1619 server.BlockOnInteractiveRequests("imagetile.ConstructTiles [yz]") 1620 1621 sliceLog := dvid.NewTimeLog() 1622 offset = offset.Modify(map[uint8]int32{0: x}) 1623 slice, err := dvid.NewOrthogSlice(dvid.YZ, offset, dvid.Point2d{width, height}) 1624 if err != nil { 1625 return err 1626 } 1627 bufferLock[bufferNum].Lock() 1628 sliceBuffers[bufferNum], err = src.NewVoxels(slice, nil) 1629 if err != nil { 1630 return err 1631 } 1632 if err = src.GetVoxels(versionID, sliceBuffers[bufferNum], ""); err != nil { 1633 return err 1634 } 1635 // Iterate through the different scales, extracting tiles at each resolution. 1636 go func(bufferNum int, offset dvid.Point) { 1637 defer bufferLock[bufferNum].Unlock() 1638 timedLog := dvid.NewTimeLog() 1639 for _, key := range sortedKeys { 1640 scaling := Scaling(key) 1641 levelSpec := tileSpec[scaling] 1642 outF, err := d.putTileFunc(versionID) 1643 if err != nil { 1644 dvid.Errorf("Error in tiling: %v\n", err) 1645 return 1646 } 1647 if err := d.extractTiles(sliceBuffers[bufferNum], offset, scaling, outF); err != nil { 1648 dvid.Errorf("Error in tiling: %v\n", err) 1649 return 1650 } 1651 if int(scaling) < len(tileSpec)-1 { 1652 if err := sliceBuffers[bufferNum].DownRes(levelSpec.levelMag); err != nil { 1653 dvid.Errorf("Error in tiling: %v\n", err) 1654 return 1655 } 1656 } 1657 } 1658 timedLog.Debugf("Tiled YZ Tile using buffer %d", bufferNum) 1659 }(bufferNum, offset) 1660 1661 sliceLog.Debugf("Read YZ Tile @ X = %d, now tiling...", x) 1662 bufferNum = (bufferNum + 1) % 2 1663 } 1664 timedLog.Infof("Total time to generate YZ Tiles") 1665 1666 default: 1667 dvid.Infof("Skipping request to tile '%s'. Unsupported.", plane) 1668 } 1669 } 1670 return nil 1671 }