-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy pathlabelvol.go
More file actions
2238 lines (1945 loc) · 67.2 KB
/
labelvol.go
File metadata and controls
2238 lines (1945 loc) · 67.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Package labelvol supports label-specific sparse volumes. It can be synced
with labelblk and is a different view of 64-bit label data.
*/
package labelvol
import (
"bytes"
"compress/gzip"
"encoding/binary"
"encoding/gob"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
humanize "github.com/dustin/go-humanize"
"github.com/janelia-flyem/dvid/datastore"
"github.com/janelia-flyem/dvid/datatype/common/labels"
"github.com/janelia-flyem/dvid/datatype/labelblk"
"github.com/janelia-flyem/dvid/dvid"
"github.com/janelia-flyem/dvid/server"
"github.com/janelia-flyem/dvid/storage"
lz4 "github.com/janelia-flyem/go/golz4-updated"
)
const (
Version = "0.1"
RepoURL = "github.com/janelia-flyem/dvid/datatype/labelvol"
TypeName = "labelvol"
)
const helpMessage = `
API for label sparse volume data type (github.com/janelia-flyem/dvid/datatype/labelvol)
=======================================================================================
Note: UUIDs referenced below are strings that may either be a unique prefix of a
hexadecimal UUID string (e.g., 3FA22) or a branch leaf specification that adds
a colon (":") followed by the case-dependent branch name. In the case of a
branch leaf specification, the unique UUID prefix just identifies the repo of
the branch, and the UUID referenced is really the leaf of the branch name.
For example, if we have a DAG with root A -> B -> C where C is the current
HEAD or leaf of the "master" (default) branch, then asking for "B:master" is
the same as asking for "C". If we add another version so A -> B -> C -> D, then
references to "B:master" now return the data from "D".
-----
Denormalizations like sparse volumes are *not* performed for the "0" label, which is
considered a special label useful for designating background. This allows users to define
sparse labeled structures in a large volume without requiring processing of entire volume.
Command-line:
$ dvid repo <UUID> new labelvol <data name> <settings...>
Adds newly named data of the 'type name' to repo with specified UUID.
Example:
$ dvid repo 3f8c new labelvol sparsevols
Arguments:
UUID Hexadecimal string with enough characters to uniquely identify a version node.
data name Name of data to create, e.g., "sparsevols"
settings Configuration settings in "key=value" format separated by spaces.
Configuration Settings (case-insensitive keys)
BlockSize Size in pixels (default: %s)
VoxelSize Resolution of voxels (default: 8.0, 8.0, 8.0)
VoxelUnits Resolution units (default: "nanometers")
$ dvid node <UUID> <data name> dump <server-accessible directory>
Dumps files, one per 512^3 voxel subvolume, for all block RLEs for each label within the subvolume.
File names will be formatted as subvols-X_Y_Z.dat, where (X,Y,Z) is the subvolume index; for example,
the file subvols-0_1_2.dat has all label block RLEs for the subvolume with smallest voxel coordinate
at (0, 512, 1024). The encoding has the following format where integers are little endian and the
order of data is exactly as specified below:
uint64 Label ID
uint32 # Spans
Repeating unit of:
int32 Coordinate of run start (dimension 0)
int32 Coordinate of run start (dimension 1)
int32 Coordinate of run start (dimension 2)
int32 Length of run
...
This is similar to the voxel RLEs returned by the sparsevol endpoint, except the initial 8 byte header
is replaced with a label identifier. Spans are always in X direction.
------------------
HTTP API (Level 2 REST):
GET <api URL>/node/<UUID>/<data name>/help
Returns data-specific help message.
GET <api URL>/node/<UUID>/<data name>/info
POST <api URL>/node/<UUID>/<data name>/info
Retrieves or puts DVID-specific data properties for these voxels.
Example:
GET <api URL>/node/3f8c/bodies/info
Returns JSON with configuration settings that include location in DVID space and
min/max block indices.
Arguments:
UUID Hexadecimal string with enough characters to uniquely identify a version node.
data name Name of labelvol data.
POST <api URL>/node/<UUID>/<data name>/sync?<options>
Establishes labelblk data instances with which the annotations are synced. Expects JSON to be POSTed
with the following format:
{ "sync": "labels" }
To delete syncs, pass an empty string of names with query string "replace=true":
{ "sync": "" }
The "sync" property should be followed by a comma-delimited list of data instances that MUST
already exist. Currently, syncs should be created before any annotations are pushed to
the server. If annotations already exist, these are currently not synced.
The labelvol data type only accepts syncs to labelblk data instances.
GET Query-string Options:
replace Set to "true" if you want passed syncs to replace and not be appended to current syncs.
Default operation is false.
GET <api URL>/node/<UUID>/<data name>/sparsevol/<label>?<options>
Returns a sparse volume with voxels of the given label in encoded RLE format. The returned
data can be optionally compressed using the "compression" option below.
The encoding has the following format where integers are little endian and the order
of data is exactly as specified below:
byte Payload descriptor:
Bit 0 (LSB) - 8-bit grayscale
Bit 1 - 16-bit grayscale
Bit 2 - 16-bit normal
If set to all 0, there is no payload and it's a binary sparse volume.
uint8 Number of dimensions
uint8 Dimension of run (typically 0 = X)
byte Reserved (to be used later)
uint32 # Voxels [TODO. 0 for now]
uint32 # Spans
Repeating unit of:
int32 Coordinate of run start (dimension 0)
int32 Coordinate of run start (dimension 1)
int32 Coordinate of run start (dimension 2)
int32 Length of run
bytes Optional payload dependent on first byte descriptor
...
GET Query-string Options:
minx Spans must be equal to or larger than this minimum x voxel coordinate.
maxx Spans must be equal to or smaller than this maximum x voxel coordinate.
miny Spans must be equal to or larger than this minimum y voxel coordinate.
maxy Spans must be equal to or smaller than this maximum y voxel coordinate.
minz Spans must be equal to or larger than this minimum z voxel coordinate.
maxz Spans must be equal to or smaller than this maximum z voxel coordinate.
exact "false" if RLEs can extend a bit outside voxel bounds within border blocks.
This will give slightly faster responses.
compression Allows retrieval of data in "lz4" and "gzip"
compressed format.
HEAD <api URL>/node/<UUID>/<data name>/sparsevol/<label>?<options>
Returns:
200 (OK) if a sparse volume of the given label exists within any optional bounds.
204 (No Content) if there is no sparse volume for the given label within any optional bounds.
404 (Not found) if the label has no sparse volume.
Note that for speed, the optional bounds are always expanded to the block-aligned containing
subvolume, i.e., it's as if exact=false for the corresponding GET.
GET Query-string Options:
minx Spans must be equal to or larger than this minimum x voxel coordinate.
maxx Spans must be equal to or smaller than this maximum x voxel coordinate.
miny Spans must be equal to or larger than this minimum y voxel coordinate.
maxy Spans must be equal to or smaller than this maximum y voxel coordinate.
minz Spans must be equal to or larger than this minimum z voxel coordinate.
maxz Spans must be equal to or smaller than this maximum z voxel coordinate.
GET <api URL>/node/<UUID>/<data name>/sparsevol-by-point/<coord>
Returns a sparse volume with voxels that pass through a given voxel.
The encoding is described in the "sparsevol" request above.
Arguments:
UUID Hexadecimal string with enough characters to uniquely identify a version node.
data name Name of mapping data.
coord Coordinate of voxel with underscore as separator, e.g., 10_20_30
GET <api URL>/node/<UUID>/<data name>/sparsevol-coarse/<label>
Returns a sparse volume with blocks of the given label in encoded RLE format.
The encoding has the following format where integers are little endian and the order
of data is exactly as specified below:
byte Set to 0
uint8 Number of dimensions
uint8 Dimension of run (typically 0 = X)
byte Reserved (to be used later)
uint32 # Blocks [TODO. 0 for now]
uint32 # Spans
Repeating unit of:
int32 Block coordinate of run start (dimension 0)
int32 Block coordinate of run start (dimension 1)
int32 Block coordinate of run start (dimension 2)
...
int32 Length of run
Note that the above format is the RLE encoding of sparsevol, where voxel coordinates
have been replaced by block coordinates.
GET <api URL>/node/<UUID>/<data name>/maxlabel
GET returns the maximum label for the version of data in JSON form:
{ "maxlabel": <label #> }
GET <api URL>/node/<UUID>/<data name>/nextlabel
POST <api URL>/node/<UUID>/<data name>/nextlabel
GET returns the next label for the version of data in JSON form:
{ "nextlabel": <label #> }
POST allows the client to request some # of labels that will be reserved.
This is used if the client wants to introduce new labels.
The request:
{ "needed": <# of labels> }
Response:
{ "start": <starting label #>, "end": <ending label #> }
POST <api URL>/node/<UUID>/<data name>/merge
Merges labels. Requires JSON in request body using the following format:
[toLabel1, fromLabel1, fromLabel2, fromLabel3, ...]
The first element of the JSON array specifies the label to be used as the merge result.
Note that it's computationally more efficient to group a number of merges into the
same toLabel as a single merge request instead of multiple merge requests.
POST <api URL>/node/<UUID>/<data name>/split/<label>[?splitlabel=X]
Splits a portion of a label's voxels into a new label or, if "splitlabel" is specified
as an optional query string, the given split label. Returns the following JSON:
{ "label": <new label> }
This request requires a binary sparse volume in the POSTed body with the following
encoded RLE format, which is compatible with the format returned by a GET on the
"sparsevol" endpoint described above:
All integers are in little-endian format.
byte Payload descriptor:
Set to 0 to indicate it's a binary sparse volume.
uint8 Number of dimensions
uint8 Dimension of run (typically 0 = X)
byte Reserved (to be used later)
uint32 # Voxels [TODO. 0 for now]
uint32 # Spans
Repeating unit of:
int32 Coordinate of run start (dimension 0)
int32 Coordinate of run start (dimension 1)
int32 Coordinate of run start (dimension 2)
...
int32 Length of run
NOTE 1: The POSTed split sparse volume must be a subset of the given label's voxels. You cannot
give an arbitrary sparse volume that may span multiple labels.
NOTE 2: If a split label is specified, it is the client's responsibility to make sure the given
label will not create conflict with labels in other versions. It should primarily be used in
chain operations like "split-coarse" followed by "split" using voxels, where the new label
created by the split coarse is used as the split label for the smaller, higher-res "split".
POST <api URL>/node/<UUID>/<data name>/split-coarse/<label>[?splitlabel=X]
Splits a portion of a label's blocks into a new label or, if "splitlabel" is specified
as an optional query string, the given split label. Returns the following JSON:
{ "label": <new label> }
This request requires a binary sparse volume in the POSTed body with the following
encoded RLE format, which is similar to the "split" request format but uses block
instead of voxel coordinates:
All integers are in little-endian format.
byte Payload descriptor:
Set to 0 to indicate it's a binary sparse volume.
uint8 Number of dimensions
uint8 Dimension of run (typically 0 = X)
byte Reserved (to be used later)
uint32 # Blocks [TODO. 0 for now]
uint32 # Spans
Repeating unit of:
int32 Coordinate of run start (dimension 0)
int32 Coordinate of run start (dimension 1)
int32 Coordinate of run start (dimension 2)
...
int32 Length of run
The Notes for "split" endpoint above are applicable to this "split-coarse" endpoint.
POST <api URL>/node/<UUID>/<data name>/resync/<label>
Regenerates the sparse volume for the given label from its synced labelblk.
This is used to repair databases that have been inadvertently shutdown or
crashed while undergoing merges/splits.
This request requires a binary sparse volume in the POSTed body with the following
encoded RLE format, which is similar to the "split" request format but uses block
instead of voxel coordinates:
All integers are in little-endian format.
byte Payload descriptor:
Set to 0 to indicate it's a binary sparse volume.
uint8 Number of dimensions
uint8 Dimension of run (typically 0 = X)
byte Reserved (to be used later)
uint32 # Blocks [TODO. 0 for now]
uint32 # Spans
Repeating unit of:
int32 Coordinate of run start (dimension 0)
int32 Coordinate of run start (dimension 1)
int32 Coordinate of run start (dimension 2)
...
int32 Length of run
DELETE <api URL>/node/<UUID>/<data name>/area/<label>/<size>/<offset>
NOTE: Does not honor syncs and is intended purely for low-level mods.
Deletes all of a label's sparse volume within a given 3d volume from a 3d offset.
Example:
DELETE <api URL>/node/3f8c/bodies/area/23/512_512_512/0_0_128
The above example deletes all sparsevol associated with label 23 in subvolume
that is 512 x 512 x 512 starting at offset (0,0,128).
`
var (
dtype *Type
DefaultBlockSize int32 = labelblk.DefaultBlockSize
DefaultRes float32 = labelblk.DefaultRes
DefaultUnits = labelblk.DefaultUnits
)
func init() {
dtype = new(Type)
dtype.Type = datastore.Type{
Name: TypeName,
URL: RepoURL,
Version: Version,
Requirements: &storage.Requirements{
Batcher: true,
},
}
// See doc for package on why channels are segregated instead of interleaved.
// Data types must be registered with the datastore to be used.
datastore.Register(dtype)
// Need to register types that will be used to fulfill interfaces.
gob.Register(&Type{})
gob.Register(&Data{})
}
// NewData returns a pointer to labelvol data.
func NewData(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (*Data, error) {
// Initialize the Data for this data type
basedata, err := datastore.NewDataService(dtype, uuid, id, name, c)
if err != nil {
return nil, err
}
props := new(Properties)
props.setDefault()
if err := props.setByConfig(c); err != nil {
return nil, err
}
data := &Data{
Data: basedata,
Properties: *props,
}
data.Properties.MaxLabel = make(map[dvid.VersionID]uint64)
return data, nil
}
// --- Labelvol Datatype -----
type Type struct {
datastore.Type
}
// --- TypeService interface ---
func (dtype *Type) NewDataService(uuid dvid.UUID, id dvid.InstanceID, name dvid.InstanceName, c dvid.Config) (datastore.DataService, error) {
return NewData(uuid, id, name, c)
}
func (dtype *Type) Help() string {
return helpMessage
}
// Properties are additional properties for data beyond those in standard datastore.Data.
type Properties struct {
Resolution dvid.Resolution
// Block size for this repo
BlockSize dvid.Point3d
mlMu sync.RWMutex // For atomic access of MaxLabel and MaxRepoLabel
// The maximum label id found in each version of this instance.
// Can be unset if no new label was added at that version, in which case
// you must traverse DAG to find max label of parent.
MaxLabel map[dvid.VersionID]uint64
// The maximum label for this instance in the entire repo. This allows us to do
// conflict-free merges without any relabeling. Note that relabeling (rebasing)
// is required if we move data between repos, e.g., when pushing remote nodes,
// since we have no control over which labels were created remotely and there
// could be conflicts between the local and remote repos. When mutations only
// occur within a single repo, however, this atomic label allows us to prevent
// conflict across all versions within this repo.
MaxRepoLabel uint64
}
// CopyPropertiesFrom copies the data instance-specific properties from a given
// data instance into the receiver's properties. Fulfills the datastore.PropertyCopier interface.
func (d *Data) CopyPropertiesFrom(src datastore.DataService, fs storage.FilterSpec) error {
d2, ok := src.(*Data)
if !ok {
return fmt.Errorf("unable to copy properties from non-labelvol data %q", src.DataName())
}
d.Properties.copyImmutable(&(d2.Properties))
// TODO -- Handle mutable data that could be potentially altered by filter.
d.MaxLabel = make(map[dvid.VersionID]uint64, len(d2.MaxLabel))
for k, v := range d2.MaxLabel {
d.MaxLabel[k] = v
}
d.MaxRepoLabel = d2.MaxRepoLabel
return nil
}
func (p *Properties) copyImmutable(p2 *Properties) {
p.BlockSize = p2.BlockSize
p.Resolution.VoxelSize = make(dvid.NdFloat32, 3)
copy(p.Resolution.VoxelSize, p2.Resolution.VoxelSize)
p.Resolution.VoxelUnits = make(dvid.NdString, 3)
copy(p.Resolution.VoxelUnits, p2.Resolution.VoxelUnits)
}
func (p Properties) MarshalJSON() ([]byte, error) {
maxLabels := make(map[string]uint64)
for v, max := range p.MaxLabel {
uuid, err := datastore.UUIDFromVersion(v)
if err != nil {
return nil, err
}
maxLabels[string(uuid)] = max
}
return json.Marshal(struct {
dvid.Resolution
BlockSize dvid.Point3d
MaxLabel map[string]uint64
}{
p.Resolution,
p.BlockSize,
maxLabels,
})
}
func (p *Properties) setDefault() {
for d := 0; d < 3; d++ {
p.BlockSize[d] = DefaultBlockSize
}
p.Resolution.VoxelSize = make(dvid.NdFloat32, 3)
for d := 0; d < 3; d++ {
p.Resolution.VoxelSize[d] = DefaultRes
}
p.Resolution.VoxelUnits = make(dvid.NdString, 3)
for d := 0; d < 3; d++ {
p.Resolution.VoxelUnits[d] = DefaultUnits
}
}
func (p *Properties) setByConfig(config dvid.Config) error {
s, found, err := config.GetString("BlockSize")
if err != nil {
return err
}
if found {
p.BlockSize, err = dvid.StringToPoint3d(s, ",")
if err != nil {
return err
}
}
s, found, err = config.GetString("VoxelSize")
if err != nil {
return err
}
if found {
dvid.Infof("Changing resolution of voxels to %s\n", s)
p.Resolution.VoxelSize, err = dvid.StringToNdFloat32(s, ",")
if err != nil {
return err
}
}
s, found, err = config.GetString("VoxelUnits")
if err != nil {
return err
}
if found {
p.Resolution.VoxelUnits, err = dvid.StringToNdString(s, ",")
if err != nil {
return err
}
}
return nil
}
// Data instance of labelvol, label sparse volumes.
type Data struct {
*datastore.Data
Properties
// Keep track of sync operations that could be updating the data.
// TODO: Think about making this per label since sync status is pessimistic, assuming
// all labels are being updated.
datastore.Updater
// channels for mutations and downres caching.
syncCh chan datastore.SyncMessage
syncDone chan *sync.WaitGroup
}
// RemapVersions modifies internal data instance properties that rely
// on server-specific version ids. This is necessary after DVID-to-DVID
// transmission.
func (d *Data) RemapVersions(vmap dvid.VersionMap) error {
maxLabels := make(map[dvid.VersionID]uint64, len(d.Properties.MaxLabel))
for oldv, label := range d.Properties.MaxLabel {
newv, found := vmap[oldv]
if !found {
dvid.Infof("No version %d in labelvol %q... discarding max label", oldv, d.DataName())
continue
}
maxLabels[newv] = label
}
d.Properties.MaxLabel = maxLabels
return nil
}
// GetSyncedLabelblk returns the synced labelblk data instance or returns
// an error if there is no synced labelblk.
func (d *Data) GetSyncedLabelblk() (*labelblk.Data, error) {
// Go through all synced names, and checking if there's a valid source.
for dataUUID := range d.SyncedData() {
source, err := labelblk.GetByDataUUID(dataUUID)
if err == nil {
return source, nil
}
}
return nil, fmt.Errorf("no labelblk data is syncing with %s", d.DataName())
}
// GetByDataUUID returns a pointer to labelvol data given a data UUID.
func GetByDataUUID(dataUUID dvid.UUID) (*Data, error) {
source, err := datastore.GetDataByDataUUID(dataUUID)
if err != nil {
return nil, err
}
data, ok := source.(*Data)
if !ok {
return nil, fmt.Errorf("Instance '%s' is not a labelvol datatype!", source.DataName())
}
return data, nil
}
// GetByUUIDName returns a pointer to labelvol data given a version (UUID) and data name.
func GetByUUIDName(uuid dvid.UUID, name dvid.InstanceName) (*Data, error) {
source, err := datastore.GetDataByUUIDName(uuid, name)
if err != nil {
return nil, err
}
data, ok := source.(*Data)
if !ok {
return nil, fmt.Errorf("Instance '%s' is not a labelvol datatype!", name)
}
return data, nil
}
// GetByVersionName returns a pointer to labelblk data given a UUID and data name.
func GetByVersionName(v dvid.VersionID, name dvid.InstanceName) (*Data, error) {
source, err := datastore.GetDataByVersionName(v, name)
if err != nil {
return nil, err
}
data, ok := source.(*Data)
if !ok {
return nil, fmt.Errorf("Instance '%s' is not a labelvol datatype!", name)
}
return data, nil
}
// --- datastore.InstanceMutator interface -----
// LoadMutable loads mutable properties of label volumes like the maximum labels
// for each version. Note that we load these max labels from key-value pairs
// rather than data instance properties persistence, because in the case of a crash,
// the actually stored repo data structure may be out-of-date compared to the guaranteed
// up-to-date key-value pairs for max labels.
func (d *Data) LoadMutable(root dvid.VersionID, storedVersion, expectedVersion uint64) (bool, error) {
ctx := storage.NewDataContext(d, 0)
store, err := datastore.GetOrderedKeyValueDB(d)
if err != nil {
return false, fmt.Errorf("Data type labelvol had error initializing store: %v\n", err)
}
wg := new(sync.WaitGroup)
wg.Add(1)
ch := make(chan *storage.KeyValue)
// Start appropriate migration function if any.
var saveRequired bool
switch storedVersion {
case 0:
// Need to update all max labels and set repo-level max label.
saveRequired = true
dvid.Infof("Migrating old version of labelvol %q to new version\n", d.DataName())
go d.migrateMaxLabels(root, wg, ch)
default:
// Load in each version max label without migration.
go d.loadMaxLabels(wg, ch)
}
// Send the max label data per version
minKey, err := ctx.MinVersionKey(maxLabelTKey)
if err != nil {
return false, err
}
maxKey, err := ctx.MaxVersionKey(maxLabelTKey)
if err != nil {
return false, err
}
keysOnly := false
if err = store.RawRangeQuery(minKey, maxKey, keysOnly, ch, nil); err != nil {
return false, err
}
wg.Wait()
dvid.Infof("Loaded max label values for labelvol %q with repo-wide max %d\n", d.DataName(), d.MaxRepoLabel)
return saveRequired, nil
}
func (d *Data) migrateMaxLabels(root dvid.VersionID, wg *sync.WaitGroup, ch chan *storage.KeyValue) {
ctx := storage.NewDataContext(d, 0)
store, err := datastore.GetOrderedKeyValueDB(d)
if err != nil {
dvid.Errorf("Can't initialize store for labelvol %q: %v\n", d.DataName(), err)
}
var maxRepoLabel uint64
d.MaxLabel = make(map[dvid.VersionID]uint64)
for {
kv := <-ch
if kv == nil {
break
}
v, err := ctx.VersionFromKey(kv.K)
if err != nil {
dvid.Errorf("Can't decode key when loading mutable data for %s", d.DataName())
continue
}
if len(kv.V) != 8 {
dvid.Errorf("Got bad value. Expected 64-bit label, got %v", kv.V)
continue
}
label := binary.LittleEndian.Uint64(kv.V)
d.MaxLabel[v] = label
if label > maxRepoLabel {
maxRepoLabel = label
}
}
// Adjust the MaxLabel data to make sure we correct for any case of child max < parent max.
d.adjustMaxLabels(store, root)
// Set the repo-wide max label.
d.MaxRepoLabel = maxRepoLabel
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, maxRepoLabel)
store.Put(ctx, maxRepoLabelTKey, buf)
wg.Done()
return
}
func (d *Data) adjustMaxLabels(store storage.KeyValueSetter, root dvid.VersionID) error {
buf := make([]byte, 8)
parentMax, ok := d.MaxLabel[root]
if !ok {
return fmt.Errorf("can't adjust version id %d since none exists in metadata", root)
}
childIDs, err := datastore.GetChildrenByVersion(root)
if err != nil {
return err
}
for _, childID := range childIDs {
var save bool
childMax, ok := d.MaxLabel[childID]
if !ok {
// set to parent max
d.MaxLabel[childID] = parentMax
save = true
} else if childMax < parentMax {
d.MaxLabel[childID] = parentMax + childMax + 1
save = true
}
// save the key-value
if save {
binary.LittleEndian.PutUint64(buf, d.MaxLabel[childID])
ctx := datastore.NewVersionedCtx(d, childID)
store.Put(ctx, maxLabelTKey, buf)
}
// recurse for depth-first
if err := d.adjustMaxLabels(store, childID); err != nil {
return err
}
}
return nil
}
func (d *Data) loadMaxLabels(wg *sync.WaitGroup, ch chan *storage.KeyValue) {
ctx := storage.NewDataContext(d, 0)
var repoMax uint64
d.MaxLabel = make(map[dvid.VersionID]uint64)
for {
kv := <-ch
if kv == nil {
break
}
v, err := ctx.VersionFromKey(kv.K)
if err != nil {
dvid.Errorf("Can't decode key when loading mutable data for %s", d.DataName())
continue
}
if len(kv.V) != 8 {
dvid.Errorf("Got bad value. Expected 64-bit label, got %v", kv.V)
continue
}
label := binary.LittleEndian.Uint64(kv.V)
d.MaxLabel[v] = label
if label > repoMax {
repoMax = label
}
}
// Load in the repo-wide max label.
store, err := datastore.GetOrderedKeyValueDB(d)
if err != nil {
dvid.Errorf("Data type labelvol had error initializing store: %v\n", err)
return
}
data, err := store.Get(ctx, maxRepoLabelTKey)
if err != nil {
dvid.Errorf("Error getting repo-wide max label: %v\n", err)
return
}
if data == nil || len(data) != 8 {
dvid.Errorf("Could not load repo-wide max label for instance %q. Only got %d bytes, not 64-bit label.\n", d.DataName(), len(data))
dvid.Errorf("Using max label across versions: %d\n", repoMax)
d.MaxRepoLabel = repoMax
} else {
d.MaxRepoLabel = binary.LittleEndian.Uint64(data)
if d.MaxRepoLabel < repoMax {
dvid.Errorf("Saved repo-wide max for instance %q was %d, changed to largest version max %d\n", d.DataName(), d.MaxRepoLabel, repoMax)
d.MaxRepoLabel = repoMax
}
}
wg.Done()
}
// --- datastore.DataService interface ---------
func (d *Data) Help() string {
return helpMessage
}
func (d *Data) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Base *datastore.Data
Extended Properties
}{
d.Data,
d.Properties,
})
}
func (d *Data) GobDecode(b []byte) error {
buf := bytes.NewBuffer(b)
dec := gob.NewDecoder(buf)
if err := dec.Decode(&(d.Data)); err != nil {
return err
}
if err := dec.Decode(&(d.Properties)); err != nil {
return err
}
return nil
}
func (d *Data) GobEncode() ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(d.Data); err != nil {
return nil, err
}
if err := enc.Encode(d.Properties); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// DoRPC acts as a switchboard for RPC commands.
func (d *Data) DoRPC(req datastore.Request, reply *datastore.Response) error {
switch req.TypeCommand() {
case "dump":
if len(req.Command) < 5 {
return fmt.Errorf("Poorly formatted dump command. See command-line help.")
}
// Parse the request
var uuidStr, dataName, cmdStr, dirStr string
req.CommandArgs(1, &uuidStr, &dataName, &cmdStr, &dirStr)
uuid, versionID, err := datastore.MatchingUUID(uuidStr)
if err != nil {
return err
}
go func() {
d.DumpSubvols(uuid, versionID, dirStr)
}()
reply.Text = fmt.Sprintf("Asynchronously dumping sparse volumes in directory: %s\n", dirStr)
default:
return fmt.Errorf("Unknown command. Data type '%s' [%s] does not support '%s' command.",
d.DataName(), d.TypeName(), req.TypeCommand())
}
return nil
}
// ServeHTTP handles all incoming HTTP requests for this data.
func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) (activity map[string]interface{}) {
timedLog := dvid.NewTimeLog()
versionID := ctx.VersionID()
// Get the action (GET, POST)
action := strings.ToLower(r.Method)
// Break URL request into arguments
url := r.URL.Path[len(server.WebAPIPath):]
parts := strings.Split(url, "/")
if len(parts[len(parts)-1]) == 0 {
parts = parts[:len(parts)-1]
}
// Handle POST on data -> setting of configuration
if len(parts) == 3 && action == "put" {
config, err := server.DecodeJSON(r)
if err != nil {
server.BadRequest(w, r, err)
return
}
if err := d.ModifyConfig(config); err != nil {
server.BadRequest(w, r, err)
return
}
if err := datastore.SaveDataByUUID(uuid, d); err != nil {
server.BadRequest(w, r, err)
return
}
fmt.Fprintf(w, "Changed '%s' based on received configuration:\n%s\n", d.DataName(), config)
return
}
if len(parts) < 4 {
server.BadRequest(w, r, "Incomplete API request")
return
}
// Process help and info.
switch parts[3] {
case "help":
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintln(w, dtype.Help())
case "info":
jsonBytes, err := d.MarshalJSON()
if err != nil {
server.BadRequest(w, r, err)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, string(jsonBytes))
case "sync":
if action != "post" {
server.BadRequest(w, r, "Only POST allowed to sync endpoint")
return
}
replace := r.URL.Query().Get("replace") == "true"
if err := datastore.SetSyncByJSON(d, uuid, replace, r.Body); err != nil {
server.BadRequest(w, r, err)
return
}
case "sparsevol":
// GET <api URL>/node/<UUID>/<data name>/sparsevol/<label>
// POST <api URL>/node/<UUID>/<data name>/sparsevol/<label>
// HEAD <api URL>/node/<UUID>/<data name>/sparsevol/<label>
if len(parts) < 5 {
server.BadRequest(w, r, "ERROR: DVID requires label ID to follow 'sparsevol' command")
return
}
label, err := strconv.ParseUint(parts[4], 10, 64)
if err != nil {
server.BadRequest(w, r, err)
return
}
if label == 0 {
server.BadRequest(w, r, "Label 0 is protected background value and cannot be used as sparse volume.\n")
return
}
queryStrings := r.URL.Query()
var b dvid.Bounds
b.Voxel, err = dvid.OptionalBoundsFromQueryString(r)
if err != nil {
server.BadRequest(w, r, "Error parsing bounds from query string: %v\n", err)
return
}
b.Block = b.Voxel.Divide(d.BlockSize)
b.Exact = true