github.com/janelia-flyem/dvid@v1.0.0/datatype/labelsz/labelsz.go (about) 1 /* 2 Package labelsz supports ranking labels by # annotations of each type. 3 */ 4 package labelsz 5 6 import ( 7 "bytes" 8 "encoding/binary" 9 "encoding/gob" 10 "encoding/json" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "math" 15 "net/http" 16 "reflect" 17 "strconv" 18 "strings" 19 "sync" 20 21 "github.com/janelia-flyem/dvid/datastore" 22 "github.com/janelia-flyem/dvid/datatype/annotation" 23 "github.com/janelia-flyem/dvid/datatype/roi" 24 "github.com/janelia-flyem/dvid/dvid" 25 "github.com/janelia-flyem/dvid/server" 26 "github.com/janelia-flyem/dvid/storage" 27 ) 28 29 const ( 30 Version = "0.1" 31 RepoURL = "github.com/janelia-flyem/dvid/datatype/labelsz" 32 TypeName = "labelsz" 33 ) 34 35 const helpMessage = ` 36 API for labelsz data type (github.com/janelia-flyem/dvid/datatype/labelsz) 37 ======================================================================================= 38 39 Note: UUIDs referenced below are strings that may either be a unique prefix of a 40 hexadecimal UUID string (e.g., 3FA22) or a branch leaf specification that adds 41 a colon (":") followed by the case-dependent branch name. In the case of a 42 branch leaf specification, the unique UUID prefix just identifies the repo of 43 the branch, and the UUID referenced is really the leaf of the branch name. 44 For example, if we have a DAG with root A -> B -> C where C is the current 45 HEAD or leaf of the "master" (default) branch, then asking for "B:master" is 46 the same as asking for "C". If we add another version so A -> B -> C -> D, then 47 references to "B:master" now return the data from "D". 48 49 Command-line: 50 51 $ dvid repo <UUID> new labelsz <data name> <settings...> 52 53 Adds newly named data of the 'type name' to repo with specified UUID. 54 55 Example: 56 57 $ dvid repo 3f8c new labelsz labelrankings 58 59 Arguments: 60 61 UUID Hexadecimal string with enough characters to uniquely identify a version node. 62 data name Name of data to create, e.g., "labelrankings" 63 settings Configuration settings in "key=value" format separated by spaces. 64 65 Configuration Settings (case-insensitive keys) 66 67 ROI Value must be in "<roiname>,<uuid>" format where <roiname> is the name of the 68 static ROI that defines the extent of tracking and <uuid> is the immutable 69 version used for this labelsz. 70 71 ------------------ 72 73 HTTP API (Level 2 REST): 74 75 GET <api URL>/node/<UUID>/<data name>/help 76 77 Returns data-specific help message. 78 79 80 GET <api URL>/node/<UUID>/<data name>/info 81 POST <api URL>/node/<UUID>/<data name>/info 82 83 Retrieves or puts DVID-specific data properties for this labelsz data instance. 84 85 Example: 86 87 GET <api URL>/node/3f8c/labelrankings/info 88 89 Returns JSON with configuration settings. 90 91 Arguments: 92 93 UUID Hexadecimal string with enough characters to uniquely identify a version node. 94 data name Name of labelsz data. 95 96 97 POST /api/repo/{uuid}/instance 98 99 Creates a new instance of the given data type. Expects configuration data in JSON 100 as the body of the POST. Configuration data is a JSON object with each property 101 corresponding to a configuration keyword for the particular data type. 102 103 JSON name/value pairs: 104 105 REQUIRED "typename" Should equal "labelsz" 106 REQUIRED "dataname" Name of the new instance 107 OPTIONAL "versioned" If "false" or "0", the data is unversioned and acts as if 108 all UUIDs within a repo become the root repo UUID. (True by default.) 109 110 OPTIONAL "ROI" Value must be in "<roiname>,<uuid>" format where <roiname> is the name of the 111 static ROI that defines the extent of tracking and <uuid> is the immutable 112 version used for this labelsz. 113 114 POST <api URL>/node/<UUID>/<data name>/sync?<options> 115 116 Establishes data instances for which the label sizes are computed. Expects JSON to be POSTed 117 with the following format: 118 119 { "sync": "synapses" } 120 121 To delete syncs, pass an empty string of names with query string "replace=true": 122 123 { "sync": "" } 124 125 The "sync" property should be followed by a comma-delimited list of data instances that MUST 126 already exist. After this sync request, the labelsz data are computed for the first time 127 and then kept in sync thereafter. It is not allowed to change syncs. You can, however, 128 create a new labelsz data instance and sync it as required. 129 130 The labelsz data type only accepts syncs to annotation data instances. 131 132 GET Query-string Options: 133 134 replace Set to "true" if you want passed syncs to replace and not be appended to current syncs. 135 Default operation is false. 136 137 138 GET <api URL>/node/<UUID>/<data name>/count/<label>/<index type> 139 140 Returns the count of the given annotation element type for the given label. 141 The index type may be any annotation element type ("PostSyn", "PreSyn", "Gap", "Note"), 142 the catch-all for synapses "AllSyn", or the number of voxels "Voxels". 143 144 For synapse indexing, the labelsz data instance must be synced with an annotations instance. 145 (future) For # voxel indexing, the labelsz data instance must be synced with a labelvol instance. 146 147 Example: 148 149 GET <api URL>/node/3f8c/labelrankings/size/21847/PreSyn 150 151 Returns: 152 153 { "Label": 21847, "PreSyn": 81 } 154 155 156 Note: For the following URL endpoints that return and accept POSTed JSON values, see the JSON format 157 at end of this documentation. 158 159 GET <api URL>/node/<UUID>/<data name>/counts/<index type> 160 161 Returns the count of the given annotation element type for the POSTed labels. 162 Note "counts" is plural. 163 <index type> is the same as individual GET call (eg, PostSyn, AllSyn, etc). 164 165 The body of the request will contain a json list of labels. 166 Return value should be like the /top endpoint: 167 168 [ 169 { "Label": 188, "PreSyn": 81 }, 170 { "Label": 23, "PreSyn": 65 }, 171 { "Label": 8137, "PreSyn": 58 } 172 ] 173 174 GET <api URL>/node/<UUID>/<data name>/top/<N>/<index type> 175 176 Returns a list of the top N labels with respect to number of the specified index type. 177 The index type may be any annotation element type ("PostSyn", "PreSyn", "Gap", "Note"), 178 the catch-all for synapses "AllSyn", or the number of voxels "Voxels". 179 180 For synapse indexing, the labelsz data instance must be synced with an annotations instance. 181 (future) For # voxel indexing, the labelsz data instance must be synced with a labelvol instance. 182 183 Example: 184 185 GET <api URL>/node/3f8c/labelrankings/top/3/PreSyn 186 187 Returns: 188 189 [ { "Label": 188, "PreSyn": 81 }, { "Label": 23, "PreSyn": 65 }, { "Label": 8137, "PreSyn": 58 } ] 190 191 GET <api URL>/node/<UUID>/<data name>/threshold/<T>/<index type>[?<options>] 192 193 Returns a list of up to 10,000 labels per request that have # given element types >= T. 194 The "page" size is 10,000 labels so a call without any query string will return the 195 largest labels with # given element types >= T. If there are more than 10,000 labels, 196 you can access the next 10,000 by including "?offset=10001". 197 198 The index type may be any annotation element type ("PostSyn", "PreSyn", "Gap", "Note"), 199 the catch-all for synapses "AllSyn", or the number of voxels "Voxels". 200 201 For synapse indexing, the labelsz data instance must be synced with an annotations instance. 202 (future) For # voxel indexing, the labelsz data instance must be synced with a labelvol instance. 203 204 GET Query-string Options: 205 206 offset The starting rank in the sorted list (in descending order) of labels with # given element types >= T. 207 n Number of labels to return. 208 209 Example: 210 211 GET <api URL>/node/3f8c/labelrankings/threshold/10/PreSyn?offset=10001&n=3 212 213 Returns: 214 215 [ { "Label": 188, "PreSyn": 38 }, { "Label": 23, "PreSyn": 38 }, { "Label": 8137, "PreSyn": 37 } ] 216 217 In the above example, the query returns the labels ranked #10,001 to #10,003 in the sorted list, in 218 descending order of # PreSyn >= 10. 219 220 POST <api URL>/node/<UUID>/<data name>/reload 221 222 Forces asynchornous denormalization from its synced annotations instance. Can be 223 used to initialize a newly added instance. Note that the labelsz will be locked until 224 the denormalization is finished with a log message. 225 ` 226 227 var ( 228 dtype *Type 229 ) 230 231 const ( 232 MaxLabelsReturned = 10000 // Maximum number of labels returned in JSON 233 ) 234 235 func init() { 236 dtype = new(Type) 237 dtype.Type = datastore.Type{ 238 Name: TypeName, 239 URL: RepoURL, 240 Version: Version, 241 Requirements: &storage.Requirements{ 242 Batcher: true, 243 }, 244 } 245 246 // See doc for package on why channels are segregated instead of interleaved. 247 // Data types must be registered with the datastore to be used. 248 datastore.Register(dtype) 249 250 // Need to register types that will be used to fulfill interfaces. 251 gob.Register(&Type{}) 252 gob.Register(&Data{}) 253 } 254 255 // LabelSize is the count for a given label for some metric like # of PreSyn annotations. 256 type LabelSize struct { 257 Label uint64 258 Size uint32 259 } 260 261 // LabelSizes is a sortable slice of LabelSize 262 type LabelSizes []LabelSize 263 264 // --- Sort interface 265 266 func (s LabelSizes) Len() int { 267 return len(s) 268 } 269 270 func (s LabelSizes) Less(i, j int) bool { 271 return s[i].Size < s[j].Size 272 } 273 274 func (s LabelSizes) Swap(i, j int) { 275 s[i], s[j] = s[j], s[i] 276 } 277 278 // NewData returns a pointer to labelsz data. 279 func NewData(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (*Data, error) { 280 // See if we have a valid DataService ROI 281 var roistr string 282 roistr, found, err := c.GetString("ROI") 283 if err != nil { 284 return nil, err 285 } 286 if found { 287 parts := strings.Split(roistr, ",") 288 if len(parts) != 2 { 289 return nil, fmt.Errorf("bad ROI value (%q) expected %q", roistr, "<roiname>,<uuid>") 290 } 291 } 292 293 // Initialize the Data for this data type 294 basedata, err := datastore.NewDataService(dtype, uuid, id, name, c) 295 if err != nil { 296 return nil, err 297 } 298 data := &Data{ 299 Data: basedata, 300 Properties: Properties{ 301 StaticROI: roistr, 302 }, 303 } 304 return data, nil 305 } 306 307 // --- Labelsz Datatype ----- 308 309 type Type struct { 310 datastore.Type 311 } 312 313 // --- TypeService interface --- 314 315 func (dtype *Type) NewDataService(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (datastore.DataService, error) { 316 return NewData(uuid, id, name, c) 317 } 318 319 func (dtype *Type) Help() string { 320 return helpMessage 321 } 322 323 // Properties are additional properties for data beyond those in standard datastore.Data. 324 type Properties struct { 325 // StaticROI is an optional static ROI specification of the form "<roiname>,<uuid>" 326 // Note that it *cannot* mutate after the labelsz instance is created. 327 StaticROI string 328 } 329 330 // Data instance of labelvol, label sparse volumes. 331 type Data struct { 332 *datastore.Data 333 Properties 334 335 // Keep track of sync operations that could be updating the data. 336 datastore.Updater 337 338 // cache of immutable ROI on which this labelsz is filtered if any. 339 iMutex sync.Mutex 340 iROI *roi.Immutable 341 roiChecked bool 342 343 // channels for processing messages from synced data like annotations 344 syncCh chan datastore.SyncMessage 345 syncDone chan *sync.WaitGroup 346 347 // if true, should just return error if trying to access the stats 348 uninitialized bool 349 } 350 351 func (d *Data) Equals(d2 *Data) bool { 352 if !d.Data.Equals(d2.Data) { 353 return false 354 } 355 return reflect.DeepEqual(d.Properties, d2.Properties) 356 } 357 358 func (d *Data) GetSyncedAnnotation() *annotation.Data { 359 for dataUUID := range d.SyncedData() { 360 source, err := annotation.GetByDataUUID(dataUUID) 361 if err == nil { 362 return source 363 } 364 dvid.Errorf("Got error accessing synced annotation %s: %v\n", dataUUID, err) 365 } 366 return nil 367 } 368 369 func (d *Data) inROI(pos dvid.Point3d) bool { 370 if d.StaticROI == "" { 371 return true // no ROI so ROI == everything 372 } 373 374 // Make sure we have immutable ROI if specified. 375 d.iMutex.Lock() 376 if !d.roiChecked { 377 d.roiChecked = true 378 iROI, err := roi.ImmutableBySpec(d.StaticROI) 379 if err != nil { 380 dvid.Errorf("could not load immutable ROI by spec %q: %v\n", d.StaticROI, err) 381 d.iMutex.Unlock() 382 return false 383 } 384 d.iROI = iROI 385 } 386 d.iMutex.Unlock() 387 388 if d.iROI == nil { 389 return false // ROI cannot be retrieved so use nothing; makes obvious failure since no ranks. 390 } 391 return d.iROI.VoxelWithin(pos) 392 } 393 394 // GetCountElementType returns a count of the given ElementType for a given label. 395 func (d *Data) GetCountElementType(ctx *datastore.VersionedCtx, label uint64, i IndexType) (uint32, error) { 396 if d.uninitialized { 397 return 0, fmt.Errorf("stats not available for labelsz %q at this time", d.DataName()) 398 } 399 400 store, err := datastore.GetOrderedKeyValueDB(d) 401 if err != nil { 402 return 0, err 403 } 404 405 val, err := store.Get(ctx, NewTypeLabelTKey(i, label)) 406 if err != nil { 407 return 0, err 408 } 409 if val == nil { 410 return 0, nil 411 } 412 if len(val) != 4 { 413 return 0, fmt.Errorf("bad size in value for index type %s, label %d: value has length %d", i, label, len(val)) 414 } 415 count := binary.LittleEndian.Uint32(val) 416 return count, nil 417 } 418 419 // SendCountsByElementType writes the counts for given index type for a list of labels 420 func (d *Data) SendCountsByElementType(w http.ResponseWriter, ctx *datastore.VersionedCtx, labels []uint64, idxType IndexType) error { 421 if d.uninitialized { 422 return fmt.Errorf("stats not available for labelsz %q at this time", d.DataName()) 423 } 424 425 store, err := datastore.GetOrderedKeyValueDB(d) 426 if err != nil { 427 return err 428 } 429 430 w.Header().Set("Content-type", "application/json") 431 if _, err := fmt.Fprintf(w, "["); err != nil { 432 return err 433 } 434 numLabels := len(labels) 435 for i, label := range labels { 436 val, err := store.Get(ctx, NewTypeLabelTKey(idxType, label)) 437 if err != nil { 438 dvid.Errorf("problem in GET for index type %s, label %d: %v", idxType, label, err) 439 continue 440 } 441 var count uint32 442 if val != nil { 443 if len(val) != 4 { 444 dvid.Errorf("bad value size %d for index type %s, label %d", len(val), idxType, label) 445 continue 446 } 447 count = binary.LittleEndian.Uint32(val) 448 } 449 if _, err := fmt.Fprintf(w, `{"Label":%d,%q:%d}`, label, idxType, count); err != nil { 450 continue 451 } 452 if i != numLabels-1 { 453 fmt.Fprintf(w, ",") 454 } 455 } 456 fmt.Fprintf(w, "]") 457 return nil 458 } 459 460 // GetTopElementType returns a sorted list of the top N labels that have the given ElementType. 461 func (d *Data) GetTopElementType(ctx *datastore.VersionedCtx, n int, i IndexType) (LabelSizes, error) { 462 if d.uninitialized { 463 return nil, fmt.Errorf("stats not available for labelsz %q at this time", d.DataName()) 464 } 465 466 if n < 0 { 467 return nil, fmt.Errorf("bad N (%d) in top request", n) 468 } 469 if n == 0 { 470 return LabelSizes{}, nil 471 } 472 473 store, err := datastore.GetOrderedKeyValueDB(d) 474 if err != nil { 475 return nil, err 476 } 477 478 // Setup key range for iterating through keys of this ElementType. 479 begTKey := NewTypeSizeLabelTKey(i, math.MaxUint32-1, 0) 480 endTKey := NewTypeSizeLabelTKey(i, 0, math.MaxUint64) 481 482 // Iterate through the first N kv then abort. 483 shortCircuitErr := fmt.Errorf("Found data, aborting.") 484 lsz := make(LabelSizes, n) 485 rank := 0 486 err = store.ProcessRange(ctx, begTKey, endTKey, nil, func(chunk *storage.Chunk) error { 487 idxType, sz, label, err := DecodeTypeSizeLabelTKey(chunk.K) 488 if err != nil { 489 return err 490 } 491 if idxType != i { 492 return fmt.Errorf("bad iteration of keys: expected index type %s, got %s", i, idxType) 493 } 494 lsz[rank] = LabelSize{Label: label, Size: sz} 495 rank++ 496 if rank >= n { 497 return shortCircuitErr 498 } 499 return nil 500 }) 501 if err != shortCircuitErr && err != nil { 502 return nil, err 503 } 504 return lsz[:rank], nil 505 } 506 507 // GetLabelsByThreshold returns a sorted list of labels that meet the given minSize threshold. 508 // We allow a maximum of MaxLabelsReturned returned labels and start with rank "offset". 509 func (d *Data) GetLabelsByThreshold(ctx *datastore.VersionedCtx, i IndexType, minSize uint32, offset, num int) (LabelSizes, error) { 510 if d.uninitialized { 511 return nil, fmt.Errorf("stats not available for labelsz %q at this time", d.DataName()) 512 } 513 514 var nReturns int 515 if num == 0 { 516 nReturns = MaxLabelsReturned 517 } else if num < 0 { 518 return nil, fmt.Errorf("bad number of requested labels (%d)", num) 519 } else { 520 nReturns = num 521 } 522 523 store, err := datastore.GetOrderedKeyValueDB(d) 524 if err != nil { 525 return nil, err 526 } 527 528 // Setup key range for iterating through keys of this ElementType. 529 begTKey := NewTypeSizeLabelTKey(i, math.MaxUint32-1, 0) 530 endTKey := NewTypeSizeLabelTKey(i, 0, math.MaxUint64) 531 532 // Iterate through sorted size list until we get what we need. 533 shortCircuitErr := fmt.Errorf("Found data, aborting.") 534 lsz := make(LabelSizes, nReturns) 535 rank := 0 536 saved := 0 537 err = store.ProcessRange(ctx, begTKey, endTKey, nil, func(chunk *storage.Chunk) error { 538 idxType, sz, label, err := DecodeTypeSizeLabelTKey(chunk.K) 539 if err != nil { 540 return err 541 } 542 if idxType != i { 543 return fmt.Errorf("bad iteration of keys: expected index type %s, got %s", i, idxType) 544 } 545 if sz < minSize { 546 return shortCircuitErr 547 } 548 if rank >= offset && rank < offset+nReturns { 549 lsz[saved] = LabelSize{Label: label, Size: sz} 550 saved++ 551 if saved == nReturns { 552 return shortCircuitErr 553 } 554 } 555 rank++ 556 return nil 557 }) 558 if err != shortCircuitErr && err != nil { 559 return nil, err 560 } 561 return lsz[:saved], nil 562 } 563 564 // GetByUUIDName returns a pointer to annotation data given a version (UUID) and data name. 565 func GetByUUIDName(uuid dvid.UUID, name dvid.InstanceName) (*Data, error) { 566 source, err := datastore.GetDataByUUIDName(uuid, name) 567 if err != nil { 568 return nil, err 569 } 570 data, ok := source.(*Data) 571 if !ok { 572 return nil, fmt.Errorf("Instance '%s' is not a labelsz datatype!", name) 573 } 574 return data, nil 575 } 576 577 // --- datastore.DataService interface --------- 578 579 func (d *Data) Help() string { 580 return helpMessage 581 } 582 583 func (d *Data) MarshalJSON() ([]byte, error) { 584 return json.Marshal(struct { 585 Base *datastore.Data 586 Extended Properties 587 }{ 588 d.Data, 589 d.Properties, 590 }) 591 } 592 593 func (d *Data) GobDecode(b []byte) error { 594 buf := bytes.NewBuffer(b) 595 dec := gob.NewDecoder(buf) 596 if err := dec.Decode(&(d.Data)); err != nil { 597 return err 598 } 599 if err := dec.Decode(&(d.Properties)); err != nil { 600 return err 601 } 602 return nil 603 } 604 605 func (d *Data) GobEncode() ([]byte, error) { 606 var buf bytes.Buffer 607 enc := gob.NewEncoder(&buf) 608 if err := enc.Encode(d.Data); err != nil { 609 return nil, err 610 } 611 if err := enc.Encode(d.Properties); err != nil { 612 return nil, err 613 } 614 return buf.Bytes(), nil 615 } 616 617 // DoRPC acts as a switchboard for RPC commands. 618 func (d *Data) DoRPC(request datastore.Request, reply *datastore.Response) error { 619 switch request.TypeCommand() { 620 default: 621 return fmt.Errorf("Unknown command. Data type '%s' [%s] does not support '%s' command.", 622 d.DataName(), d.TypeName(), request.TypeCommand()) 623 } 624 } 625 626 // ServeHTTP handles all incoming HTTP requests for this data. 627 func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) (activity map[string]interface{}) { 628 timedLog := dvid.NewTimeLog() 629 630 // Get the action (GET, POST) 631 action := strings.ToLower(r.Method) 632 633 // Break URL request into arguments 634 url := r.URL.Path[len(server.WebAPIPath):] 635 parts := strings.Split(url, "/") 636 if len(parts[len(parts)-1]) == 0 { 637 parts = parts[:len(parts)-1] 638 } 639 640 // Handle POST on data -> setting of configuration 641 if len(parts) == 3 && action == "put" { 642 config, err := server.DecodeJSON(r) 643 if err != nil { 644 server.BadRequest(w, r, err) 645 return 646 } 647 if err := d.ModifyConfig(config); err != nil { 648 server.BadRequest(w, r, err) 649 return 650 } 651 if err := datastore.SaveDataByUUID(uuid, d); err != nil { 652 server.BadRequest(w, r, err) 653 return 654 } 655 fmt.Fprintf(w, "Changed '%s' based on received configuration:\n%s\n", d.DataName(), config) 656 return 657 } 658 659 if len(parts) < 4 { 660 server.BadRequest(w, r, "Incomplete API request") 661 return 662 } 663 664 // Process help and info. 665 switch parts[3] { 666 case "help": 667 w.Header().Set("Content-Type", "text/plain") 668 fmt.Fprintln(w, dtype.Help()) 669 670 case "info": 671 jsonBytes, err := d.MarshalJSON() 672 if err != nil { 673 server.BadRequest(w, r, err) 674 return 675 } 676 w.Header().Set("Content-Type", "application/json") 677 fmt.Fprintf(w, string(jsonBytes)) 678 679 case "sync": 680 if action != "post" { 681 server.BadRequest(w, r, "Only POST allowed to sync endpoint") 682 return 683 } 684 replace := r.URL.Query().Get("replace") == "true" 685 if err := datastore.SetSyncByJSON(d, uuid, replace, r.Body); err != nil { 686 server.BadRequest(w, r, err) 687 return 688 } 689 690 case "count": 691 if action != "get" { 692 server.BadRequest(w, r, "Only GET action is available on 'count' endpoint.") 693 return 694 } 695 if len(parts) < 6 { 696 server.BadRequest(w, r, "Must include label and element type after 'count' endpoint.") 697 return 698 } 699 label, err := strconv.ParseUint(parts[4], 10, 64) 700 if err != nil { 701 server.BadRequest(w, r, err) 702 return 703 } 704 idxType := StringToIndexType(parts[5]) 705 if idxType == UnknownIndex { 706 server.BadRequest(w, r, fmt.Errorf("unknown index type specified (%q)", parts[5])) 707 return 708 } 709 count, err := d.GetCountElementType(ctx, label, idxType) 710 if err != nil { 711 server.BadRequest(w, r, err) 712 return 713 } 714 w.Header().Set("Content-type", "application/json") 715 jsonStr := fmt.Sprintf(`{"Label":%d,%q:%d}`, label, idxType, count) 716 if _, err := io.WriteString(w, jsonStr); err != nil { 717 server.BadRequest(w, r, err) 718 return 719 } 720 timedLog.Infof("HTTP %s: get count for label %d, index type %s: %s", r.Method, label, idxType, r.URL) 721 722 case "counts": 723 if action != "get" { 724 server.BadRequest(w, r, "Only GET action is available on 'counts' endpoint.") 725 return 726 } 727 data, err := ioutil.ReadAll(r.Body) 728 if err != nil { 729 server.BadRequest(w, r, "Bad GET request body for counts query: %v", err) 730 return 731 } 732 if len(parts) < 5 { 733 server.BadRequest(w, r, "Must include element type after 'counts' endpoint.") 734 return 735 } 736 idxType := StringToIndexType(parts[4]) 737 if idxType == UnknownIndex { 738 server.BadRequest(w, r, fmt.Errorf("unknown index type specified (%q)", parts[4])) 739 return 740 } 741 var labels []uint64 742 if err := json.Unmarshal(data, &labels); err != nil { 743 server.BadRequest(w, r, fmt.Sprintf("Bad JSON label array sent in 'counts' query: %v", err)) 744 return 745 } 746 if err := d.SendCountsByElementType(w, ctx, labels, idxType); err != nil { 747 server.BadRequest(w, r, err) 748 return 749 } 750 timedLog.Infof("HTTP GET counts query of %d labels (%s)", len(labels), r.URL) 751 752 case "top": 753 if action != "get" { 754 server.BadRequest(w, r, "Only GET action is available on 'top' endpoint.") 755 return 756 } 757 if len(parts) < 6 { 758 server.BadRequest(w, r, "Must include N and element type after 'top' endpoint.") 759 return 760 } 761 n, err := strconv.ParseUint(parts[4], 10, 32) 762 if err != nil { 763 server.BadRequest(w, r, err) 764 return 765 } 766 i := StringToIndexType(parts[5]) 767 if i == UnknownIndex { 768 server.BadRequest(w, r, fmt.Errorf("unknown index type specified (%q)", parts[5])) 769 return 770 } 771 labelSizes, err := d.GetTopElementType(ctx, int(n), i) 772 if err != nil { 773 server.BadRequest(w, r, err) 774 return 775 } 776 w.Header().Set("Content-type", "application/json") 777 jsonBytes, err := json.Marshal(labelSizes) 778 if err != nil { 779 server.BadRequest(w, r, err) 780 return 781 } 782 if _, err := w.Write(jsonBytes); err != nil { 783 server.BadRequest(w, r, err) 784 return 785 } 786 timedLog.Infof("HTTP %s: get top %d labels for index type %s: %s", r.Method, n, i, r.URL) 787 788 case "threshold": 789 if action != "get" { 790 server.BadRequest(w, r, "Only GET action is available on 'threshold' endpoint.") 791 return 792 } 793 if len(parts) < 6 { 794 server.BadRequest(w, r, "Must include threshold # and element type after 'threshold' endpoint.") 795 return 796 } 797 t, err := strconv.ParseUint(parts[4], 10, 32) 798 if err != nil { 799 server.BadRequest(w, r, err) 800 return 801 } 802 minSize := uint32(t) 803 i := StringToIndexType(parts[5]) 804 if i == UnknownIndex { 805 server.BadRequest(w, r, fmt.Errorf("unknown index type specified (%q)", parts[5])) 806 return 807 } 808 809 queryStrings := r.URL.Query() 810 var num, offset int 811 offsetStr := queryStrings.Get("offset") 812 if offsetStr != "" { 813 offset, err = strconv.Atoi(offsetStr) 814 if err != nil { 815 server.BadRequest(w, r, fmt.Errorf("bad offset specified in query string (%q)", offsetStr)) 816 return 817 } 818 } 819 numStr := queryStrings.Get("n") 820 if numStr != "" { 821 num, err = strconv.Atoi(numStr) 822 if err != nil { 823 server.BadRequest(w, r, fmt.Errorf("bad num specified in query string (%q)", numStr)) 824 return 825 } 826 } 827 828 labels, err := d.GetLabelsByThreshold(ctx, i, minSize, offset, num) 829 if err != nil { 830 server.BadRequest(w, r, err) 831 return 832 } 833 w.Header().Set("Content-type", "application/json") 834 jsonBytes, err := json.Marshal(labels) 835 if err != nil { 836 server.BadRequest(w, r, err) 837 return 838 } 839 if _, err := w.Write(jsonBytes); err != nil { 840 server.BadRequest(w, r, err) 841 return 842 } 843 timedLog.Infof("HTTP %s: get %d labels for index type %s with threshold %d: %s", r.Method, num, i, t, r.URL) 844 845 case "reload": 846 // POST <api URL>/node/<UUID>/<data name>/reload 847 if action != "post" { 848 server.BadRequest(w, r, "Only POST action is available on 'reload' endpoint.") 849 return 850 } 851 d.ReloadData(ctx) 852 853 default: 854 server.BadAPIRequest(w, r, d) 855 } 856 return 857 } 858 859 func (d *Data) ReloadData(ctx *datastore.VersionedCtx) { 860 go d.resync(ctx) 861 dvid.Infof("Started recalculation of labelsz %q...\n", d.DataName()) 862 } 863 864 // Get all labeled annotations from synced annotation instance and repopulate the labelsz. 865 func (d *Data) resync(ctx *datastore.VersionedCtx) { 866 timedLog := dvid.NewTimeLog() 867 868 annot := d.GetSyncedAnnotation() 869 if annot == nil { 870 dvid.Errorf("Unable to get synced annotation. Aborting reload of labelsz %q.\n", d.DataName()) 871 return 872 } 873 874 store, err := datastore.GetOrderedKeyValueDB(d) 875 if err != nil { 876 dvid.Errorf("Labelsz %q had error initializing store: %v\n", d.DataName(), err) 877 return 878 } 879 880 d.StartUpdate() 881 defer d.StopUpdate() 882 883 d.uninitialized = true 884 defer func() { 885 d.uninitialized = false 886 }() 887 888 minTSLTKey := storage.MinTKey(keyTypeSizeLabel) 889 maxTSLTKey := storage.MaxTKey(keyTypeSizeLabel) 890 if err := store.DeleteRange(ctx, minTSLTKey, maxTSLTKey); err != nil { 891 dvid.Errorf("Unable to delete type-size-label denormalization for labelsz %q: %v\n", d.DataName(), err) 892 return 893 } 894 895 minTypeTKey := storage.MinTKey(keyTypeLabel) 896 maxTypeTKey := storage.MaxTKey(keyTypeLabel) 897 if err := store.DeleteRange(ctx, minTypeTKey, maxTypeTKey); err != nil { 898 dvid.Errorf("Unable to delete type-label denormalization for labelsz %q: %v\n", d.DataName(), err) 899 return 900 } 901 timedLog.Infof("Completed deletion of labelsz %q indices. Now regenerating.", d.DataName()) 902 903 // launch goroutines to write label stats 904 var writerWG sync.WaitGroup 905 writerCh := make(chan *storage.TKeyValue, 1000) 906 for i := 0; i < 4; i++ { 907 writerWG.Add(1) 908 go func() { 909 for tkv := range writerCh { 910 if tkv == nil { 911 return 912 } 913 if err := store.Put(ctx, tkv.K, tkv.V); err != nil { 914 dvid.Errorf("Unable to write label stat for labelsz %q: %v\n", d.DataName(), err) 915 } 916 } 917 writerWG.Done() 918 }() 919 } 920 921 // interate through all label annotations and pass to writer 922 var totLabels uint64 923 err = annot.ProcessLabelAnnotations(ctx.VersionID(), func(label uint64, elems annotation.ElementsNR) { 924 var indexMap [AllSyn]uint32 925 926 for _, elem := range elems { 927 if d.inROI(elem.Pos) { 928 indexMap[elementToIndexType(elem.Kind)]++ 929 } 930 } 931 var allsyn uint32 932 for i := IndexType(0); i < AllSyn; i++ { 933 if indexMap[i] > 0 { 934 buf := make([]byte, 4) 935 binary.LittleEndian.PutUint32(buf, indexMap[i]) 936 writerCh <- &storage.TKeyValue{K: NewTypeLabelTKey(i, label), V: buf} 937 writerCh <- &storage.TKeyValue{K: NewTypeSizeLabelTKey(i, indexMap[i], label)} 938 allsyn += indexMap[i] 939 } 940 } 941 buf := make([]byte, 4) 942 binary.LittleEndian.PutUint32(buf, allsyn) 943 writerCh <- &storage.TKeyValue{K: NewTypeLabelTKey(AllSyn, label), V: buf} 944 writerCh <- &storage.TKeyValue{K: NewTypeSizeLabelTKey(AllSyn, allsyn, label)} 945 946 totLabels++ 947 if totLabels%10000 == 0 { 948 timedLog.Infof("Reloading labelsz %q: %d labels processed", d.DataName(), totLabels) 949 } 950 }) 951 if err != nil { 952 dvid.Errorf("Error in reload of labelsz %q: %v\n", d.DataName(), err) 953 } 954 close(writerCh) 955 writerWG.Wait() 956 957 timedLog.Infof("Completed labelsz %q reload of %d labels from annotation %q", d.DataName(), totLabels, annot.DataName()) 958 }