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

     1  /*
     2  	Package multichan16 tailors the voxels data type for 16-bit fluorescent images with multiple
     3  	channels that can be read from V3D Raw format.  Note that this data type has multiple
     4  	channels but segregates its channel data in (c, z, y, x) fashion rather than interleave
     5  	it within a block of data in (z, y, x, c) fashion.  There is not much advantage at
     6  	using interleaving; most forms of RGB compression fails to preserve the
     7  	independence of the channels.  Segregating the channel data lets us use straightforward
     8  	compression on channel slices.
     9  
    10  	Specific channels of multichan16 data are addressed by adding a numerical suffix to the
    11  	data name.  For example, if we have "mydata" multichan16 data, we reference channel 1
    12  	as "mydata1" and channel 2 as "mydata2".  Up to the first 3 channels are composited
    13  	into a RGBA volume that is addressible using "mydata" or "mydata0".
    14  
    15  	NOTE: This data type has not been actively maintained and was writtern earlier to
    16  */
    17  package multichan16
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/binary"
    22  	"encoding/gob"
    23  	"encoding/json"
    24  	"fmt"
    25  	"net/http"
    26  	"os"
    27  	"path/filepath"
    28  	"strconv"
    29  	"strings"
    30  
    31  	"github.com/janelia-flyem/dvid/datastore"
    32  	"github.com/janelia-flyem/dvid/datatype/imageblk"
    33  	"github.com/janelia-flyem/dvid/dvid"
    34  	"github.com/janelia-flyem/dvid/server"
    35  	"github.com/janelia-flyem/dvid/storage"
    36  )
    37  
    38  const (
    39  	Version  = "0.1"
    40  	RepoURL  = "github.com/janelia-flyem/dvid/datatype/multichan16"
    41  	TypeName = "multichan16"
    42  )
    43  
    44  const helpMessage = `
    45  API for datatypes derived from multichan16 (github.com/janelia-flyem/dvid/datatype/multichan16)
    46  ===============================================================================================
    47  
    48  Command-line:
    49  
    50  $ dvid node <UUID> <data name> load <V3D raw filename>
    51  
    52      Adds multichannel data to a version node when the server can see the local files ("local")
    53      or when the server must be sent the files via rpc ("remote").
    54  
    55      Example: 
    56  
    57      $ dvid node 3f8c mydata load local mydata.v3draw
    58  
    59      Arguments:
    60  
    61      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
    62      data name     Name of data to add.
    63      filename      Filename of a V3D Raw format file.
    64  	
    65      ------------------
    66  
    67  HTTP API (Level 2 REST):
    68  
    69  GET  <api URL>/node/<UUID>/<data name>/help
    70  
    71  	Returns data-specific help message.
    72  
    73  
    74  GET  <api URL>/node/<UUID>/<data name>/info
    75  POST <api URL>/node/<UUID>/<data name>/info
    76  
    77      Retrieves or puts data properties.
    78  
    79      Example: 
    80  
    81      GET <api URL>/node/3f8c/multichan16/info
    82  
    83      Returns JSON with configuration settings.
    84  
    85      Arguments:
    86  
    87      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
    88      data name     Name of multichan16 data.
    89  
    90  
    91  GET  <api URL>/node/<UUID>/<data name>/<dims>/<size>/<offset>[/<format>]
    92  POST <api URL>/node/<UUID>/<data name>/<dims>/<size>/<offset>[/<format>]
    93  
    94      Retrieves or puts orthogonal plane image data to named multichannel 16-bit data.
    95  
    96      Example: 
    97  
    98      GET <api URL>/node/3f8c/mydata2/xy/200,200/0,0,100/jpg:80  (channel 2 of mydata)
    99  
   100      Arguments:
   101  
   102      UUID          Hexadecimal string with enough characters to uniquely identify a version node.
   103      data name     Name of data.  Optionally add a numerical suffix for the channel number.
   104      dims          The axes of data extraction in form "i_j_k,..."  Example: "0_2" can be XZ.
   105                      Slice strings ("xy", "xz", or "yz") are also accepted.
   106      size          Size in pixels in the format "dx_dy".
   107      offset        3d coordinate in the format "x_y_z".  Gives coordinate of top upper left voxel.
   108      format        Valid formats depend on the dimensionality of the request and formats
   109                      available in server implementation.
   110                    2D: "png", "jpg" (default: "png")
   111                      jpg allows lossy quality setting, e.g., "jpg:80"
   112  
   113  `
   114  
   115  // DefaultBlockMax specifies the default size for each block of this data type.
   116  var (
   117  	DefaultBlockSize int32 = 32
   118  
   119  	typeService datastore.TypeService
   120  
   121  	compositeValues = dvid.DataValues{
   122  		{
   123  			T:     dvid.T_uint8,
   124  			Label: "red",
   125  		},
   126  		{
   127  			T:     dvid.T_uint8,
   128  			Label: "green",
   129  		},
   130  		{
   131  			T:     dvid.T_uint8,
   132  			Label: "blue",
   133  		},
   134  		{
   135  			T:     dvid.T_uint8,
   136  			Label: "alpha",
   137  		},
   138  	}
   139  )
   140  
   141  func init() {
   142  	interpolable := true
   143  	dtype := NewType(compositeValues, interpolable)
   144  
   145  	// See doc for package on why channels are segregated instead of interleaved.
   146  	// Data types must be registered with the datastore to be used.
   147  	typeService = &dtype
   148  	datastore.Register(&dtype)
   149  
   150  	// Need to register types that will be used to fulfill interfaces.
   151  	gob.Register(&Type{})
   152  	gob.Register(&Data{})
   153  }
   154  
   155  func CompositeEncodeFormat() dvid.DataValues {
   156  	return compositeValues
   157  }
   158  
   159  // Type just uses voxels data type by composition.
   160  type Type struct {
   161  	imageblk.Type
   162  }
   163  
   164  // NewType returns a pointer to a new voxels Type with default values set.
   165  func NewType(values dvid.DataValues, interpolable bool) Type {
   166  	basetype := imageblk.NewType(compositeValues, interpolable)
   167  	basetype.Name = TypeName
   168  	basetype.URL = RepoURL
   169  	basetype.Version = Version
   170  	return Type{basetype}
   171  }
   172  
   173  // --- TypeService interface ---
   174  
   175  // NewDataService returns a pointer to a new multichan16 with default values.
   176  func (dtype *Type) NewDataService(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (datastore.DataService, error) {
   177  	voxelData, err := dtype.Type.NewDataService(uuid, id, name, c)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	basedata := voxelData.(*imageblk.Data)
   182  	basedata.Properties.Values = nil
   183  	return &Data{Data: basedata}, nil
   184  }
   185  
   186  func (dtype *Type) Help() string {
   187  	return helpMessage
   188  }
   189  
   190  // -------  ExtData interface implementation -------------
   191  
   192  // Channel is an image volume that fulfills the imageblk.ExtData interface.
   193  type Channel struct {
   194  	*imageblk.Voxels
   195  
   196  	// Channel 0 is the composite RGBA channel and all others are 16-bit.
   197  	channelNum int32
   198  }
   199  
   200  func (c *Channel) String() string {
   201  	return fmt.Sprintf("Channel %d of size %s @ offset %s", c.channelNum, c.Size(), c.StartPoint())
   202  }
   203  
   204  func (c *Channel) Interpolable() bool {
   205  	return true
   206  }
   207  
   208  func (c *Channel) NewChunkIndex() dvid.ChunkIndexer {
   209  	return &dvid.IndexCZYX{c.channelNum, dvid.IndexZYX{}}
   210  }
   211  
   212  // Index returns a channel-specific Index
   213  func (c *Channel) Index(p dvid.ChunkPoint) dvid.Index {
   214  	return &dvid.IndexCZYX{c.channelNum, dvid.IndexZYX(p.(dvid.ChunkPoint3d))}
   215  }
   216  
   217  // NewIndexIterator returns an iterator that can move across the voxel geometry,
   218  // generating indices or index spans.
   219  func (c *Channel) NewIndexIterator(chunkSize dvid.Point) (dvid.IndexIterator, error) {
   220  	// Setup traversal
   221  	begVoxel, ok := c.StartPoint().(dvid.Chunkable)
   222  	if !ok {
   223  		return nil, fmt.Errorf("ExtData StartPoint() cannot handle Chunkable points.")
   224  	}
   225  	endVoxel, ok := c.EndPoint().(dvid.Chunkable)
   226  	if !ok {
   227  		return nil, fmt.Errorf("ExtData EndPoint() cannot handle Chunkable points.")
   228  	}
   229  
   230  	blockSize := chunkSize.(dvid.Point3d)
   231  	begBlock := begVoxel.Chunk(blockSize).(dvid.ChunkPoint3d)
   232  	endBlock := endVoxel.Chunk(blockSize).(dvid.ChunkPoint3d)
   233  
   234  	return dvid.NewIndexCZYXIterator(c.channelNum, begBlock, endBlock), nil
   235  }
   236  
   237  // Data of multichan16 type embeds voxels and extends it with channels.
   238  type Data struct {
   239  	*imageblk.Data
   240  
   241  	// Number of channels for this data.  The names are referenced by
   242  	// adding a number onto the data name, e.g., mydata1, mydata2, etc.
   243  	NumChannels int
   244  }
   245  
   246  func (d *Data) Equals(d2 *Data) bool {
   247  	if !d.Data.Equals(d2.Data) ||
   248  		d.NumChannels != d2.NumChannels {
   249  		return false
   250  	}
   251  	return true
   252  }
   253  
   254  type propertiesT struct {
   255  	imageblk.Properties
   256  	NumChannels int
   257  }
   258  
   259  // CopyPropertiesFrom copies the data instance-specific properties from a given
   260  // data instance into the receiver's properties.   Fulfills the datastore.PropertyCopier interface.
   261  func (d *Data) CopyPropertiesFrom(src datastore.DataService, fs storage.FilterSpec) error {
   262  	d2, ok := src.(*Data)
   263  	if !ok {
   264  		return fmt.Errorf("unable to copy properties from non-multichan16 data %q", src.DataName())
   265  	}
   266  	d.NumChannels = d2.NumChannels
   267  
   268  	return nil
   269  }
   270  
   271  func (d *Data) MarshalJSON() ([]byte, error) {
   272  	return json.Marshal(struct {
   273  		Base     *datastore.Data
   274  		Extended propertiesT
   275  	}{
   276  		d.Data.Data,
   277  		propertiesT{
   278  			d.Data.Properties,
   279  			d.NumChannels,
   280  		},
   281  	})
   282  }
   283  
   284  func (d *Data) GobDecode(b []byte) error {
   285  	buf := bytes.NewBuffer(b)
   286  	dec := gob.NewDecoder(buf)
   287  	if err := dec.Decode(&(d.Data)); err != nil {
   288  		return err
   289  	}
   290  	if err := dec.Decode(&(d.NumChannels)); err != nil {
   291  		return err
   292  	}
   293  	return nil
   294  }
   295  
   296  func (d *Data) GobEncode() ([]byte, error) {
   297  	var buf bytes.Buffer
   298  	enc := gob.NewEncoder(&buf)
   299  	if err := enc.Encode(d.Data); err != nil {
   300  		return nil, err
   301  	}
   302  	if err := enc.Encode(d.NumChannels); err != nil {
   303  		return nil, err
   304  	}
   305  	return buf.Bytes(), nil
   306  }
   307  
   308  // --- DataService interface ---
   309  
   310  // Do acts as a switchboard for RPC commands.
   311  func (d *Data) DoRPC(request datastore.Request, reply *datastore.Response) error {
   312  	if request.TypeCommand() != "load" {
   313  		return fmt.Errorf("Unknown command.  Data type '%s' [%s] does not support '%s' command.",
   314  			d.DataName(), d.TypeName(), request.TypeCommand())
   315  	}
   316  	if len(request.Command) < 5 {
   317  		return fmt.Errorf("Poorly formatted load command.  See command-line help.")
   318  	}
   319  	return d.LoadLocal(request, reply)
   320  }
   321  
   322  // ServeHTTP handles all incoming HTTP requests for this data.
   323  func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) (activity map[string]interface{}) {
   324  	timedLog := dvid.NewTimeLog()
   325  
   326  	// Get the action (GET, POST)
   327  	action := strings.ToLower(r.Method)
   328  	switch action {
   329  	case "get":
   330  	case "post":
   331  	default:
   332  		server.BadRequest(w, r, "Can only handle GET or POST HTTP verbs")
   333  		return
   334  	}
   335  
   336  	// Break URL request into arguments
   337  	url := r.URL.Path[len(server.WebAPIPath):]
   338  	parts := strings.Split(url, "/")
   339  	if len(parts[len(parts)-1]) == 0 {
   340  		parts = parts[:len(parts)-1]
   341  	}
   342  	if len(parts) < 4 {
   343  		server.BadRequest(w, r, "Incomplete API request")
   344  		return
   345  	}
   346  
   347  	// Process help and info.
   348  	switch parts[3] {
   349  	case "help":
   350  		w.Header().Set("Content-Type", "text/plain")
   351  		fmt.Fprintln(w, d.Help())
   352  		return
   353  	case "info":
   354  		jsonBytes, err := d.MarshalJSON()
   355  		if err != nil {
   356  			server.BadRequest(w, r, err)
   357  			return
   358  		}
   359  		w.Header().Set("Content-Type", "application/json")
   360  		fmt.Fprintf(w, string(jsonBytes))
   361  		return
   362  	default:
   363  	}
   364  
   365  	// Get the data name and parse out the channel number or see if composite is required.
   366  	var channelNum int32
   367  	channumStr := strings.TrimPrefix(parts[2], string(d.DataName()))
   368  	if len(channumStr) == 0 {
   369  		channelNum = 0
   370  	} else {
   371  		n, err := strconv.ParseInt(channumStr, 10, 32)
   372  		if err != nil {
   373  			server.BadRequest(w, r, "Error parsing channel number from data name '%s': %v", parts[2], err)
   374  			return
   375  		}
   376  		if int(n) > d.NumChannels {
   377  			minChannelName := fmt.Sprintf("%s1", d.DataName())
   378  			maxChannelName := fmt.Sprintf("%s%d", d.DataName(), d.NumChannels)
   379  			server.BadRequest(w, r, "Data only has %d channels.  Use names '%s' -> '%s'", d.NumChannels,
   380  				minChannelName, maxChannelName)
   381  			return
   382  		}
   383  		channelNum = int32(n)
   384  	}
   385  
   386  	// Get the data shape.
   387  	shapeStr := dvid.DataShapeString(parts[3])
   388  	dataShape, err := shapeStr.DataShape()
   389  	if err != nil {
   390  		server.BadRequest(w, r, "Bad data shape given '%s'", shapeStr)
   391  		return
   392  	}
   393  
   394  	switch dataShape.ShapeDimensions() {
   395  	case 2:
   396  		sizeStr, offsetStr := parts[4], parts[5]
   397  		slice, err := dvid.NewSliceFromStrings(shapeStr, offsetStr, sizeStr, "_")
   398  		if err != nil {
   399  			server.BadRequest(w, r, err)
   400  			return
   401  		}
   402  		if action == "post" {
   403  			server.BadRequest(w, r, "DVID does not yet support POST of slices into multichannel data")
   404  			return
   405  		} else {
   406  			if d.NumChannels == 0 || d.Data.Values == nil {
   407  				server.BadRequest(w, r, "Cannot retrieve absent data '%d'.  Please load data.", d.DataName())
   408  				return
   409  			}
   410  			values := d.Data.Values
   411  			if len(values) <= int(channelNum) {
   412  				server.BadRequest(w, r, "Must choose channel from 0 to %d", len(values))
   413  				return
   414  			}
   415  			stride := slice.Size().Value(0) * values.BytesPerElement()
   416  			dataValues := dvid.DataValues{values[channelNum]}
   417  			data := make([]uint8, int(slice.NumVoxels()))
   418  			v := imageblk.NewVoxels(slice, dataValues, data, stride)
   419  			channel := &Channel{
   420  				Voxels:     v,
   421  				channelNum: channelNum,
   422  			}
   423  			img, err := d.GetImage(ctx.VersionID(), channel.Voxels, "")
   424  			var formatStr string
   425  			if len(parts) >= 7 {
   426  				formatStr = parts[6]
   427  			}
   428  			//dvid.ElapsedTime(dvid.Normal, startTime, "%s %s upto image formatting", op, slice)
   429  			err = dvid.WriteImageHttp(w, img.Get(), formatStr)
   430  			if err != nil {
   431  				server.BadRequest(w, r, err)
   432  				return
   433  			}
   434  		}
   435  	case 3:
   436  		sizeStr, offsetStr := parts[4], parts[5]
   437  		_, err := dvid.NewSubvolumeFromStrings(offsetStr, sizeStr, "_")
   438  		if err != nil {
   439  			server.BadRequest(w, r, err)
   440  			return
   441  		}
   442  		if action == "get" {
   443  			server.BadRequest(w, r, "DVID does not yet support GET of volume data")
   444  			return
   445  		} else {
   446  			server.BadRequest(w, r, "DVID does not yet support POST of volume data")
   447  			return
   448  		}
   449  	default:
   450  		server.BadRequest(w, r, "DVID does not yet support nD volumes")
   451  		return
   452  	}
   453  	timedLog.Infof("HTTP %s: %s", r.Method, dataShape)
   454  	return
   455  }
   456  
   457  // LoadLocal adds image data to a version node.  See helpMessage for example of
   458  // command-line use of "load local".
   459  func (d *Data) LoadLocal(request datastore.Request, reply *datastore.Response) error {
   460  	timedLog := dvid.NewTimeLog()
   461  
   462  	// Parse the request
   463  	var uuidStr, dataName, cmdStr, sourceStr, filename string
   464  	_ = request.CommandArgs(1, &uuidStr, &dataName, &cmdStr, &sourceStr, &filename)
   465  
   466  	// Get the uuid from a uniquely identifiable string
   467  	uuid, versionID, err := datastore.MatchingUUID(uuidStr)
   468  	if err != nil {
   469  		return fmt.Errorf("Could not find node with UUID %s: %v", uuidStr, err)
   470  	}
   471  
   472  	// Load the V3D Raw file.
   473  	ext := filepath.Ext(filename)
   474  	switch ext {
   475  	case ".raw", ".v3draw":
   476  	default:
   477  		return fmt.Errorf("Unknown extension '%s' when expected V3D Raw file", ext)
   478  	}
   479  	file, err := os.Open(filename)
   480  	if err != nil {
   481  		return err
   482  	}
   483  	unmarshaler := V3DRawMarshaler{}
   484  	channels, err := unmarshaler.UnmarshalV3DRaw(file)
   485  	if err != nil {
   486  		return err
   487  	}
   488  
   489  	// Store the metadata
   490  	d.NumChannels = len(channels)
   491  	d.Properties.Values = make(dvid.DataValues, d.NumChannels)
   492  	if d.NumChannels > 0 {
   493  		reply.Text = fmt.Sprintf("Loaded %s into data '%s': found %d channels\n",
   494  			d.DataName(), filename, d.NumChannels)
   495  		reply.Text += fmt.Sprintf(" %s", channels[0])
   496  	} else {
   497  		reply.Text = fmt.Sprintf("Found no channels in file %s\n", filename)
   498  		return nil
   499  	}
   500  	for i, channel := range channels {
   501  		d.Properties.Values[i] = channel.Voxels.Values()[0]
   502  	}
   503  
   504  	// Get repo and save it.
   505  	if err := datastore.SaveDataByUUID(uuid, d); err != nil {
   506  		return err
   507  	}
   508  
   509  	// PUT each channel of the file into the datastore using a separate data name.
   510  	mutID := d.NewMutationID()
   511  	for _, channel := range channels {
   512  		dvid.Infof("Processing channel %d... \n", channel.channelNum)
   513  		err = d.IngestVoxels(versionID, mutID, channel.Voxels, "")
   514  		if err != nil {
   515  			return err
   516  		}
   517  	}
   518  
   519  	// Create a RGB composite from the first 3 channels.  This is considered to be channel 0
   520  	// or can be accessed with the base data name.
   521  	dvid.Infof("Creating composite image from channels...\n")
   522  	err = d.storeComposite(versionID, mutID, channels)
   523  	if err != nil {
   524  		return err
   525  	}
   526  
   527  	timedLog.Infof("RPC load local '%s' completed", filename)
   528  	return nil
   529  }
   530  
   531  // Create a RGB interleaved volume.
   532  func (d *Data) storeComposite(v dvid.VersionID, mutID uint64, channels []*Channel) error {
   533  	// Setup the composite Channel
   534  	geom := channels[0].Geometry
   535  	pixels := int(geom.NumVoxels())
   536  	stride := geom.Size().Value(0) * 4
   537  	composite := &Channel{
   538  		Voxels:     imageblk.NewVoxels(geom, compositeValues, channels[0].Data(), stride),
   539  		channelNum: channels[0].channelNum,
   540  	}
   541  
   542  	// Get the min/max of each channel.
   543  	numChannels := len(channels)
   544  	if numChannels > 3 {
   545  		numChannels = 3
   546  	}
   547  	var min, max [3]uint16
   548  	min[0] = uint16(0xFFFF)
   549  	min[1] = uint16(0xFFFF)
   550  	min[2] = uint16(0xFFFF)
   551  	for c := 0; c < numChannels; c++ {
   552  		channel := channels[c]
   553  		data := channel.Data()
   554  		beg := 0
   555  		for i := 0; i < pixels; i++ {
   556  			value := binary.LittleEndian.Uint16(data[beg : beg+2])
   557  			if value < min[c] {
   558  				min[c] = value
   559  			}
   560  			if value > max[c] {
   561  				max[c] = value
   562  			}
   563  			beg += 2
   564  		}
   565  	}
   566  
   567  	// Do second pass, normalizing each channel and storing it into the appropriate byte.
   568  	compdata := composite.Voxels.Data()
   569  	for c := 0; c < numChannels; c++ {
   570  		channel := channels[c]
   571  		window := int(max[c] - min[c])
   572  		if window == 0 {
   573  			window = 1
   574  		}
   575  		data := channel.Data()
   576  		beg := 0
   577  		begC := c // Channel 0 -> R, Channel 1 -> G, Channel 2 -> B
   578  		for i := 0; i < pixels; i++ {
   579  			value := binary.LittleEndian.Uint16(data[beg : beg+2])
   580  			normalized := 255 * int(value-min[c]) / window
   581  			if normalized > 255 {
   582  				normalized = 255
   583  			}
   584  			compdata[begC] = uint8(normalized)
   585  			beg += 2
   586  			begC += 4
   587  		}
   588  	}
   589  
   590  	// Set the alpha channel to 255.
   591  	alphaI := 3
   592  	for i := 0; i < pixels; i++ {
   593  		compdata[alphaI] = 255
   594  		alphaI += 4
   595  	}
   596  
   597  	// Store the result
   598  	return d.IngestVoxels(v, mutID, composite.Voxels, "")
   599  }