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  }