github.com/janelia-flyem/dvid@v1.0.0/datatype/labelmap/points.go (about) 1 // Functions that support point operations across DVID datatypes. 2 3 package labelmap 4 5 import ( 6 "encoding/binary" 7 "fmt" 8 "sort" 9 "sync" 10 11 "github.com/janelia-flyem/dvid/datatype/common/labels" 12 "github.com/janelia-flyem/dvid/dvid" 13 ) 14 15 // GetLabelAtScaledPoint returns the 64-bit unsigned int label for a given point. 16 func (d *Data) GetLabelAtScaledPoint(v dvid.VersionID, pt dvid.Point, scale uint8, supervoxels bool) (uint64, error) { 17 coord, ok := pt.(dvid.Chunkable) 18 if !ok { 19 return 0, fmt.Errorf("Can't determine block of point %s", pt) 20 } 21 blockSize := d.BlockSize() 22 bcoord := coord.Chunk(blockSize).(dvid.ChunkPoint3d) 23 24 labelData, err := d.getBlockLabels(v, bcoord, scale, supervoxels) 25 if err != nil { 26 return 0, err 27 } 28 if len(labelData) == 0 { 29 return 0, nil 30 } 31 32 // Retrieve the particular label within the block. 33 ptInBlock := coord.PointInChunk(blockSize) 34 nx := int64(blockSize.Value(0)) 35 nxy := nx * int64(blockSize.Value(1)) 36 i := (int64(ptInBlock.Value(0)) + int64(ptInBlock.Value(1))*nx + int64(ptInBlock.Value(2))*nxy) * 8 37 38 return binary.LittleEndian.Uint64(labelData[i : i+8]), nil 39 } 40 41 // The following functions implement an interface to synced data types like annotation. 42 43 // GetLabelBytes returns a block of hi-res (body) labels (scale 0) in packed little-endian uint64 format 44 func (d *Data) GetLabelBytes(v dvid.VersionID, bcoord dvid.ChunkPoint3d) ([]byte, error) { 45 return d.getBlockLabels(v, bcoord, 0, false) 46 } 47 48 // GetLabelAtPoint returns the 64-bit unsigned int label for a given point. 49 func (d *Data) GetLabelAtPoint(v dvid.VersionID, pt dvid.Point) (uint64, error) { 50 return d.GetLabelAtScaledPoint(v, pt, 0, false) 51 } 52 53 // GetSupervoxelAtPoint returns the 64-bit unsigned int supervoxel id for a given point. 54 func (d *Data) GetSupervoxelAtPoint(v dvid.VersionID, pt dvid.Point) (uint64, error) { 55 return d.GetLabelAtScaledPoint(v, pt, 0, true) 56 } 57 58 type ptsIndex struct { 59 pts []dvid.Point3d 60 indices []int 61 } 62 63 type blockPtsI struct { 64 *labels.PositionedBlock 65 ptsIndex 66 } 67 68 type blockPtsSlice []blockPtsI 69 70 // --- Functions for sort.Sort interface on blockPtsSlice 71 72 func (b blockPtsSlice) Len() int { 73 return len(b) 74 } 75 76 func (b blockPtsSlice) Less(i, j int) bool { 77 return b[i].BCoord < b[j].BCoord 78 } 79 80 func (b blockPtsSlice) Swap(i, j int) { 81 b[i], b[j] = b[j], b[i] 82 } 83 84 // partitions points int blocks and returns a list of blocks with points, sorted 85 // by block coordinate in ZYX order. 86 func (d *Data) sortByBlockCoord(pts []dvid.Point3d) blockPtsSlice { 87 // iterate through points, adding to the corresponding blockPtsI if it exists 88 // or creating a new one. 89 indexStartSize := len(pts) / 10 90 if indexStartSize < 10 { 91 indexStartSize = 10 92 } 93 blockIndex := make(map[dvid.IZYXString]int, indexStartSize) 94 blockPts := make(blockPtsSlice, 0, indexStartSize) 95 blockSize := d.BlockSize().(dvid.Point3d) 96 for origPos, pt := range pts { 97 // Autogenerated. 98 x := pt[0] / blockSize[0] 99 y := pt[1] / blockSize[1] 100 z := pt[2] / blockSize[2] 101 bx := pt[0] % blockSize[0] 102 by := pt[1] % blockSize[1] 103 bz := pt[2] % blockSize[2] 104 bcoord := dvid.ChunkPoint3d{x, y, z}.ToIZYXString() 105 i, found := blockIndex[bcoord] 106 if found { 107 blockPts[i].pts = append(blockPts[i].pts, dvid.Point3d{bx, by, bz}) 108 blockPts[i].indices = append(blockPts[i].indices, origPos) 109 } else { 110 i = len(blockPts) 111 blockIndex[bcoord] = i 112 blockPts = append(blockPts, blockPtsI{ 113 PositionedBlock: &labels.PositionedBlock{ 114 BCoord: bcoord, 115 }, 116 ptsIndex: ptsIndex{ 117 pts: []dvid.Point3d{dvid.Point3d{bx, by, bz}}, 118 indices: []int{origPos}, 119 }, 120 }) 121 } 122 } 123 sort.Sort(blockPts) 124 return blockPts 125 } 126 127 func (d *Data) partitionPoints(pts []dvid.Point3d) map[dvid.IZYXString]ptsIndex { 128 blockSize := d.BlockSize().(dvid.Point3d) 129 blockPts := make(map[dvid.IZYXString]ptsIndex) 130 for i, pt := range pts { 131 x := pt[0] / blockSize[0] 132 y := pt[1] / blockSize[1] 133 z := pt[2] / blockSize[2] 134 bx := pt[0] % blockSize[0] 135 by := pt[1] % blockSize[1] 136 bz := pt[2] % blockSize[2] 137 bpt := dvid.Point3d{bx, by, bz} 138 bcoord := dvid.ChunkPoint3d{x, y, z}.ToIZYXString() 139 ptsi, found := blockPts[bcoord] 140 if found { 141 ptsi.pts = append(ptsi.pts, bpt) 142 ptsi.indices = append(ptsi.indices, i) 143 } else { 144 ptsi.pts = []dvid.Point3d{bpt} 145 ptsi.indices = []int{i} 146 } 147 blockPts[bcoord] = ptsi 148 } 149 return blockPts 150 } 151 152 func (d *Data) getFewLabelPoints(mapped []uint64, v dvid.VersionID, bptsSlice blockPtsSlice, mapping *VCache, mappedVersions distFromRoot, scale uint8) error { 153 for _, bptsI := range bptsSlice { 154 chunkPt3d, err := bptsI.BCoord.ToChunkPoint3d() 155 if err != nil { 156 return err 157 } 158 block, err := d.getSupervoxelBlock(v, chunkPt3d, scale) 159 if err != nil { 160 return err 161 } 162 if len(mappedVersions) > 0 { 163 mapping.ApplyMappingToBlock(mappedVersions, block) 164 } 165 blockLabels := block.GetPointLabels(bptsI.pts) 166 for i, index := range bptsI.indices { 167 mapped[index] = blockLabels[i] 168 } 169 } 170 return nil 171 } 172 173 func (d *Data) getManyLabelPoints(mapped []uint64, v dvid.VersionID, bptsSlice blockPtsSlice, mapping *VCache, mappedVersions distFromRoot, scale uint8) (err error) { 174 timedLog := dvid.NewTimeLog() 175 176 // Launch goroutines to receive blocks and extract labels directly from compact data structure. 177 var processorWG sync.WaitGroup 178 processors := len(bptsSlice) / 10 179 if processors < 1 { 180 processors = 1 181 } 182 processCh := make(chan *blockPtsI, processors) 183 for c := 0; c < processors; c++ { 184 processorWG.Add(1) 185 go func() { 186 for bptsI := range processCh { 187 if len(mappedVersions) > 0 { 188 mapping.ApplyMappingToBlock(mappedVersions, &(bptsI.Block)) 189 } 190 blockLabels := bptsI.Block.GetPointLabels(bptsI.pts) 191 for i, index := range bptsI.indices { 192 mapped[index] = blockLabels[i] 193 } 194 } 195 processorWG.Done() 196 }() 197 } 198 199 // Launch small number of goroutines to get blocks concurrently since tests suggest 200 // we don't saturate SSDs with up to 16 goroutines. (Depends on SSD load) 201 var readerWG sync.WaitGroup 202 numReaders := 16 203 readerCh := make(chan *blockPtsI, numReaders) 204 errorCh := make(chan error) 205 for r := 0; r < numReaders; r++ { 206 readerWG.Add(1) 207 go func(reader int) { 208 for bptsI := range readerCh { 209 chunkPt3d, err := bptsI.BCoord.ToChunkPoint3d() 210 if err != nil { 211 errorCh <- err 212 readerWG.Done() 213 return 214 } 215 block, err := d.getSupervoxelBlock(v, chunkPt3d, scale) 216 if err != nil { 217 errorCh <- err 218 readerWG.Done() 219 return 220 } 221 bptsI.Block = *block 222 processCh <- bptsI 223 } 224 readerWG.Done() 225 }(r) 226 } 227 228 // Stream sorted blocks to readers. 229 for i := 0; i < len(bptsSlice); i++ { 230 readerCh <- &(bptsSlice[i]) 231 } 232 close(readerCh) 233 readerWG.Wait() 234 235 close(processCh) 236 processorWG.Wait() 237 238 timedLog.Infof("Large-scale labelmap %q label lookup for %d pts -> %d blocks with %d processors / %d readers", 239 d.DataName(), len(mapped), len(bptsSlice), processors, numReaders) 240 241 select { 242 case err := <-errorCh: 243 dvid.Errorf("Large-scale labelmap %q label lookup had error: %v", d.DataName(), err) 244 return err 245 default: 246 return nil 247 } 248 } 249 250 // GetLabelPoints returns labels or supervoxels corresponding to given 3d points. 251 func (d *Data) GetLabelPoints(v dvid.VersionID, pts []dvid.Point3d, scale uint8, useSupervoxels bool) (mapped []uint64, err error) { 252 if len(pts) == 0 { 253 return 254 } 255 mapped = make([]uint64, len(pts)) 256 bptsSlice := d.sortByBlockCoord(pts) 257 258 // Get mapping. 259 var mapping *VCache 260 var mappedVersions distFromRoot 261 if !useSupervoxels { 262 if mapping, err = getMapping(d, v); err != nil { 263 return nil, err 264 } 265 if mapping != nil { 266 mappedVersions = mapping.getMappedVersionsDist(v) 267 if err != nil { 268 err = fmt.Errorf("unable to get ancestry for version %d: %v", v, err) 269 return 270 } 271 } 272 } 273 274 if len(pts) < 100 { 275 err = d.getFewLabelPoints(mapped, v, bptsSlice, mapping, mappedVersions, scale) 276 } else { 277 err = d.getManyLabelPoints(mapped, v, bptsSlice, mapping, mappedVersions, scale) 278 } 279 return 280 } 281 282 // GetPointsInSupervoxels returns the 3d points that fall within given supervoxels that are 283 // assumed to be mapped to one label. If supervoxels are assigned to more than one label, 284 // or a mapping is not available, an error is returned. The label index is not used so 285 // this function will use immutable data underneath if there are no supervoxel splits. 286 func (d *Data) GetPointsInSupervoxels(v dvid.VersionID, pts []dvid.Point3d, supervoxels []uint64) (inSupervoxels []bool, err error) { 287 inSupervoxels = make([]bool, len(pts)) 288 if len(pts) == 0 || len(supervoxels) == 0 { 289 return 290 } 291 timedLog := dvid.NewTimeLog() 292 293 supervoxelSet := make(labels.Set) 294 for _, supervoxel := range supervoxels { 295 supervoxelSet[supervoxel] = struct{}{} 296 } 297 ptsByBlock := d.partitionPoints(pts) 298 299 // Launch the block processing goroutines 300 var wg sync.WaitGroup 301 concurrency := len(ptsByBlock) / 10 302 if concurrency < 1 { 303 concurrency = 1 304 } 305 ch := make(chan blockPtsI, len(ptsByBlock)) 306 for c := 0; c < concurrency; c++ { 307 wg.Add(1) 308 go func() { 309 for bptsI := range ch { 310 blockLabels := bptsI.Block.GetPointLabels(bptsI.pts) 311 for i, index := range bptsI.indices { 312 supervoxel := blockLabels[i] 313 if _, found := supervoxelSet[supervoxel]; found { 314 inSupervoxels[index] = true 315 } 316 } 317 } 318 wg.Done() 319 }() 320 } 321 322 // Send the blocks spanning the given supervoxels. 323 var block *labels.Block 324 for bcoord, bptsI := range ptsByBlock { 325 var chunkPt3d dvid.ChunkPoint3d 326 if chunkPt3d, err = bcoord.ToChunkPoint3d(); err != nil { 327 close(ch) 328 return 329 } 330 if block, err = d.getSupervoxelBlock(v, chunkPt3d, 0); err != nil { 331 close(ch) 332 return nil, err 333 } 334 ch <- blockPtsI{&labels.PositionedBlock{Block: *block}, bptsI} 335 } 336 close(ch) 337 wg.Wait() 338 339 timedLog.Infof("Annotation %q point check for %d supervoxels, %d points -> %d blocks (%d goroutines)", d.DataName(), len(supervoxels), len(pts), len(ptsByBlock), concurrency) 340 return 341 }