Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ segmentation\_param `D` `R` | セグメンテーション時の分離距離を
fit\_inserting `AXIS`... | 貼り付け中の点群を既存の点群に位置合わせ [\*2](#footnoteKey2) (位置合わせを行う軸 `AXIS` をスペース区切りで複数指定 [\*3](#footnoteKey3))
label_segmentation\_param | ラベルを元にしてのセグメンテーション時の範囲と隣接する点群の最大距離を表示 [\*1](#footnoteKey1)
label_segmentation\_param `D` `R` | ラベルを元にしてのセグメンテーション時の隣接する点群の最大距離を `D` \[メートル\]、範囲を `R` \[メートル\]に設定
render_label_range\_param `Min` `Max` | `Min` - `Max`の範囲内のラベルのみに色をつけて表示
relabel\_param `Min` `Max` `New` | `Min` - `Max`の範囲内のラベルを`New`値に設定
unlabel\_param `label1` `label2` `...` | `label1, label2, ...`以外のラベルを`0`に設定

<dl>
<dt><a id="footnoteKey1">[1] 数値の表示</a></dt><dd>
Expand Down
24 changes: 24 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -1004,3 +1004,27 @@ func (c *commandContext) SelectLabelSegment(p mat.Vec3) error {
c.selectMode = selectModeMask
return nil
}

func (c *commandContext) RelabelPointsInLabelRange(minLabel, maxLabel, newLabel uint32) error {
err := c.editor.relabelPointsInLabelRange(minLabel, maxLabel, newLabel)
if err != nil {
return err
}

c.pointCloudUpdated = true
return nil
}

func (c *commandContext) UnlabelPoints(labelsToKeep []uint32) error {
if len(labelsToKeep) == 0 {
return nil
}

err := c.editor.unlabelPoints(labelsToKeep)
if err != nil {
return err
}

c.pointCloudUpdated = true
return nil
}
164 changes: 163 additions & 1 deletion command_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"reflect"
"testing"

"github.com/seqsense/pcgol/mat"
Expand Down Expand Up @@ -230,7 +231,7 @@ func (dummyPCDIO) exportPCD(pp *pc.PointCloud) (interface{}, error) {
return pp, nil
}

func TestBaseFileter(t *testing.T) {
func TestBaseFilter(t *testing.T) {
c := &commandContext{
selectMask: []uint32{
0,
Expand Down Expand Up @@ -316,3 +317,164 @@ func TestAddSurface(t *testing.T) {
}
})
}

func TestRelabelPointsInLabelRange(t *testing.T) {
header := pc.PointCloudHeader{
Fields: []string{"x", "y", "z", "label"},
Size: []int{4, 4, 4, 4},
Type: []string{"F", "F", "F", "U"},
Count: []int{1, 1, 1, 1},
Width: 4,
Height: 1,
}
pp := &pc.PointCloud{
PointCloudHeader: header,
Points: 4,
Data: make([]byte, 4*4*4),
}
it, err := pp.Vec3Iterator()
if err != nil {
t.Fatal(err)
}
it.SetVec3(mat.Vec3{1, 2, 3})
it.Incr()
it.SetVec3(mat.Vec3{4, 5, 6})
it.Incr()
it.SetVec3(mat.Vec3{7, 8, 9})
it.Incr()
it.SetVec3(mat.Vec3{10, 11, 12})

lt, err := pp.Uint32Iterator("label")
if err != nil {
t.Fatal(err)
}
lt.SetUint32(0)
lt.Incr()
lt.SetUint32(1)
lt.Incr()
lt.SetUint32(2)
lt.Incr()
lt.SetUint32(3)
lt.Incr()

c := newCommandContext(&dummyPCDIO{}, nil)

testCases := map[string]struct {
minLabel, maxLabel, newLabel uint32
expectedLabels []uint32
}{
"NoChange": {
minLabel: 4,
maxLabel: 10,
newLabel: 0,
expectedLabels: []uint32{0, 1, 2, 3},
},
"SetLabel1&2to0": {
minLabel: 1,
maxLabel: 2,
newLabel: 0,
expectedLabels: []uint32{0, 0, 0, 3},
},
}

for name, tt := range testCases {
tt := tt
t.Run(name, func(t *testing.T) {
c.SetPointCloud(pp, cloudMain)

if err := c.RelabelPointsInLabelRange(tt.minLabel, tt.maxLabel, tt.newLabel); err != nil {
t.Fatal(err)
}

lt, err = c.editor.pp.Uint32Iterator("label")
var labels []uint32
for ; lt.IsValid(); lt.Incr() {
labels = append(labels, lt.Uint32())
}
if !reflect.DeepEqual(tt.expectedLabels, labels) {
t.Errorf("Expected labels: %v, got: %v", tt.expectedLabels, labels)
}
})
}
}

func TestUnlabelPoints(t *testing.T) {
header := pc.PointCloudHeader{
Fields: []string{"x", "y", "z", "label"},
Size: []int{4, 4, 4, 4},
Type: []string{"F", "F", "F", "U"},
Count: []int{1, 1, 1, 1},
Width: 4,
Height: 1,
}
pp := &pc.PointCloud{
PointCloudHeader: header,
Points: 4,
Data: make([]byte, 4*4*4),
}
it, err := pp.Vec3Iterator()
if err != nil {
t.Fatal(err)
}
it.SetVec3(mat.Vec3{1, 2, 3})
it.Incr()
it.SetVec3(mat.Vec3{4, 5, 6})
it.Incr()
it.SetVec3(mat.Vec3{7, 8, 9})
it.Incr()
it.SetVec3(mat.Vec3{10, 11, 12})

lt, err := pp.Uint32Iterator("label")
if err != nil {
t.Fatal(err)
}
lt.SetUint32(0)
lt.Incr()
lt.SetUint32(1)
lt.Incr()
lt.SetUint32(2)
lt.Incr()
lt.SetUint32(3)
lt.Incr()

c := newCommandContext(&dummyPCDIO{}, nil)
c.SetPointCloud(pp, cloudMain)

testCases := map[string]struct {
labelsToKeep []uint32
expectedLabels []uint32
}{
"NoChangeEmptyList": {
labelsToKeep: []uint32{},
expectedLabels: []uint32{0, 1, 2, 3},
},
"NoChange": {
labelsToKeep: []uint32{0, 1, 2, 3},
expectedLabels: []uint32{0, 1, 2, 3},
},
"Unlabel1&2": {
labelsToKeep: []uint32{0, 3},
expectedLabels: []uint32{0, 0, 0, 3},
},
}

for name, tt := range testCases {
tt := tt
t.Run(name, func(t *testing.T) {
c.SetPointCloud(pp, cloudMain)

if err := c.UnlabelPoints(tt.labelsToKeep); err != nil {
t.Fatal(err)
}

lt, err = c.editor.pp.Uint32Iterator("label")
var labels []uint32
for ; lt.IsValid(); lt.Incr() {
labels = append(labels, lt.Uint32())
}
if !reflect.DeepEqual(tt.expectedLabels, labels) {
t.Errorf("Expected labels: %v, got: %v", tt.expectedLabels, labels)
}
})
}
}
13 changes: 13 additions & 0 deletions console.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,19 @@ var consoleCommands = map[string]func(c *console, updateSel updateSelectionFn, a
return nil, errArgumentNumber
}
},
"relabel": func(c *console, updateSel updateSelectionFn, args []float32) ([][]float32, error) {
if len(args) < 3 {
return nil, errArgumentNumber
}
return nil, c.cmd.RelabelPointsInLabelRange(uint32(args[0]), uint32(args[1]), uint32(args[2]))
},
"unlabel": func(c *console, updateSel updateSelectionFn, args []float32) ([][]float32, error) {
var labelsToKeep []uint32
for _, v := range args {
labelsToKeep = append(labelsToKeep, uint32(v))
}
return nil, c.cmd.UnlabelPoints(labelsToKeep)
},
}

func (c *console) Run(line string, updateSel updateSelectionFn) ([][]float32, error) {
Expand Down
74 changes: 74 additions & 0 deletions editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,80 @@ func (e *editor) passThroughByMask(sel []uint32, mask, val uint32) error {
return nil
}

func (e *editor) relabelPointsInLabelRange(minLabel, maxLabel, newLabel uint32) error {
_, err := e.pp.Uint32Iterator("label")
if err != nil {
return err
}

pcNew := &pc.PointCloud{
PointCloudHeader: e.pp.PointCloudHeader.Clone(),
Data: make([]byte, len(e.pp.Data)),
Points: e.pp.Points,
}
copy(pcNew.Data, e.pp.Data)
pcNew.Width = e.pp.Width
pcNew.Height = e.pp.Height

lt, err := pcNew.Uint32Iterator("label")
if err != nil {
return err
}

for ; lt.IsValid(); lt.Incr() {
l := lt.Uint32()
if l == newLabel || l < minLabel || l > maxLabel {
continue
}
lt.SetUint32(newLabel)
}

e.pp = e.push(pcNew)
runtime.GC()
return nil
}

func (e *editor) unlabelPoints(labelsToKeep []uint32) error {
_, err := e.pp.Uint32Iterator("label")
if err != nil {
return err
}

pcNew := &pc.PointCloud{
PointCloudHeader: e.pp.PointCloudHeader.Clone(),
Data: make([]byte, len(e.pp.Data)),
Points: e.pp.Points,
}
copy(pcNew.Data, e.pp.Data)
pcNew.Width = e.pp.Width
pcNew.Height = e.pp.Height

lt, err := pcNew.Uint32Iterator("label")
if err != nil {
return err
}

isInLabelsToKeep := func(l uint32) bool {
for _, kl := range labelsToKeep {
if kl == l {
return true
}
}
return false
}

for ; lt.IsValid(); lt.Incr() {
if isInLabelsToKeep(lt.Uint32()) {
continue
}
lt.SetUint32(0)
}

e.pp = e.push(pcNew)
runtime.GC()
return nil
}

func passThrough(pp *pc.PointCloud, fn func(int, mat.Vec3) bool) (*pc.PointCloud, error) {
it, err := pp.Vec3Iterator()
if err != nil {
Expand Down
17 changes: 0 additions & 17 deletions pcdeditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ class PCDEditor {
maxLabelInput.onchange = () => onRenderLabelRangeChange()
onRenderLabelRangeChange()


this.qs('#top').onclick = async () => {
try {
await pcdeditor.command('snap_yaw')
Expand Down Expand Up @@ -793,22 +792,6 @@ class PCDEditor {
</div>
</div>
<hr/>
<div class="${id('foldMenuElem')}">
<button id="${id('delete')}">Delete</button>
</div>
<hr/>
<div class="${id('foldMenuElem')}">
<label
class="${id('inputLabelShort')}"
>Insert pcd</label>
<input
id="${id('insertSubPcdFile')}"
type="file"
accept=".pcd"
style="display: none;"
/>
<button id="${id('insertSubPcd')}">Select file</button>
</div>
<div class="${id('foldMenuElem')}">
<label class="${id('inputLabel')}">Clipboard</label>
<button id="${id('clipboardCopy')}">Copy</button>
Expand Down