// NewLabels returns labelblk Labels, a representation of externally usable subvolume // or slice data, given some geometry and optional image data. // If img is passed in, the function will initialize Voxels with data from the image. // Otherwise, it will allocate a zero buffer of appropriate size. // // TODO : Unlike the standard imageblk.NewVoxels, the labelblk version can modify the // labels based on the z-coordinate of the given geometry for Raveler labeling. // This will be removed when Raveler-specific labels are moved outside DVID. func (d *Data) NewLabels(geom dvid.Geometry, img interface{}) (*Labels, error) { bytesPerVoxel := d.Properties.Values.BytesPerElement() stride := geom.Size().Value(0) * bytesPerVoxel var data []byte if img == nil { numVoxels := geom.NumVoxels() if numVoxels <= 0 { return nil, fmt.Errorf("Illegal geometry requested: %s", geom) } requestSize := int64(bytesPerVoxel) * numVoxels if requestSize > server.MaxDataRequest { return nil, fmt.Errorf("Requested payload (%d bytes) exceeds this DVID server's set limit (%d)", requestSize, server.MaxDataRequest) } data = make([]byte, requestSize) } else { switch t := img.(type) { case image.Image: var inputBytesPerVoxel, actualStride int32 var err error data, inputBytesPerVoxel, actualStride, err = dvid.ImageData(t) if err != nil { return nil, err } if actualStride != stride { // Need to do some conversion here. switch d.Labeling { case Standard64bit: data, err = d.convertTo64bit(geom, data, int(inputBytesPerVoxel), int(actualStride)) if err != nil { return nil, err } case RavelerLabel: data, err = d.addLabelZ(geom, data, actualStride) if err != nil { return nil, err } default: return nil, fmt.Errorf("unexpected label type in labelblk: %s", d.Labeling) } } case []byte: data = t actualLen := int64(len(data)) expectedLen := int64(bytesPerVoxel) * geom.NumVoxels() if actualLen != expectedLen { return nil, fmt.Errorf("labels data was %d bytes, expected %d bytes for %s", actualLen, expectedLen, geom) } default: return nil, fmt.Errorf("unexpected image type given to NewVoxels(): %T", t) } } labels := &Labels{ imageblk.NewVoxels(geom, d.Properties.Values, data, stride), } return labels, nil }
// NewVoxels returns Voxels with given geometry and optional image data. // If img is passed in, the function will initialize the Voxels with data from the image. // Otherwise, it will allocate a zero buffer of appropriate size. func (d *Data) NewVoxels(geom dvid.Geometry, img interface{}) (*Voxels, error) { bytesPerVoxel := d.Properties.Values.BytesPerElement() stride := geom.Size().Value(0) * bytesPerVoxel voxels := &Voxels{ Geometry: geom, values: d.Properties.Values, stride: stride, } if img == nil { numVoxels := geom.NumVoxels() if numVoxels <= 0 { return nil, fmt.Errorf("Illegal geometry requested: %s", geom) } requestSize := int64(bytesPerVoxel) * numVoxels if requestSize > server.MaxDataRequest { return nil, fmt.Errorf("Requested payload (%d bytes) exceeds this DVID server's set limit (%d)", requestSize, server.MaxDataRequest) } voxels.data = make([]uint8, requestSize) } else { switch t := img.(type) { case image.Image: var actualStride int32 var err error voxels.data, _, actualStride, err = dvid.ImageData(t) if err != nil { return nil, err } if actualStride < stride { return nil, fmt.Errorf("Too little data in input image (expected stride %d)", stride) } voxels.stride = actualStride case []byte: voxels.data = t actualLen := int64(len(voxels.data)) expectedLen := int64(bytesPerVoxel) * geom.NumVoxels() if actualLen != expectedLen { return nil, fmt.Errorf("voxels data was %d bytes, expected %d bytes for %s", actualLen, expectedLen, geom) } default: return nil, fmt.Errorf("Unexpected image type given to NewVoxels(): %T", t) } } return voxels, nil }
// Convert a 32-bit label into a 64-bit label by adding the Z coordinate into high 32 bits. // Also drops the high byte (alpha channel) since Raveler labels only use 24-bits. func (d *Data) addLabelZ(geom dvid.Geometry, data32 []uint8, stride int32) ([]byte, error) { if len(data32)%4 != 0 { return nil, fmt.Errorf("expected 4 byte/voxel alignment but have %d bytes!", len(data32)) } coord := geom.StartPoint() if coord.NumDims() < 3 { return nil, fmt.Errorf("expected n-d (n >= 3) offset for image. Got %d dimensions.", coord.NumDims()) } superpixelBytes := make([]byte, 8, 8) binary.BigEndian.PutUint32(superpixelBytes[0:4], uint32(coord.Value(2))) nx := int(geom.Size().Value(0)) ny := int(geom.Size().Value(1)) numBytes := nx * ny * 8 data64 := make([]byte, numBytes, numBytes) dstI := 0 for y := 0; y < ny; y++ { srcI := y * int(stride) for x := 0; x < nx; x++ { if data32[srcI] == 0 && data32[srcI+1] == 0 && data32[srcI+2] == 0 { copy(data64[dstI:dstI+8], ZeroBytes()) } else { superpixelBytes[5] = data32[srcI+2] superpixelBytes[6] = data32[srcI+1] superpixelBytes[7] = data32[srcI] copy(data64[dstI:dstI+8], superpixelBytes) } // NOTE: we skip the 4th byte (alpha) at srcI+3 //a := uint32(data32[srcI+3]) //b := uint32(data32[srcI+2]) //g := uint32(data32[srcI+1]) //r := uint32(data32[srcI+0]) //spid := (b << 16) | (g << 8) | r srcI += 4 dstI += 8 } } return data64, nil }
// GetImage returns an image given a 2d orthogonal image description. Since imagetile tiles // have precomputed XY, XZ, and YZ orientations, reconstruction of the desired image should // be much faster than computing the image from voxel blocks. func (d *Data) GetImage(ctx storage.Context, src *imageblk.Data, geom dvid.Geometry, isotropic bool) (*dvid.Image, error) { // Iterate through tiles that intersect our geometry. if d.Levels == nil || len(d.Levels) == 0 { return nil, fmt.Errorf("%s has no specification for tiles at highest resolution", d.DataName()) } levelSpec := d.Levels[0] minSlice, err := dvid.Isotropy2D(src.VoxelSize, geom, isotropic) if err != nil { return nil, err } // Create an image of appropriate size and type using source's ExtData creation. dstW := minSlice.Size().Value(0) dstH := minSlice.Size().Value(1) dst, err := src.BlankImage(dstW, dstH) if err != nil { return nil, err } // Read each tile that intersects the geometry and store into final image. slice := minSlice.DataShape() tileW, tileH, err := slice.GetSize2D(levelSpec.TileSize) if err != nil { return nil, err } tileSize := dvid.Point2d{tileW, tileH} minPtX, minPtY, err := slice.GetSize2D(minSlice.StartPoint()) if err != nil { return nil, err } wg := new(sync.WaitGroup) topLeftGlobal := dvid.Point2d{minPtX, minPtY} tilePt := topLeftGlobal.Chunk(tileSize) bottomRightGlobal := tilePt.MaxPoint(tileSize).(dvid.Point2d) y0 := int32(0) y1 := bottomRightGlobal[1] - minPtY + 1 for y0 < dstH { x0 := int32(0) x1 := bottomRightGlobal[0] - minPtX + 1 for x0 < dstW { wg.Add(1) go func(x0, y0, x1, y1 int32) { defer wg.Done() // Get this tile from datastore tileCoord, err := slice.PlaneToChunkPoint3d(x0, y0, minSlice.StartPoint(), levelSpec.TileSize) goImg, err := d.GetTile(ctx, slice, 0, dvid.IndexZYX(tileCoord)) if err != nil || goImg == nil { return } // Get tile space coordinate for top left. curStart := dvid.Point2d{x0 + minPtX, y0 + minPtY} p := curStart.PointInChunk(tileSize) ptInTile := image.Point{int(p.Value(0)), int(p.Value(1))} // Paste the pertinent rectangle from this tile into our destination. r := image.Rect(int(x0), int(y0), int(x1), int(y1)) draw.Draw(dst.GetDrawable(), r, goImg, ptInTile, draw.Src) }(x0, y0, x1, y1) x0 = x1 x1 += tileW } y0 = y1 y1 += tileH } wg.Wait() if isotropic { dstW := int(geom.Size().Value(0)) dstH := int(geom.Size().Value(1)) dst, err = dst.ScaleImage(dstW, dstH) if err != nil { return nil, err } } return dst, nil }
// Convert raw image data into a 2d array of 64-bit labels func (d *Data) convertTo64bit(geom dvid.Geometry, data []uint8, bytesPerVoxel, stride int) ([]byte, error) { nx := int(geom.Size().Value(0)) ny := int(geom.Size().Value(1)) numBytes := nx * ny * 8 data64 := make([]byte, numBytes, numBytes) var byteOrder binary.ByteOrder if geom.DataShape().ShapeDimensions() == 2 { byteOrder = binary.BigEndian // This is the default for PNG } else { byteOrder = binary.LittleEndian } switch bytesPerVoxel { case 1: dstI := 0 for y := 0; y < ny; y++ { srcI := y * stride for x := 0; x < nx; x++ { binary.LittleEndian.PutUint64(data64[dstI:dstI+8], uint64(data[srcI])) srcI++ dstI += 8 } } case 2: dstI := 0 for y := 0; y < ny; y++ { srcI := y * stride for x := 0; x < nx; x++ { value := byteOrder.Uint16(data[srcI : srcI+2]) binary.LittleEndian.PutUint64(data64[dstI:dstI+8], uint64(value)) srcI += 2 dstI += 8 } } case 4: dstI := 0 for y := 0; y < ny; y++ { srcI := y * stride for x := 0; x < nx; x++ { value := byteOrder.Uint32(data[srcI : srcI+4]) binary.LittleEndian.PutUint64(data64[dstI:dstI+8], uint64(value)) srcI += 4 dstI += 8 } } case 8: dstI := 0 for y := 0; y < ny; y++ { srcI := y * stride for x := 0; x < nx; x++ { value := byteOrder.Uint64(data[srcI : srcI+8]) binary.LittleEndian.PutUint64(data64[dstI:dstI+8], uint64(value)) srcI += 8 dstI += 8 } } default: return nil, fmt.Errorf("could not convert to 64-bit label given %d bytes/voxel", bytesPerVoxel) } return data64, nil }