github.com/janelia-flyem/dvid@v1.0.0/datatype/googlevoxels/googlevoxels.go (about)

     1  /*
     2  Package googlevoxels implements DVID support for multi-scale tiles and volumes in XY, XZ,
     3  and YZ orientation using the Google BrainMaps API.
     4  */
     5  package googlevoxels
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/gob"
    10  	"encoding/json"
    11  	"fmt"
    12  	"image"
    13  	"io"
    14  	"io/ioutil"
    15  	"net/http"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/janelia-flyem/dvid/datastore"
    20  	"github.com/janelia-flyem/dvid/datatype/imagetile"
    21  	"github.com/janelia-flyem/dvid/dvid"
    22  	"github.com/janelia-flyem/dvid/server"
    23  	"github.com/janelia-flyem/dvid/storage"
    24  
    25  	"golang.org/x/oauth2"
    26  	"golang.org/x/oauth2/google"
    27  
    28  	lz4 "github.com/janelia-flyem/go/golz4-updated"
    29  )
    30  
    31  const (
    32  	Version  = "0.1"
    33  	RepoURL  = "github.com/janelia-flyem/dvid/datatype/googlevoxels"
    34  	TypeName = "googlevoxels"
    35  )
    36  
    37  const helpMessage = `
    38  API for datatypes derived from googlevoxels (github.com/janelia-flyem/dvid/datatype/googlevoxels)
    39  =================================================================================================
    40  
    41  Command-line:
    42  
    43  $ dvid repo <UUID> new googlevoxels <data name> <settings...>
    44  
    45  	Adds voxel support using Google BrainMaps API.
    46  
    47  	Example:
    48  
    49  	$ dvid repo 3f8c new googlevoxels grayscale volumeid=281930192:stanford jwtfile=/foo/myname-319.json
    50  
    51      Arguments:
    52  
    53      UUID           Hexadecimal string with enough characters to uniquely identify a version node.
    54      data name      Name of data to create, e.g., "mygrayscale"
    55      settings       Configuration settings in "key=value" format separated by spaces.
    56  
    57      Required Configuration Settings (case-insensitive keys)
    58  
    59      volumeid       The globally unique identifier of the volume within Google BrainMaps API.
    60      jwtfile        Path to JSON Web Token file downloaded from http://console.developers.google.com.
    61                     Under the BrainMaps API, visit the Credentials area, create credentials for a
    62                     service account key, then download that JWT file.
    63  
    64      Optional Configuration Settings (case-insensitive keys)
    65  
    66      tilesize       Default size in pixels along one dimension of square tile.  If unspecified, 512.
    67  
    68  
    69  $ dvid googlevoxels volumes <jwtfile>
    70  
    71  	Contacts Google BrainMaps API and returns the available volume ids for a user identified by a 
    72  	JSON Web Token (JWT) file.
    73  
    74  	Example:
    75  
    76  	$ dvid googlevoxels volumes /foo/myname-319.json
    77  
    78      Arguments:
    79  
    80      jwtfile        Path to JSON Web Token file downloaded from http://console.developers.google.com.
    81                     Under the BrainMaps API, visit the Credentials area, create credentials for a
    82                     service account key, then download that JWT file.
    83  
    84  
    85      ------------------
    86  
    87  HTTP API (Level 2 REST):
    88  
    89  GET  <api URL>/node/<UUID>/<data name>/help
    90  
    91  	Returns data-specific help message.
    92  
    93  
    94  GET  <api URL>/node/<UUID>/<data name>/info
    95  
    96      Retrieves characteristics of this data in JSON format.
    97  
    98      Example: 
    99  
   100      GET <api URL>/node/3f8c/grayscale/info
   101  
   102      Arguments:
   103  
   104      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   105      data name     Name of googlevoxels data.
   106  
   107  
   108  GET  <api URL>/node/<UUID>/<data name>/tile/<dims>/<scaling>/<tile coord>[?options]
   109  
   110      Retrieves a tile of named data within a version node.  The default tile size is used unless
   111      the query string "tilesize" is provided.
   112  
   113      Example: 
   114  
   115      GET <api URL>/node/3f8c/grayscale/tile/xy/0/10_10_20
   116  
   117      Arguments:
   118  
   119      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   120      data name     Name of data to add.
   121      dims          The axes of data extraction in form "i_j_k,..."  Example: "0_2" can be XZ.
   122                      Slice strings ("xy", "xz", or "yz") are also accepted.
   123      scaling       Value from 0 (original resolution) to N where each step is downres by 2.
   124      tile coord    The tile coordinate in "x_y_z" format.  See discussion of scaling above.
   125  
   126    	Query-string options:
   127  
   128      tilesize      Size in pixels along one dimension of square tile.
   129    	noblanks	  If true, any tile request for tiles outside the currently stored extents
   130    				  will return a placeholder.
   131      format        "png", "jpeg" (default: "png")
   132                      jpeg allows lossy quality setting, e.g., "jpeg:80"  (0 <= quality <= 100)
   133                      png allows compression levels, e.g., "png:7"  (0 <= level <= 9)
   134  
   135  
   136  GET  <api URL>/node/<UUID>/<data name>/raw/<dims>/<size>/<offset>[/<format>][?queryopts]
   137  
   138      Retrieves either 2d images (PNG by default) or 3d binary data, depending on the dims parameter.  
   139      The 3d binary data response has "Content-type" set to "application/octet-stream" and is an array of 
   140      voxel values in ZYX order (X iterates most rapidly).
   141  
   142      Example: 
   143  
   144      GET <api URL>/node/3f8c/segmentation/raw/0_1/512_256/0_0_100/jpg:80
   145  
   146      Returns a raw XY slice (0th and 1st dimensions) with width (x) of 512 voxels and
   147      height (y) of 256 voxels with offset (0,0,100) in JPG format with quality 80.
   148      By "raw", we mean that no additional processing is applied based on voxel
   149      resolutions to make sure the retrieved image has isotropic pixels.
   150      The example offset assumes the "grayscale" data in version node "3f8c" is 3d.
   151      The "Content-type" of the HTTP response should agree with the requested format.
   152      For example, returned PNGs will have "Content-type" of "image/png", and returned
   153      nD data will be "application/octet-stream". 
   154  
   155      Arguments:
   156  
   157      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   158      data name     Name of data to add.
   159      dims          The axes of data extraction in form "i_j_k,..."  
   160                      Slice strings ("xy", "xz", or "yz") are also accepted.
   161                      Example: "0_2" is XZ, and "0_1_2" is a 3d subvolume.
   162      size          Size in voxels along each dimension specified in <dims>.
   163      offset        Gives coordinate of first voxel using dimensionality of data.
   164      format        Valid formats depend on the dimensionality of the request and formats
   165                      available in server implementation.
   166                    2D: "png", "jpg" (default: "png")
   167                      jpg allows lossy quality setting, e.g., "jpg:80"
   168                    nD: uses default "octet-stream".
   169  
   170      Query-string Options:
   171  
   172      compression   Allows retrieval or submission of 3d data in "raw" (default) or "lz4" format.  
   173                       The 2d data will ignore this and use the image-based codec.
   174    	scale         Default is 0.  For scale N, returns an image down-sampled by a factor of 2^N.
   175      throttle      Only works for 3d data requests.  If "true", makes sure only N compute-intense operation 
   176      				(all API calls that can be throttled) are handled.  If the server can't initiate the API 
   177      				call right away, a 503 (Service Unavailable) status code is returned.
   178  `
   179  
   180  func init() {
   181  	datastore.Register(NewType())
   182  
   183  	// Need to register types that will be used to fulfill interfaces.
   184  	gob.Register(&Type{})
   185  	gob.Register(&Data{})
   186  }
   187  
   188  var (
   189  	DefaultTileSize   int32  = 512
   190  	DefaultTileFormat string = "png"
   191  	bmapsPrefix       string = "https://brainmaps.googleapis.com/v1"
   192  )
   193  
   194  // Type embeds the datastore's Type to create a unique type with tile functions.
   195  // Refinements of general tile types can be implemented by embedding this type,
   196  // choosing appropriate # of channels and bytes/voxel, overriding functions as
   197  // needed, and calling datastore.Register().
   198  // Note that these fields are invariant for all instances of this type.  Fields
   199  // that can change depending on the type of data (e.g., resolution) should be
   200  // in the Data type.
   201  type Type struct {
   202  	datastore.Type
   203  }
   204  
   205  // NewDatatype returns a pointer to a new voxels Datatype with default values set.
   206  func NewType() *Type {
   207  	return &Type{
   208  		datastore.Type{
   209  			Name:    "googlevoxels",
   210  			URL:     "github.com/janelia-flyem/dvid/datatype/googlevoxels",
   211  			Version: "0.1",
   212  			Requirements: &storage.Requirements{
   213  				Batcher: true,
   214  			},
   215  		},
   216  	}
   217  }
   218  
   219  // --- TypeService interface ---
   220  
   221  // NewData returns a pointer to new googlevoxels data with default values.
   222  func (dtype *Type) NewDataService(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (datastore.DataService, error) {
   223  	// Make sure we have needed volumeid and authentication key.
   224  	volumeid, found, err := c.GetString("volumeid")
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	if !found {
   229  		return nil, fmt.Errorf("Cannot make googlevoxels data without valid 'volumeid' setting.")
   230  	}
   231  	jwtfile, found, err := c.GetString("jwtfile")
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  	if !found {
   236  		return nil, fmt.Errorf("Cannot make googlevoxels data without valid 'jwtfile' specifying path to JSON Web Token")
   237  	}
   238  
   239  	// Read in the JSON Web Token
   240  	jwtdata, err := ioutil.ReadFile(jwtfile)
   241  	if err != nil {
   242  		return nil, fmt.Errorf("Cannot load JSON Web Token file (%s): %v", jwtfile, err)
   243  	}
   244  	conf, err := google.JWTConfigFromJSON(jwtdata, "https://www.googleapis.com/auth/brainmaps")
   245  	if err != nil {
   246  		return nil, fmt.Errorf("Cannot establish JWT Config file from Google: %v", err)
   247  	}
   248  	client := conf.Client(oauth2.NoContext)
   249  
   250  	// Make URL call to get the available scaled volumes.
   251  	url := fmt.Sprintf("%s/volumes/%s", bmapsPrefix, volumeid)
   252  	resp, err := client.Get(url)
   253  	if err != nil {
   254  		return nil, fmt.Errorf("Error getting volume metadata from Google: %v", err)
   255  	}
   256  	defer resp.Body.Close()
   257  	if resp.StatusCode != http.StatusOK {
   258  		return nil, fmt.Errorf("Unexpected status code %d returned when getting volume metadata for %q", resp.StatusCode, volumeid)
   259  	}
   260  	metadata, err := ioutil.ReadAll(resp.Body)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  	var m struct {
   265  		Geoms Geometries `json:"geometry"`
   266  	}
   267  	if err := json.Unmarshal(metadata, &m); err != nil {
   268  		return nil, fmt.Errorf("Error decoding volume JSON metadata: %v", err)
   269  	}
   270  	dvid.Infof("Successfully got geometries:\nmetadata:\n%s\nparsed JSON:\n%v\n", metadata, m)
   271  
   272  	// Compute the mapping from tile scale/orientation to scaled volume index.
   273  	geomMap := GeometryMap{}
   274  
   275  	// (1) Find the highest resolution geometry.
   276  	var highResIndex GeometryIndex
   277  	minVoxelSize := dvid.NdFloat32{10000, 10000, 10000}
   278  	for i, geom := range m.Geoms {
   279  		if geom.PixelSize[0] < minVoxelSize[0] || geom.PixelSize[1] < minVoxelSize[1] || geom.PixelSize[2] < minVoxelSize[2] {
   280  			minVoxelSize = geom.PixelSize
   281  			highResIndex = GeometryIndex(i)
   282  		}
   283  	}
   284  	dvid.Infof("Google voxels %q: found highest resolution was geometry %d: %s\n", name, highResIndex, minVoxelSize)
   285  
   286  	// (2) For all geometries, find out what the scaling is relative to the highest resolution pixel size.
   287  	for i, geom := range m.Geoms {
   288  		if i == int(highResIndex) {
   289  			geomMap[GSpec{0, XY}] = highResIndex
   290  			geomMap[GSpec{0, XZ}] = highResIndex
   291  			geomMap[GSpec{0, YZ}] = highResIndex
   292  			geomMap[GSpec{0, XYZ}] = highResIndex
   293  		} else {
   294  			scaleX := geom.PixelSize[0] / minVoxelSize[0]
   295  			scaleY := geom.PixelSize[1] / minVoxelSize[1]
   296  			scaleZ := geom.PixelSize[2] / minVoxelSize[2]
   297  			var shape Shape
   298  			switch {
   299  			case scaleX > scaleZ && scaleY > scaleZ:
   300  				shape = XY
   301  			case scaleX > scaleY && scaleZ > scaleY:
   302  				shape = XZ
   303  			case scaleY > scaleX && scaleZ > scaleX:
   304  				shape = YZ
   305  			default:
   306  				shape = XYZ
   307  			}
   308  			var mag float32
   309  			if scaleX > mag {
   310  				mag = scaleX
   311  			}
   312  			if scaleY > mag {
   313  				mag = scaleY
   314  			}
   315  			if scaleZ > mag {
   316  				mag = scaleZ
   317  			}
   318  			scaling := log2(mag)
   319  			geomMap[GSpec{scaling, shape}] = GeometryIndex(i)
   320  			dvid.Infof("%s at scaling %d set to geometry %d: resolution %s\n", shape, scaling, i, geom.PixelSize)
   321  		}
   322  	}
   323  
   324  	// Create a client that will be authorized and authenticated on behalf of the account.
   325  
   326  	// Initialize the googlevoxels data
   327  	basedata, err := datastore.NewDataService(dtype, uuid, id, name, c)
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  	data := &Data{
   332  		Data: basedata,
   333  		Properties: Properties{
   334  			VolumeID:     volumeid,
   335  			JWT:          string(jwtdata),
   336  			TileSize:     DefaultTileSize,
   337  			GeomMap:      geomMap,
   338  			Scales:       m.Geoms,
   339  			HighResIndex: highResIndex,
   340  		},
   341  		client: client,
   342  	}
   343  	return data, nil
   344  }
   345  
   346  // Do handles command-line requests to the Google BrainMaps API
   347  func (dtype *Type) Do(cmd datastore.Request, reply *datastore.Response) error {
   348  	switch cmd.Argument(1) {
   349  	case "volumes":
   350  		// Read in the JSON Web Token
   351  		jwtdata, err := ioutil.ReadFile(cmd.Argument(2))
   352  		if err != nil {
   353  			return fmt.Errorf("Cannot load JSON Web Token file (%s): %v", cmd.Argument(2), err)
   354  		}
   355  		conf, err := google.JWTConfigFromJSON(jwtdata, "https://www.googleapis.com/auth/brainmaps")
   356  		if err != nil {
   357  			return fmt.Errorf("Cannot establish JWT Config file from Google: %v", err)
   358  		}
   359  		client := conf.Client(oauth2.NoContext)
   360  
   361  		// Make the call.
   362  		url := fmt.Sprintf("%s/volumes", bmapsPrefix)
   363  		resp, err := client.Get(url)
   364  		if err != nil {
   365  			return fmt.Errorf("Error getting volumes metadata from Google: %v", err)
   366  		}
   367  		defer resp.Body.Close()
   368  		if resp.StatusCode != http.StatusOK {
   369  			return fmt.Errorf("Unexpected status code %d returned when getting volumes for user", resp.StatusCode)
   370  		}
   371  		metadata, err := ioutil.ReadAll(resp.Body)
   372  		if err != nil {
   373  			return err
   374  		}
   375  		reply.Text = string(metadata)
   376  		return nil
   377  
   378  	default:
   379  		return fmt.Errorf("unknown command for type %s", dtype.GetTypeName())
   380  	}
   381  }
   382  
   383  // log2 returns the power of 2 necessary to cover the given value.
   384  func log2(value float32) Scaling {
   385  	var exp Scaling
   386  	pow := float32(1.0)
   387  	for {
   388  		if pow >= value {
   389  			return exp
   390  		}
   391  		pow *= 2
   392  		exp++
   393  	}
   394  }
   395  
   396  func (dtype *Type) Help() string {
   397  	return helpMessage
   398  }
   399  
   400  // GSpec encapsulates the scale and orientation of a tile.
   401  type GSpec struct {
   402  	scaling Scaling
   403  	shape   Shape
   404  }
   405  
   406  func (ts GSpec) MarshalBinary() ([]byte, error) {
   407  	return []byte{byte(ts.scaling), byte(ts.shape)}, nil
   408  }
   409  
   410  func (ts *GSpec) UnmarshalBinary(data []byte) error {
   411  	if len(data) != 2 {
   412  		return fmt.Errorf("GSpec serialization is 2 bytes.  Got %d bytes instead: %v", len(data), data)
   413  	}
   414  	ts.scaling = Scaling(data[0])
   415  	ts.shape = Shape(data[1])
   416  	return nil
   417  }
   418  
   419  // GetGSpec returns a GSpec for a given scale and dvid Geometry.
   420  func GetGSpec(scaling Scaling, shape dvid.DataShape) (*GSpec, error) {
   421  	ts := new(GSpec)
   422  	ts.scaling = scaling
   423  	if err := ts.shape.FromShape(shape); err != nil {
   424  		return nil, err
   425  	}
   426  	return ts, nil
   427  }
   428  
   429  // Scaling describes the resolution where 0 is the highest resolution
   430  type Scaling uint8
   431  
   432  // Shape describes the orientation of a 2d or 3d image.
   433  type Shape uint8
   434  
   435  const (
   436  	XY Shape = iota
   437  	XZ
   438  	YZ
   439  	XYZ
   440  )
   441  
   442  func (s *Shape) FromShape(shape dvid.DataShape) error {
   443  	switch {
   444  	case shape.Equals(dvid.XY):
   445  		*s = XY
   446  	case shape.Equals(dvid.XZ):
   447  		*s = XZ
   448  	case shape.Equals(dvid.YZ):
   449  		*s = YZ
   450  	case shape.Equals(dvid.Vol3d):
   451  		*s = XYZ
   452  	default:
   453  		return fmt.Errorf("No Google BrainMaps shape corresponds to DVID %s shape", shape)
   454  	}
   455  	return nil
   456  }
   457  
   458  func (s Shape) String() string {
   459  	switch s {
   460  	case XY:
   461  		return "XY"
   462  	case XZ:
   463  		return "XZ"
   464  	case YZ:
   465  		return "YZ"
   466  	case XYZ:
   467  		return "XYZ"
   468  	default:
   469  		return "Unknown orientation"
   470  	}
   471  }
   472  
   473  // GeometryMap provides a mapping from DVID scale (0 is highest res) and tile orientation
   474  // to the specific geometry (Google "scale" value) that supports it.
   475  type GeometryMap map[GSpec]GeometryIndex
   476  
   477  func (gm GeometryMap) MarshalJSON() ([]byte, error) {
   478  	s := "{"
   479  	mapStr := make([]string, len(gm))
   480  	i := 0
   481  	for ts, gi := range gm {
   482  		mapStr[i] = fmt.Sprintf(`"%s:%d": %d`, ts.shape, ts.scaling, gi)
   483  		i++
   484  	}
   485  	s += strings.Join(mapStr, ",")
   486  	s += "}"
   487  	return []byte(s), nil
   488  }
   489  
   490  type GeometryIndex int
   491  
   492  // Geometry corresponds to a Volume Geometry in Google BrainMaps API
   493  type Geometry struct {
   494  	VolumeSize   dvid.Point3d   `json:"volumeSize"`
   495  	ChannelCount uint32         `json:"channelCount"`
   496  	ChannelType  string         `json:"channelType"`
   497  	PixelSize    dvid.NdFloat32 `json:"pixelSize"`
   498  }
   499  
   500  // JSON from Google API encodes unsigned long as string because javascript has limited max
   501  // integers due to Javascript number types using double float.
   502  
   503  type uint3d struct {
   504  	X uint32
   505  	Y uint32
   506  	Z uint32
   507  }
   508  
   509  func (u *uint3d) UnmarshalJSON(b []byte) error {
   510  	var m struct {
   511  		X string `json:"x"`
   512  		Y string `json:"y"`
   513  		Z string `json:"z"`
   514  	}
   515  	if err := json.Unmarshal(b, &m); err != nil {
   516  		return err
   517  	}
   518  	x, err := strconv.Atoi(m.X)
   519  	if err != nil {
   520  		return fmt.Errorf("Could not parse X coordinate with unsigned long: %v", err)
   521  	}
   522  	u.X = uint32(x)
   523  
   524  	y, err := strconv.Atoi(m.Y)
   525  	if err != nil {
   526  		return fmt.Errorf("Could not parse Y coordinate with unsigned long: %v", err)
   527  	}
   528  	u.Y = uint32(y)
   529  
   530  	z, err := strconv.Atoi(m.Z)
   531  	if err != nil {
   532  		return fmt.Errorf("Could not parse Z coordinate with unsigned long: %v", err)
   533  	}
   534  	u.Z = uint32(z)
   535  	return nil
   536  }
   537  
   538  func (i uint3d) String() string {
   539  	return fmt.Sprintf("%d x %d x %d", i.X, i.Y, i.Z)
   540  }
   541  
   542  type float3d struct {
   543  	X float32 `json:"x"`
   544  	Y float32 `json:"y"`
   545  	Z float32 `json:"z"`
   546  }
   547  
   548  func (f float3d) String() string {
   549  	return fmt.Sprintf("%f x %f x %f", f.X, f.Y, f.Z)
   550  }
   551  
   552  func (g *Geometry) UnmarshalJSON(b []byte) error {
   553  	if g == nil {
   554  		return fmt.Errorf("Can't unmarshal JSON into nil Geometry")
   555  	}
   556  	var m struct {
   557  		VolumeSize   uint3d  `json:"volumeSize"`
   558  		ChannelCount string  `json:"channelCount"`
   559  		ChannelType  string  `json:"channelType"`
   560  		PixelSize    float3d `json:"pixelSize"`
   561  	}
   562  	if err := json.Unmarshal(b, &m); err != nil {
   563  		return err
   564  	}
   565  	g.VolumeSize = dvid.Point3d{int32(m.VolumeSize.X), int32(m.VolumeSize.Y), int32(m.VolumeSize.Z)}
   566  	g.PixelSize = dvid.NdFloat32{m.PixelSize.X, m.PixelSize.Y, m.PixelSize.Z}
   567  	channels, err := strconv.Atoi(m.ChannelCount)
   568  	if err != nil {
   569  		return fmt.Errorf("Could not parse channelCount: %v", err)
   570  	}
   571  	g.ChannelCount = uint32(channels)
   572  	g.ChannelType = m.ChannelType
   573  	return nil
   574  }
   575  
   576  type Geometries []Geometry
   577  
   578  // GoogleSubvolGeom encapsulates all information needed for voxel retrieval (aside from authentication)
   579  // from the Google BrainMaps API, as well as processing the returned data.
   580  type GoogleSubvolGeom struct {
   581  	shape    Shape
   582  	offset   dvid.Point3d
   583  	size     dvid.Point3d // This is the size we can retrieve, not necessarily the requested size
   584  	sizeWant dvid.Point3d // This is the requested size.
   585  	gi       GeometryIndex
   586  	edge     bool // Is the tile on the edge, i.e., partially outside a scaled volume?
   587  	outside  bool // Is the tile totally outside any scaled volume?
   588  
   589  	// cached data that immediately follows from the geometry index
   590  	channelCount  uint32
   591  	channelType   string
   592  	bytesPerVoxel int32
   593  }
   594  
   595  // GetGoogleSubvolGeom returns a google-specific voxel spec, which includes how the data is positioned relative to
   596  // scaled volume boundaries.  Not that the size parameter is the desired size and not what is required to fit
   597  // within a scaled volume.
   598  func (d *Data) GetGoogleSubvolGeom(scaling Scaling, shape dvid.DataShape, offset dvid.Point3d, size dvid.Point) (*GoogleSubvolGeom, error) {
   599  	gsg := new(GoogleSubvolGeom)
   600  	if err := gsg.shape.FromShape(shape); err != nil {
   601  		return nil, err
   602  	}
   603  	gsg.offset = offset
   604  
   605  	// If 2d plane, convert combination of plane and size into 3d size.
   606  	if size.NumDims() == 2 {
   607  		size2d := size.(dvid.Point2d)
   608  		sizeWant, err := dvid.GetPoint3dFrom2d(shape, size2d, 1)
   609  		if err != nil {
   610  			return nil, err
   611  		}
   612  		gsg.sizeWant = sizeWant
   613  	} else {
   614  		var ok bool
   615  		gsg.sizeWant, ok = size.(dvid.Point3d)
   616  		if !ok {
   617  			return nil, fmt.Errorf("Can't convert %v to dvid.Point3d", size)
   618  		}
   619  	}
   620  
   621  	// Determine which geometry is appropriate given the scaling and the shape/orientation
   622  	tileSpec, err := GetGSpec(scaling, shape)
   623  	if err != nil {
   624  		return nil, err
   625  	}
   626  	geomIndex, found := d.GeomMap[*tileSpec]
   627  	if !found {
   628  		return nil, fmt.Errorf("Could not find scaled volume in %q for %s with scaling %d", d.DataName(), shape, scaling)
   629  	}
   630  	geom := d.Scales[geomIndex]
   631  	gsg.gi = geomIndex
   632  	gsg.channelCount = geom.ChannelCount
   633  	gsg.channelType = geom.ChannelType
   634  
   635  	// Get the # bytes for each pixel
   636  	switch geom.ChannelType {
   637  	case "UINT8":
   638  		gsg.bytesPerVoxel = 1
   639  	case "FLOAT":
   640  		gsg.bytesPerVoxel = 4
   641  	case "UINT64":
   642  		gsg.bytesPerVoxel = 8
   643  	default:
   644  		return nil, fmt.Errorf("Unknown volume channel type in %s: %s", d.DataName(), geom.ChannelType)
   645  	}
   646  
   647  	// Check if the requested area is completely outside the volume.
   648  	volumeSize := geom.VolumeSize
   649  	if offset[0] >= volumeSize[0] || offset[1] >= volumeSize[1] || offset[2] >= volumeSize[2] {
   650  		gsg.outside = true
   651  		return gsg, nil
   652  	}
   653  
   654  	// Check if the requested shape is on the edge and adjust size.
   655  	adjSize := gsg.sizeWant
   656  	maxpt := offset.Add(adjSize)
   657  	for i := uint8(0); i < 3; i++ {
   658  		if maxpt.Value(i) > volumeSize[i] {
   659  			gsg.edge = true
   660  			adjSize[i] = volumeSize[i] - offset[i]
   661  		}
   662  	}
   663  	gsg.size = adjSize
   664  
   665  	return gsg, nil
   666  }
   667  
   668  // GetURL returns the base API URL for retrieving an image.  Note that the authentication key
   669  // or token needs to be added to the returned string to form a valid URL.  The formatStr
   670  // parameter is of the form "jpeg" or "jpeg:80" or "png:8" where an optional compression
   671  // level follows the image format and a colon.  Leave formatStr empty for default.
   672  func (gsg GoogleSubvolGeom) GetURL(volumeid, formatStr string) (url string, opts io.Reader, err error) {
   673  	url = fmt.Sprintf("%s/volumes/%s", bmapsPrefix, volumeid)
   674  
   675  	jsonSpec := fmt.Sprintf(`{"geometry": {"corner":"%d,%d,%d","size":"%d,%d,%d","scale": %d}`,
   676  		gsg.offset[0], gsg.offset[1], gsg.offset[2],
   677  		gsg.size[0], gsg.size[1], gsg.size[2], gsg.gi)
   678  	if gsg.shape == XYZ {
   679  		url += "/subvolume:binary"
   680  		if formatStr != "" {
   681  			jsonSpec += `,"subvolumeFormat": "SINGLE_IMAGE"`
   682  		} else {
   683  			jsonSpec += `,"subvolumeFormat": "RAW"`
   684  		}
   685  	} else {
   686  		jsonSpec = `{"imageSpec":` + jsonSpec
   687  		url += "/imagetile:binary"
   688  	}
   689  
   690  	if formatStr != "" {
   691  		format := strings.Split(formatStr, ":")
   692  		var gformat string
   693  		switch format[0] {
   694  		case "jpg", "jpeg", "JPG":
   695  			gformat = "JPEG"
   696  		case "png":
   697  			gformat = "PNG"
   698  		default:
   699  			err = fmt.Errorf("googlevoxels tiles only support JPEG or PNG formats, not %q", format[0])
   700  			return
   701  		}
   702  		jsonSpec += fmt.Sprintf(`,"imageOptions":{"imageFormat":%q`, gformat)
   703  		if len(format) > 1 {
   704  			var level int
   705  			level, err = strconv.Atoi(format[1])
   706  			if err != nil {
   707  				return
   708  			}
   709  			switch format[0] {
   710  			case "jpeg":
   711  				jsonSpec += fmt.Sprintf(`,"jpegQuality":%d`, level)
   712  			case "png":
   713  				jsonSpec += fmt.Sprintf(`,"pngCompressionLevel":%d`, level)
   714  			}
   715  		}
   716  		jsonSpec += "}"
   717  	}
   718  	if gsg.shape == XYZ {
   719  		jsonSpec += "}"
   720  	} else {
   721  		jsonSpec += "}}"
   722  	}
   723  	dvid.Infof("Sending image options:\n%s\n", jsonSpec)
   724  	opts = bytes.NewBufferString(jsonSpec)
   725  	// url += "?alt=media"
   726  
   727  	return
   728  }
   729  
   730  // padData takes returned data and pads it to full expected size.
   731  // currently assumes that data padding needed on far edges, not near edges.
   732  func (gsg GoogleSubvolGeom) padData(data []byte) ([]byte, error) {
   733  	if gsg.size[0]*gsg.size[1]*gsg.size[2]*gsg.bytesPerVoxel != int32(len(data)) {
   734  		return nil, fmt.Errorf("Before padding, for %d x %d x %d bytes/voxel tile, received %d bytes",
   735  			gsg.size[0], gsg.size[1], gsg.bytesPerVoxel, len(data))
   736  	}
   737  
   738  	inRowBytes := gsg.size[0] * gsg.bytesPerVoxel
   739  	outRowBytes := gsg.sizeWant[0] * gsg.bytesPerVoxel
   740  	outBytes := outRowBytes * gsg.sizeWant[1]
   741  	out := make([]byte, outBytes, outBytes)
   742  	inI := int32(0)
   743  	outI := int32(0)
   744  	for y := int32(0); y < gsg.size[1]; y++ {
   745  		copy(out[outI:outI+inRowBytes], data[inI:inI+inRowBytes])
   746  		inI += inRowBytes
   747  		outI += outRowBytes
   748  	}
   749  	return out, nil
   750  }
   751  
   752  // Properties are additional properties for keyvalue data instances beyond those
   753  // in standard datastore.Data.   These will be persisted to metadata storage.
   754  type Properties struct {
   755  	// Necessary information to select data from Google BrainMaps API.
   756  	VolumeID string
   757  	JWT      string
   758  
   759  	// Default size in pixels along one dimension of square tile.
   760  	TileSize int32
   761  
   762  	// GeomMap provides mapping between scale and various image shapes to Google scaling index.
   763  	GeomMap GeometryMap
   764  
   765  	// Scales is the list of available precomputed scales ("geometries" in Google terms) for this data.
   766  	Scales Geometries
   767  
   768  	// HighResIndex is the geometry that is the highest resolution among the available scaled volumes.
   769  	HighResIndex GeometryIndex
   770  
   771  	// OAuth2 configuration
   772  	oa2conf *oauth2.Config
   773  }
   774  
   775  // CopyPropertiesFrom copies the data instance-specific properties from a given
   776  // data instance into the receiver's properties.  Fulfills the datastore.PropertyCopier interface.
   777  func (d *Data) CopyPropertiesFrom(src datastore.DataService, fs storage.FilterSpec) error {
   778  	d2, ok := src.(*Data)
   779  	if !ok {
   780  		return fmt.Errorf("unable to copy properties from non-imageblk data %q", src.DataName())
   781  	}
   782  	// These should all be immutable so can have shared reference with source.
   783  	d.VolumeID = d2.VolumeID
   784  	d.JWT = d2.JWT
   785  	d.TileSize = d2.TileSize
   786  	d.GeomMap = d2.GeomMap
   787  	d.Scales = d2.Scales
   788  	d.HighResIndex = d2.HighResIndex
   789  	d.oa2conf = d2.oa2conf
   790  
   791  	return nil
   792  }
   793  
   794  // MarshalJSON handles JSON serialization for googlevoxels Data.  It adds "Levels" metadata equivalent
   795  // to imagetile's tile specification so clients can treat googlevoxels tile API identically to
   796  // imagetile.  Sensitive information like AuthKey are withheld.
   797  func (p Properties) MarshalJSON() ([]byte, error) {
   798  	var minTileCoord, maxTileCoord dvid.Point3d
   799  	if len(p.Scales) > 0 {
   800  		vol := p.Scales[0].VolumeSize
   801  		maxX := vol[0] / p.TileSize
   802  		if vol[0]%p.TileSize > 0 {
   803  			maxX++
   804  		}
   805  		maxY := vol[1] / p.TileSize
   806  		if vol[1]%p.TileSize > 0 {
   807  			maxY++
   808  		}
   809  		maxZ := vol[2] / p.TileSize
   810  		if vol[2]%p.TileSize > 0 {
   811  			maxZ++
   812  		}
   813  		maxTileCoord = dvid.Point3d{maxX, maxY, maxZ}
   814  	}
   815  	return json.Marshal(struct {
   816  		VolumeID     string
   817  		MinTileCoord dvid.Point3d
   818  		MaxTileCoord dvid.Point3d
   819  		TileSize     int32
   820  		GeomMap      GeometryMap
   821  		Scales       Geometries
   822  		HighResIndex GeometryIndex
   823  		Levels       imagetile.TileSpec
   824  	}{
   825  		p.VolumeID,
   826  		minTileCoord,
   827  		maxTileCoord,
   828  		p.TileSize,
   829  		p.GeomMap,
   830  		p.Scales,
   831  		p.HighResIndex,
   832  		getGSpec(p.TileSize, p.Scales[p.HighResIndex], p.GeomMap),
   833  	})
   834  }
   835  
   836  // Converts Google BrainMaps scaling to imagetile-style tile specifications.
   837  // This assumes that Google levels always downsample by 2.
   838  func getGSpec(tileSize int32, hires Geometry, geomMap GeometryMap) imagetile.TileSpec {
   839  	// Determine how many levels we have by the max of any orientation.
   840  	// TODO -- Warn user in some way if BrainMaps API has levels in one orientation but not in other.
   841  	var maxScale Scaling
   842  	for tileSpec := range geomMap {
   843  		if tileSpec.scaling > maxScale {
   844  			maxScale = tileSpec.scaling
   845  		}
   846  	}
   847  
   848  	// Create the levels from 0 (hires) to max level.
   849  	levelSpec := imagetile.LevelSpec{
   850  		TileSize: dvid.Point3d{tileSize, tileSize, tileSize},
   851  	}
   852  	levelSpec.Resolution = make(dvid.NdFloat32, 3)
   853  	copy(levelSpec.Resolution, hires.PixelSize)
   854  	ms2dGSpec := make(imagetile.TileSpec, maxScale+1)
   855  	for scale := Scaling(0); scale <= maxScale; scale++ {
   856  		curSpec := levelSpec.Duplicate()
   857  		ms2dGSpec[imagetile.Scaling(scale)] = imagetile.TileScaleSpec{LevelSpec: curSpec}
   858  		levelSpec.Resolution[0] *= 2
   859  		levelSpec.Resolution[1] *= 2
   860  		levelSpec.Resolution[2] *= 2
   861  	}
   862  	return ms2dGSpec
   863  }
   864  
   865  // Data embeds the datastore's Data and extends it with voxel-specific properties.
   866  type Data struct {
   867  	*datastore.Data
   868  	Properties
   869  
   870  	client *http.Client // HTTP client that provides Authorization headers
   871  }
   872  
   873  // GetClient returns a potentially cached client that handles authorization to Google.
   874  // Assumes a JSON Web Token has been loaded into Data or else returns an error.
   875  func (d *Data) GetClient() (*http.Client, error) {
   876  	if d.client != nil {
   877  		return d.client, nil
   878  	}
   879  	if d.Properties.JWT == "" {
   880  		return nil, fmt.Errorf("No JSON Web Token has been set for this data")
   881  	}
   882  	conf, err := google.JWTConfigFromJSON([]byte(d.Properties.JWT), "https://www.googleapis.com/auth/brainmaps")
   883  	if err != nil {
   884  		return nil, fmt.Errorf("Cannot establish JWT Config file from Google: %v", err)
   885  	}
   886  	client := conf.Client(oauth2.NoContext)
   887  	d.client = client
   888  	return client, nil
   889  }
   890  
   891  func (d *Data) GetVoxelSize(ts *GSpec) (dvid.NdFloat32, error) {
   892  	if d.Scales == nil || len(d.Scales) == 0 {
   893  		return nil, fmt.Errorf("%s has no geometries and therefore no volumes for access", d.DataName())
   894  	}
   895  	if d.GeomMap == nil {
   896  		return nil, fmt.Errorf("%s has not been initialized and can't return voxel sizes", d.DataName())
   897  	}
   898  	if ts == nil {
   899  		return nil, fmt.Errorf("Can't get voxel sizes for nil tile spec!")
   900  	}
   901  	scaleIndex := d.GeomMap[*ts]
   902  	if int(scaleIndex) > len(d.Scales) {
   903  		return nil, fmt.Errorf("Can't map tile spec (%v) to available geometries", *ts)
   904  	}
   905  	geom := d.Scales[scaleIndex]
   906  	return geom.PixelSize, nil
   907  }
   908  
   909  func (d *Data) MarshalJSON() ([]byte, error) {
   910  	return json.Marshal(struct {
   911  		Base     *datastore.Data
   912  		Extended Properties
   913  	}{
   914  		d.Data,
   915  		d.Properties,
   916  	})
   917  }
   918  
   919  func (d *Data) GobDecode(b []byte) error {
   920  	buf := bytes.NewBuffer(b)
   921  	dec := gob.NewDecoder(buf)
   922  	if err := dec.Decode(&(d.Data)); err != nil {
   923  		return err
   924  	}
   925  	if err := dec.Decode(&(d.Properties)); err != nil {
   926  		return err
   927  	}
   928  	return nil
   929  }
   930  
   931  func (d *Data) GobEncode() ([]byte, error) {
   932  	var buf bytes.Buffer
   933  	enc := gob.NewEncoder(&buf)
   934  	if err := enc.Encode(d.Data); err != nil {
   935  		return nil, err
   936  	}
   937  	if err := enc.Encode(d.Properties); err != nil {
   938  		return nil, err
   939  	}
   940  	return buf.Bytes(), nil
   941  }
   942  
   943  // --- DataService interface ---
   944  
   945  func (d *Data) Help() string {
   946  	return helpMessage
   947  }
   948  
   949  // getBlankTileData returns a background 2d tile data
   950  func (d *Data) getBlankTileImage(tile *GoogleSubvolGeom) (image.Image, error) {
   951  	if tile == nil {
   952  		return nil, fmt.Errorf("Can't get blank tile for unknown tile spec")
   953  	}
   954  	if d.Scales == nil || len(d.Scales) <= int(tile.gi) {
   955  		return nil, fmt.Errorf("Scaled volumes for %s not suitable for tile spec: %d scales <= %d tile scales", d.DataName(), len(d.Scales), int(tile.gi))
   956  	}
   957  
   958  	// Generate the blank image
   959  	numBytes := tile.sizeWant[0] * tile.sizeWant[1] * tile.bytesPerVoxel
   960  	data := make([]byte, numBytes, numBytes)
   961  	return dvid.GoImageFromData(data, int(tile.sizeWant[0]), int(tile.sizeWant[1]))
   962  }
   963  
   964  func (d *Data) serveTile(w http.ResponseWriter, r *http.Request, geom *GoogleSubvolGeom, formatStr string, noblanks bool) error {
   965  	// If it's outside, write blank tile unless user wants no blanks.
   966  	if geom.outside {
   967  		if noblanks {
   968  			http.NotFound(w, r)
   969  			return fmt.Errorf("Requested tile is outside of available volume.")
   970  		}
   971  		img, err := d.getBlankTileImage(geom)
   972  		if err != nil {
   973  			return err
   974  		}
   975  		return dvid.WriteImageHttp(w, img, formatStr)
   976  	}
   977  
   978  	// If we are within volume, get data from Google.
   979  	url, imgOptions, err := geom.GetURL(d.VolumeID, formatStr)
   980  	if err != nil {
   981  		return err
   982  	}
   983  
   984  	timedLog := dvid.NewTimeLog()
   985  	client, err := d.GetClient()
   986  	if err != nil {
   987  		dvid.Errorf("Can't get OAuth2 connection to Google: %v\n", err)
   988  		return err
   989  	}
   990  	resp, err := client.Post(url, "application/json", imgOptions)
   991  	if err != nil {
   992  		return err
   993  	}
   994  	timedLog.Infof("PROXY HTTP to Google: %s, returned response %d", url, resp.StatusCode)
   995  	defer resp.Body.Close()
   996  
   997  	// Set the image header
   998  	if err := dvid.SetImageHeader(w, formatStr); err != nil {
   999  		return err
  1000  	}
  1001  
  1002  	// If it's on edge, we need to pad the tile to the tile size.
  1003  	if geom.edge {
  1004  		// We need to read whole thing in to pad it.
  1005  		data, err := ioutil.ReadAll(resp.Body)
  1006  		timedLog.Infof("Got edge tile from Google, %d bytes\n", len(data))
  1007  		if err != nil {
  1008  			return err
  1009  		}
  1010  		paddedData, err := geom.padData(data)
  1011  		if err != nil {
  1012  			return err
  1013  		}
  1014  		_, err = w.Write(paddedData)
  1015  		return err
  1016  	}
  1017  
  1018  	// If we aren't on edge or outside, our return status should be OK.
  1019  	if resp.StatusCode != http.StatusOK {
  1020  		return fmt.Errorf("Unexpected status code %d on tile request (%q, volume id %q)", resp.StatusCode, d.DataName(), d.VolumeID)
  1021  	}
  1022  
  1023  	// Just send the data as we get it from Google in chunks.
  1024  	respBytes := 0
  1025  	const BufferSize = 32 * 1024
  1026  	buf := make([]byte, BufferSize)
  1027  	for {
  1028  		n, err := resp.Body.Read(buf)
  1029  		respBytes += n
  1030  		eof := (err == io.EOF)
  1031  		if err != nil && !eof {
  1032  			return err
  1033  		}
  1034  		if _, err = w.Write(buf[:n]); err != nil {
  1035  			return err
  1036  		}
  1037  		if f, ok := w.(http.Flusher); ok {
  1038  			f.Flush()
  1039  		}
  1040  		if eof {
  1041  			break
  1042  		}
  1043  	}
  1044  	timedLog.Infof("Got non-edge tile from Google, %d bytes\n", respBytes)
  1045  	return nil
  1046  }
  1047  
  1048  func (d *Data) serveVolume(w http.ResponseWriter, r *http.Request, geom *GoogleSubvolGeom, noblanks bool, formatstr string) error {
  1049  	// If it's outside, write blank tile unless user wants no blanks.
  1050  	if geom.outside {
  1051  		if noblanks {
  1052  			http.NotFound(w, r)
  1053  			return fmt.Errorf("Requested subvolume is outside of available volume.")
  1054  		}
  1055  		return nil
  1056  	}
  1057  
  1058  	// If we are within volume, get data from Google.
  1059  	url, imgOptions, err := geom.GetURL(d.VolumeID, formatstr)
  1060  	if err != nil {
  1061  		return err
  1062  	}
  1063  
  1064  	timedLog := dvid.NewTimeLog()
  1065  	client, err := d.GetClient()
  1066  	if err != nil {
  1067  		dvid.Errorf("Can't get OAuth2 connection to Google: %v\n", err)
  1068  		return err
  1069  	}
  1070  	resp, err := client.Post(url, "application/json", imgOptions)
  1071  	if err != nil {
  1072  		return err
  1073  	}
  1074  	timedLog.Infof("PROXY HTTP to Google: %s, returned response %d", url, resp.StatusCode)
  1075  	defer resp.Body.Close()
  1076  
  1077  	// If it's on edge, we need to pad the subvolume to the requested size.
  1078  	if geom.edge {
  1079  		return fmt.Errorf("Googlevoxels subvolume GET does not pad data on edge at this time")
  1080  	}
  1081  
  1082  	// If we aren't on edge or outside, our return status should be OK.
  1083  	if resp.StatusCode != http.StatusOK {
  1084  		return fmt.Errorf("Unexpected status code %d on volume request (%q, volume id %q)", resp.StatusCode, d.DataName(), d.VolumeID)
  1085  	}
  1086  
  1087  	w.Header().Set("Content-type", "application/octet-stream")
  1088  
  1089  	queryStrings := r.URL.Query()
  1090  	compression := queryStrings.Get("compression")
  1091  
  1092  	switch compression {
  1093  	case "lz4":
  1094  		data, err := ioutil.ReadAll(resp.Body)
  1095  		timedLog.Infof("Got raw subvolume from Google, %d bytes\n", len(data))
  1096  		if err != nil {
  1097  			return err
  1098  		}
  1099  
  1100  		// Recompress and transmit as lz4
  1101  		lz4data := make([]byte, lz4.CompressBound(data))
  1102  		outSize, err := lz4.Compress(data, lz4data)
  1103  		if err != nil {
  1104  			return err
  1105  		}
  1106  		if _, err := w.Write(lz4data[:outSize]); err != nil {
  1107  			return err
  1108  		}
  1109  		timedLog.Infof("Sent lz4-encoded subvolume from DVID, %d bytes\n", outSize)
  1110  
  1111  	case "", "raw":
  1112  		// Just stream raw data from Google
  1113  		respBytes := 0
  1114  		const BufferSize = 32 * 1024
  1115  		buf := make([]byte, BufferSize)
  1116  		for {
  1117  			n, err := resp.Body.Read(buf)
  1118  			respBytes += n
  1119  			eof := (err == io.EOF)
  1120  			if err != nil && !eof {
  1121  				return err
  1122  			}
  1123  			if _, err = w.Write(buf[:n]); err != nil {
  1124  				return err
  1125  			}
  1126  			if f, ok := w.(http.Flusher); ok {
  1127  				f.Flush()
  1128  			}
  1129  			if eof {
  1130  				break
  1131  			}
  1132  		}
  1133  		timedLog.Infof("Proxied encoded subvolume from Google, %d bytes\n", respBytes)
  1134  	default:
  1135  		return fmt.Errorf("unknown compression requested %q", compression)
  1136  	}
  1137  
  1138  	return nil
  1139  }
  1140  
  1141  // See if scaling was specified in query string, otherwise return high-res (scale 0)
  1142  func getScale(r *http.Request) (Scaling, error) {
  1143  	var scale Scaling
  1144  	queryStrings := r.URL.Query()
  1145  	scalingStr := queryStrings.Get("scale")
  1146  	if scalingStr != "" {
  1147  		scale64, err := strconv.ParseUint(scalingStr, 10, 8)
  1148  		if err != nil {
  1149  			return 0, fmt.Errorf("Illegal tile scale: %s (%v)", scalingStr, err)
  1150  		}
  1151  		scale = Scaling(scale64)
  1152  	}
  1153  	return scale, nil
  1154  }
  1155  
  1156  func (d *Data) handleImage2d(w http.ResponseWriter, r *http.Request, parts []string) error {
  1157  	return nil
  1158  }
  1159  
  1160  // handleImageReq returns an image with appropriate Content-Type set.  This function differs
  1161  // from handleTileReq in the way parameters are passed to it.  handleTileReq accepts a tile coordinate.
  1162  // This function allows arbitrary offset and size, unconstrained by tile sizes.
  1163  func (d *Data) handleImageReq(w http.ResponseWriter, r *http.Request, parts []string) error {
  1164  	if len(parts) < 7 {
  1165  		return fmt.Errorf("%q must be followed by shape/size/offset", parts[3])
  1166  	}
  1167  	shapeStr, sizeStr, offsetStr := parts[4], parts[5], parts[6]
  1168  	planeStr := dvid.DataShapeString(shapeStr)
  1169  	plane, err := planeStr.DataShape()
  1170  	if err != nil {
  1171  		return err
  1172  	}
  1173  
  1174  	var size dvid.Point
  1175  	if size, err = dvid.StringToPoint(sizeStr, "_"); err != nil {
  1176  		return err
  1177  	}
  1178  	offset, err := dvid.StringToPoint3d(offsetStr, "_")
  1179  	if err != nil {
  1180  		return err
  1181  	}
  1182  
  1183  	// Determine how this request sits in the available scaled volumes.
  1184  	scale, err := getScale(r)
  1185  	if err != nil {
  1186  		return err
  1187  	}
  1188  	geom, err := d.GetGoogleSubvolGeom(scale, plane, offset, size)
  1189  	if err != nil {
  1190  		return err
  1191  	}
  1192  
  1193  	switch plane.ShapeDimensions() {
  1194  	case 2:
  1195  		var formatStr string
  1196  		if len(parts) >= 8 {
  1197  			formatStr = parts[7]
  1198  		}
  1199  		if formatStr == "" {
  1200  			formatStr = DefaultTileFormat
  1201  		}
  1202  
  1203  		return d.serveTile(w, r, geom, formatStr, false)
  1204  	case 3:
  1205  		if len(parts) >= 8 {
  1206  			return d.serveVolume(w, r, geom, false, parts[7])
  1207  		} else {
  1208  			return d.serveVolume(w, r, geom, false, "")
  1209  		}
  1210  	}
  1211  	return nil
  1212  }
  1213  
  1214  // handleTileReq returns a tile with appropriate Content-Type set.
  1215  func (d *Data) handleTileReq(w http.ResponseWriter, r *http.Request, parts []string) error {
  1216  
  1217  	if len(parts) < 7 {
  1218  		return fmt.Errorf("'tile' request must be following by plane, scale level, and tile coordinate")
  1219  	}
  1220  	planeStr, scalingStr, coordStr := parts[4], parts[5], parts[6]
  1221  	queryStrings := r.URL.Query()
  1222  
  1223  	var noblanks bool
  1224  	noblanksStr := dvid.InstanceName(queryStrings.Get("noblanks"))
  1225  	if noblanksStr == "true" {
  1226  		noblanks = true
  1227  	}
  1228  
  1229  	var tilesize int32 = DefaultTileSize
  1230  	tileSizeStr := queryStrings.Get("tilesize")
  1231  	if tileSizeStr != "" {
  1232  		tilesizeInt, err := strconv.Atoi(tileSizeStr)
  1233  		if err != nil {
  1234  			return err
  1235  		}
  1236  		tilesize = int32(tilesizeInt)
  1237  	}
  1238  	size := dvid.Point2d{tilesize, tilesize}
  1239  
  1240  	var formatStr string
  1241  	if len(parts) >= 8 {
  1242  		formatStr = parts[7]
  1243  	}
  1244  	if formatStr == "" {
  1245  		formatStr = DefaultTileFormat
  1246  	}
  1247  
  1248  	// Parse the tile specification
  1249  	plane := dvid.DataShapeString(planeStr)
  1250  	shape, err := plane.DataShape()
  1251  	if err != nil {
  1252  		err = fmt.Errorf("Illegal tile plane: %s (%v)", planeStr, err)
  1253  		server.BadRequest(w, r, err)
  1254  		return err
  1255  	}
  1256  	scale, err := strconv.ParseUint(scalingStr, 10, 8)
  1257  	if err != nil {
  1258  		err = fmt.Errorf("Illegal tile scale: %s (%v)", scalingStr, err)
  1259  		server.BadRequest(w, r, err)
  1260  		return err
  1261  	}
  1262  	tileCoord, err := dvid.StringToPoint(coordStr, "_")
  1263  	if err != nil {
  1264  		err = fmt.Errorf("Illegal tile coordinate: %s (%v)", coordStr, err)
  1265  		server.BadRequest(w, r, err)
  1266  		return err
  1267  	}
  1268  
  1269  	// Convert tile coordinate to offset.
  1270  	var ox, oy, oz int32
  1271  	switch {
  1272  	case shape.Equals(dvid.XY):
  1273  		ox = tileCoord.Value(0) * tilesize
  1274  		oy = tileCoord.Value(1) * tilesize
  1275  		oz = tileCoord.Value(2)
  1276  	case shape.Equals(dvid.XZ):
  1277  		ox = tileCoord.Value(0) * tilesize
  1278  		oy = tileCoord.Value(1)
  1279  		oz = tileCoord.Value(2) * tilesize
  1280  	case shape.Equals(dvid.YZ):
  1281  		ox = tileCoord.Value(0)
  1282  		oy = tileCoord.Value(1) * tilesize
  1283  		oz = tileCoord.Value(2) * tilesize
  1284  	default:
  1285  		return fmt.Errorf("Unknown tile orientation: %s", shape)
  1286  	}
  1287  
  1288  	// Determine how this request sits in the available scaled volumes.
  1289  	geom, err := d.GetGoogleSubvolGeom(Scaling(scale), shape, dvid.Point3d{ox, oy, oz}, size)
  1290  	if err != nil {
  1291  		server.BadRequest(w, r, err)
  1292  		return err
  1293  	}
  1294  
  1295  	// Send the tile.
  1296  	return d.serveTile(w, r, geom, formatStr, noblanks)
  1297  }
  1298  
  1299  // DoRPC handles the 'generate' command.
  1300  func (d *Data) DoRPC(request datastore.Request, reply *datastore.Response) error {
  1301  	return fmt.Errorf("Unknown command.  Data instance %q does not support any commands.  See API help.", d.DataName())
  1302  }
  1303  
  1304  // ServeHTTP handles all incoming HTTP requests for this data.
  1305  func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) (activity map[string]interface{}) {
  1306  	timedLog := dvid.NewTimeLog()
  1307  
  1308  	action := strings.ToLower(r.Method)
  1309  	switch action {
  1310  	case "get":
  1311  		// Acceptable
  1312  	default:
  1313  		server.BadRequest(w, r, "googlevoxels can only handle GET HTTP verbs at this time")
  1314  		return
  1315  	}
  1316  
  1317  	// Break URL request into arguments
  1318  	url := r.URL.Path[len(server.WebAPIPath):]
  1319  	parts := strings.Split(url, "/")
  1320  	if len(parts[len(parts)-1]) == 0 {
  1321  		parts = parts[:len(parts)-1]
  1322  	}
  1323  	if len(parts) < 4 {
  1324  		server.BadRequest(w, r, "incomplete API request")
  1325  		return
  1326  	}
  1327  
  1328  	switch parts[3] {
  1329  	case "help":
  1330  		w.Header().Set("Content-Type", "text/plain")
  1331  		fmt.Fprintln(w, d.Help())
  1332  
  1333  	case "info":
  1334  		jsonBytes, err := d.MarshalJSON()
  1335  		if err != nil {
  1336  			server.BadRequest(w, r, err)
  1337  			return
  1338  		}
  1339  		w.Header().Set("Content-Type", "application/json")
  1340  		fmt.Fprintf(w, string(jsonBytes))
  1341  
  1342  	case "tile":
  1343  		if err := d.handleTileReq(w, r, parts); err != nil {
  1344  			server.BadRequest(w, r, err)
  1345  			return
  1346  		}
  1347  		timedLog.Infof("HTTP %s: tile (%s)", r.Method, r.URL)
  1348  
  1349  	case "raw":
  1350  		queryStrings := r.URL.Query()
  1351  		if throttle := queryStrings.Get("throttle"); throttle == "on" || throttle == "true" {
  1352  			if server.ThrottledHTTP(w) {
  1353  				return
  1354  			}
  1355  			defer server.ThrottledOpDone()
  1356  		}
  1357  		if err := d.handleImageReq(w, r, parts); err != nil {
  1358  			server.BadRequest(w, r, err)
  1359  			return
  1360  		}
  1361  		timedLog.Infof("HTTP %s: image (%s)", r.Method, r.URL)
  1362  	default:
  1363  		server.BadAPIRequest(w, r, d)
  1364  	}
  1365  	return
  1366  }