Skip to content

Commit

Permalink
potree format
Browse files Browse the repository at this point in the history
  • Loading branch information
EliCDavis committed May 27, 2024
1 parent 9f6f9bc commit cd52153
Show file tree
Hide file tree
Showing 15 changed files with 894 additions and 26 deletions.
82 changes: 82 additions & 0 deletions examples/potree-utils/extract_pointcloud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package main

import (
"fmt"
"io"
"os"

"github.com/EliCDavis/polyform/formats/ply"
"github.com/EliCDavis/polyform/formats/potree"
"github.com/EliCDavis/polyform/modeling"
"github.com/urfave/cli/v2"
)

func buildModel(octreeFile *os.File, node *potree.OctreeNode, metadata *potree.Metadata, includeChildren bool) (*modeling.Mesh, error) {
_, err := octreeFile.Seek(int64(node.ByteOffset), 0)
if err != nil {
return nil, err
}

buf := make([]byte, node.ByteSize)
_, err = io.ReadFull(octreeFile, buf)
if err != nil {
return nil, err
}

mesh := potree.LoadNode(*node, *metadata, buf)
if includeChildren {
for _, c := range node.Children {
childMesh, err := buildModel(octreeFile, c, metadata, true)
if err != nil {
return nil, err
}
mesh = mesh.Append(*childMesh)
}
}
return &mesh, nil
}

var ExtractPointcloudCommand = &cli.Command{
Name: "extract-pointcloud",
Flags: []cli.Flag{
metadataFlag,
hierarchyFlag,
octreeFlag,
&cli.StringFlag{
Name: "node",
Value: "r",
Usage: "Name of node to extract point data from",
},
&cli.BoolFlag{
Name: "include-children",
Value: false,
Usage: "Whether or not to include children data",
},
&cli.StringFlag{
Name: "out",
Value: "out.ply",
Usage: "Name of ply file to write pointcloud data too",
},
},
Action: func(ctx *cli.Context) error {
metadata, hierarchy, err := loadHierarchy(ctx)
if err != nil {
return err
}

octreeFile, err := openOctreeFile(ctx)
if err != nil {
return err
}
defer octreeFile.Close()

mesh, err := buildModel(octreeFile, hierarchy, metadata, ctx.Bool("include-children"))
if err != nil {
return err
}

fmt.Fprintf(ctx.App.Writer, "Writing pointcloud with %d points to %s", mesh.Indices().Len(), ctx.String("out"))

return ply.SaveBinary(ctx.String("out"), *mesh)
},
}
42 changes: 42 additions & 0 deletions examples/potree-utils/hierarchy_to_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"encoding/json"
"os"

"github.com/urfave/cli/v2"
)

var HierarhcyToJsonCommand = &cli.Command{
Name: "hierarchy-to-json",
Flags: []cli.Flag{
metadataFlag,
hierarchyFlag,
&cli.StringFlag{
Name: "out",
Value: "hierarchy.json",
Usage: "Name of JSON file to write hierarchy data too",
},
},
Action: func(ctx *cli.Context) error {
_, hierarchy, err := loadHierarchy(ctx)
if err != nil {
return err
}

data, err := json.Marshal(hierarchy)
if err != nil {
return err
}

f, err := os.Create(ctx.String("out"))
if err != nil {
return err
}
defer f.Close()

_, err = f.Write(data)

return err
},
}
28 changes: 28 additions & 0 deletions examples/potree-utils/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"os"

"github.com/urfave/cli/v2"
)

func main() {

cmd := cli.App{
Name: "Potree Utils",
Version: "0.0.1",
Authors: []*cli.Author{
{Name: "Eli Davis"},
},
Description: "Different utilities for inspecting potree files",
Commands: []*cli.Command{
RenderHierarchyCommand,
ExtractPointcloudCommand,
HierarhcyToJsonCommand,
},
}

if err := cmd.Run(os.Args); err != nil {
panic(err)
}
}
100 changes: 100 additions & 0 deletions examples/potree-utils/render_hierarchy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"math"
"os"

"github.com/EliCDavis/polyform/formats/potree"
"github.com/urfave/cli/v2"
)

func GetPointCounts(depth int, node *potree.OctreeNode, out map[int][]int) {
if _, ok := out[depth]; !ok {
out[depth] = make([]int, 0, 1)
}

out[depth] = append(out[depth], int(node.NumPoints))

for _, c := range node.Children {
GetPointCounts(depth+1, c, out)
}
}

var RenderHierarchyCommand = &cli.Command{
Name: "render-hierarchy",
Flags: []cli.Flag{
metadataFlag,
hierarchyFlag,

//
&cli.IntFlag{
Name: "row-count",
Value: 100,
},

&cli.StringFlag{
Name: "out",
Value: "image.png",
},
},
Action: func(ctx *cli.Context) error {
_, hierarchy, err := loadHierarchy(ctx)
if err != nil {
return err
}
counts := make(map[int][]int)
GetPointCounts(0, hierarchy, counts)

rows := ctx.Int("row-count")
numNodes := hierarchy.DescendentCount() + 1
columns := numNodes / rows

columns += (len(counts) - 1) * 2 // Add spacing

fmt.Fprintf(ctx.App.Writer, "Column Count: %d", columns)

img := image.NewRGBA(image.Rectangle{
Min: image.Point{},
Max: image.Point{
X: columns,
Y: rows,
},
})
draw.Draw(img, img.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src)

f, err := os.Create(ctx.String("out"))
if err != nil {
return err
}
defer f.Close()

depth := 0
count := 0
offset := 0
for i := 0; i < numNodes; i++ {
y := (i % rows) - offset
x := int(math.Floor(float64(i)/float64(rows))) + (depth * 2)

v := byte((float64(counts[depth][count]) / 20000.) * 255)
img.Set(x, y, color.RGBA{
R: v,
A: 255,
})

count++

if count == len(counts[depth]) {
count = 0
depth++
// offset = rows - (y + rows)
}
}

return png.Encode(f, img)
},
}
52 changes: 52 additions & 0 deletions examples/potree-utils/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"os"
"path/filepath"

"github.com/EliCDavis/polyform/formats/potree"
"github.com/urfave/cli/v2"
)

var metadataFlag = &cli.StringFlag{
Name: "metadata",
Value: "metadata.json",
}

var hierarchyFlag = &cli.StringFlag{
Name: "hierarchy",
Value: "",
Usage: "If blank, it will assume the file named 'hierarchy.bin' located in the same folder as the metadata.json.",
}

var octreeFlag = &cli.StringFlag{
Name: "octree",
Value: "",
Usage: "If blank, it will assume the file named 'octree.bin' located in the same folder as the metadata.json.",
}

func loadHierarchy(ctx *cli.Context) (*potree.Metadata, *potree.OctreeNode, error) {
metadataPath := ctx.String("metadata")
hierarchyPath := ctx.String("hierarchy")
if hierarchyPath == "" {
hierarchyPath = filepath.Join(filepath.Dir(metadataPath), "hierarchy.bin")
}

metadata, err := potree.LoadMetadata(metadataPath)
if err != nil {
return nil, nil, err
}

hierarchy, err := metadata.LoadHierarchy(hierarchyPath)
return metadata, hierarchy, err
}

func openOctreeFile(ctx *cli.Context) (*os.File, error) {
metadataPath := ctx.String("metadata")
octreePath := ctx.String("octree")
if octreePath == "" {
octreePath = filepath.Join(filepath.Dir(metadataPath), "octree.bin")
}

return os.Open(octreePath)
}
27 changes: 16 additions & 11 deletions formats/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@

I'm not doing FBX. FBX is cursed and Autodesk can burn in hell.

| Format | Reading | Writing |
| ------- | ------- | ----------- |
| PTS | ✔️ ||
| PTX |||
| PLY | ✔️ | ✔️ |
| OBJ | ✔️ | ✔️ |
| GLTF || ✔️ |
| STL |||
| COLMAP | ✔️ ||
| OpenSFM | ✔️ ||
| Splat | ✔️ | ✔️ |
* ✔️ = Implemented
* ❌ = Planned
* ➖ = Not Planned

| Format | Reading | Writing |
| ---------- | ------- | ----------- |
| PTS | ✔️ ||
| PTX |||
| PLY | ✔️ | ✔️ |
| OBJ | ✔️ | ✔️ |
| GLTF || ✔️ |
| STL |||
| COLMAP | ✔️ ||
| OpenSFM | ✔️ ||
| Splat | ✔️ | ✔️ |
| Potree 2.0 | ✔️ ||
6 changes: 6 additions & 0 deletions formats/potree/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Potree V2 Format

## Resources

Test data pulled from the example found here:
https://potree.org/potree/examples/vr_heidentor.html
23 changes: 23 additions & 0 deletions formats/potree/attribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package potree

import "strings"

type Attribute struct {
Name string `json:"name"`
Description string `json:"description"`
Size int `json:"size"`
NumElements int `json:"numElements"`
ElementSize int `json:"elementSize"`
Type string `json:"type"`
Min []float64 `json:"min"`
Max []float64 `json:"max"`
}

func (a Attribute) IsPosition() bool {
return a.Name == "position" || a.Name == "POSITION_CARTESIAN"
}

func (a Attribute) IsColor() bool {
n := strings.ToLower(a.Name)
return n == "rgba" || n == "rgb"
}
Binary file added formats/potree/hierarchy.bin
Binary file not shown.
Loading

0 comments on commit cd52153

Please sign in to comment.