-
Notifications
You must be signed in to change notification settings - Fork 110
/
pointcloud.go
211 lines (187 loc) · 5.84 KB
/
pointcloud.go
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
// Package pointcloud defines a point cloud and provides an implementation for one.
//
// Its implementation is dictionary based is not yet efficient. The current focus is
// to make it useful and as such the API is experimental and subject to change
// considerably.
package pointcloud
import (
"math"
"sync"
"github.com/golang/geo/r3"
"go.viam.com/utils"
"gonum.org/v1/gonum/mat"
)
const numThreadsPointCloud = 8
// MetaData is data about what's stored in the point cloud.
type MetaData struct {
HasColor bool
HasValue bool
MinX, MaxX float64
MinY, MaxY float64
MinZ, MaxZ float64
totalX, totalY, totalZ float64
}
// PointCloud is a general purpose container of points. It does not
// dictate whether or not the cloud is sparse or dense. The current
// basic implementation is sparse however.
type PointCloud interface {
// Size returns the number of points in the cloud.
Size() int
// MetaData returns meta data
MetaData() MetaData
// Set places the given point in the cloud.
Set(p r3.Vector, d Data) error
// At returns the point in the cloud at the given position.
// The 2nd return is if the point exists, the first is data if any.
At(x, y, z float64) (Data, bool)
// Iterate iterates over all points in the cloud and calls the given
// function for each point. If the supplied function returns false,
// iteration will stop after the function returns.
// numBatches lets you divide up he work. 0 means don't divide
// myBatch is used iff numBatches > 0 and is which batch you want
Iterate(numBatches, myBatch int, fn func(p r3.Vector, d Data) bool)
}
// NewMetaData creates a new MetaData.
func NewMetaData() MetaData {
return MetaData{
MinX: math.MaxFloat64,
MinY: math.MaxFloat64,
MinZ: math.MaxFloat64,
MaxX: -math.MaxFloat64,
MaxY: -math.MaxFloat64,
MaxZ: -math.MaxFloat64,
totalX: 0,
totalY: 0,
totalZ: 0,
}
}
// TotalX returns the totalX stored in metadata.
func (meta *MetaData) TotalX() float64 {
return meta.totalX
}
// TotalY returns the totalY stored in metadata.
func (meta *MetaData) TotalY() float64 {
return meta.totalY
}
// TotalZ returns the totalZ stored in metadata.
func (meta *MetaData) TotalZ() float64 {
return meta.totalZ
}
// Merge updates the meta data with the new data.
func (meta *MetaData) Merge(v r3.Vector, data Data) {
if data != nil {
if data.HasColor() {
meta.HasColor = true
}
if data.HasValue() {
meta.HasValue = true
}
}
if v.X > meta.MaxX {
meta.MaxX = v.X
}
if v.Y > meta.MaxY {
meta.MaxY = v.Y
}
if v.Z > meta.MaxZ {
meta.MaxZ = v.Z
}
if v.X < meta.MinX {
meta.MinX = v.X
}
if v.Y < meta.MinY {
meta.MinY = v.Y
}
if v.Z < meta.MinZ {
meta.MinZ = v.Z
}
// Add to totals for centroid calculation.
meta.totalX += v.X
meta.totalY += v.Y
meta.totalZ += v.Z
}
// CloudContains is a silly helper method.
func CloudContains(cloud PointCloud, x, y, z float64) bool {
_, got := cloud.At(x, y, z)
return got
}
// CloudCentroid returns the centroid of a pointcloud as a vector.
func CloudCentroid(pc PointCloud) r3.Vector {
if pc.Size() == 0 {
// This is done to match the centroids provided by GetObjectPointClouds.
// Returning {NaN, NaN, NaN} is probably more correct, but this matches
// Previous behavior.
return r3.Vector{}
}
return r3.Vector{
X: pc.MetaData().totalX / float64(pc.Size()),
Y: pc.MetaData().totalY / float64(pc.Size()),
Z: pc.MetaData().totalZ / float64(pc.Size()),
}
}
// CloudMatrixCol is a type that represents the columns of a CloudMatrix.
type CloudMatrixCol int
const (
// CloudMatrixColX is the x column in the cloud matrix.
CloudMatrixColX CloudMatrixCol = 0
// CloudMatrixColY is the y column in the cloud matrix.
CloudMatrixColY CloudMatrixCol = 1
// CloudMatrixColZ is the z column in the cloud matrix.
CloudMatrixColZ CloudMatrixCol = 2
// CloudMatrixColR is the r column in the cloud matrix.
CloudMatrixColR CloudMatrixCol = 3
// CloudMatrixColG is the g column in the cloud matrix.
CloudMatrixColG CloudMatrixCol = 4
// CloudMatrixColB is the b column in the cloud matrix.
CloudMatrixColB CloudMatrixCol = 5
// CloudMatrixColV is the value column in the cloud matrix.
CloudMatrixColV CloudMatrixCol = 6
)
// CloudMatrix Returns a Matrix representation of a Cloud along with a Header list.
// The Header list is a list of CloudMatrixCols that correspond to the columns in the matrix.
// CloudMatrix is not guaranteed to return points in the same order as the cloud.
func CloudMatrix(pc PointCloud) (*mat.Dense, []CloudMatrixCol) {
if pc.Size() == 0 {
return nil, nil
}
header := []CloudMatrixCol{CloudMatrixColX, CloudMatrixColY, CloudMatrixColZ}
pointSize := 3 // x, y, z
if pc.MetaData().HasColor {
pointSize += 3 // color
header = append(header, CloudMatrixColR, CloudMatrixColG, CloudMatrixColB)
}
if pc.MetaData().HasValue {
pointSize++ // value
header = append(header, CloudMatrixColV)
}
var wg sync.WaitGroup
wg.Add(numThreadsPointCloud)
matChan := make(chan []float64, numThreadsPointCloud)
for thread := 0; thread < numThreadsPointCloud; thread++ {
f := func(thread int) {
defer wg.Done()
batchSize := (pc.Size() + numThreadsPointCloud - 1) / numThreadsPointCloud
buf := make([]float64, 0, pointSize*batchSize)
pc.Iterate(numThreadsPointCloud, thread, func(p r3.Vector, d Data) bool {
buf = append(buf, p.X, p.Y, p.Z)
if pc.MetaData().HasColor {
r, g, b := d.RGB255()
buf = append(buf, float64(r), float64(g), float64(b))
}
if pc.MetaData().HasValue {
buf = append(buf, float64(d.Value()))
}
return true
})
matChan <- buf
}
threadCopy := thread
utils.PanicCapturingGo(func() { f(threadCopy) })
}
wg.Wait()
matData := make([]float64, 0, pc.Size()*pointSize)
for i := 0; i < numThreadsPointCloud; i++ {
matData = append(matData, <-matChan...)
}
return mat.NewDense(pc.Size(), pointSize, matData), header
}