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 }