Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OctreeManager and vector operations #17

Merged
merged 55 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
e92b8ea
implement MultiPoseManager
true-real-michael Nov 9, 2023
77600e4
remove `Point(np.ndarray)`, `PointCloud(np.ndarray)`, `PosePoint(np.n…
true-real-michael Nov 9, 2023
09108b6
unify interface. first arg is pose_number, second arg is points
true-real-michael Nov 10, 2023
28eb6e4
fix interfase
true-real-michael Nov 10, 2023
1762150
faster point distribution for Grid
true-real-michael Nov 10, 2023
348f549
format with black
true-real-michael Nov 10, 2023
dca8c25
rename MultiPoseManager to OctreeManager
true-real-michael Nov 10, 2023
86aed1b
use faster point distribution in octree
true-real-michael Nov 10, 2023
94c6698
simplify grid point distribution
true-real-michael Nov 10, 2023
9357fee
add type annotations
true-real-michael Nov 10, 2023
a59e659
add docstrings
true-real-michael Nov 10, 2023
8f52fe5
fix tests
true-real-michael Nov 10, 2023
7e506f5
use __pose_voxel_coordinates when getting leaf points
true-real-michael Nov 10, 2023
0e93ada
remove comments
true-real-michael Nov 10, 2023
4d5a302
remove comments
true-real-michael Nov 10, 2023
1110bd9
rename RawPoint to Point and RawPointCloud to PointCloud
true-real-michael Nov 10, 2023
b651377
remove CloudManager
true-real-michael Nov 12, 2023
6569749
add comments
true-real-michael Nov 12, 2023
dc50706
rename multi_pose_manager.py to octree_manager.py
true-real-michael Nov 12, 2023
9b61e4f
add comments
true-real-michael Nov 12, 2023
3186b2d
remove pose_number optionality
true-real-michael Nov 12, 2023
94d5553
black format
true-real-michael Nov 12, 2023
5194c9c
remove octreenode position
true-real-michael Nov 12, 2023
a080b43
remove _get_voxel_for_point
true-real-michael Nov 12, 2023
ad14a3e
add subdivide_as to OctreeBase
true-real-michael Nov 13, 2023
a0a00ac
comment on OctreeManager.__empty_octree
true-real-michael Nov 13, 2023
6a1d9b1
make unify and generate_children protected not private
true-real-michael Nov 14, 2023
bce6d17
fix grid config octree and octree manager type specification
true-real-michael Nov 14, 2023
a8016f9
use default factory in dataclass
true-real-michael Nov 14, 2023
16a16b5
use default factory in dataclass
true-real-michael Nov 14, 2023
2ed9ca6
fix gridconfig
true-real-michael Nov 14, 2023
efe8d4e
GridConfig docstring
true-real-michael Nov 14, 2023
dc12d6b
better naming
true-real-michael Nov 14, 2023
5516212
remove Octree._unify
true-real-michael Nov 14, 2023
761a91e
remove lambdas from dataclass default factories
true-real-michael Nov 14, 2023
d5b056b
remove __empty_octree from OctreeManager, replace with more readable ifs
true-real-michael Nov 14, 2023
2782911
fix GridBaseConfig
true-real-michael Nov 15, 2023
25f77cb
rename grid_voxel_edge_length to voxel_edge_length
true-real-michael Nov 15, 2023
12cbe7a
black format
true-real-michael Nov 15, 2023
a84eab9
fix GridConfigBase
true-real-michael Nov 15, 2023
dfcd57f
fix comments for GridBase and Grid
true-real-michael Nov 15, 2023
5ee80a7
fix comments for octreenodes
true-real-michael Nov 15, 2023
bdda9d6
remove redefinitions of GridConfig default fields
true-real-michael Nov 15, 2023
8ed0cd4
remove from __future__ import annotations, replace with '' annotations
true-real-michael Nov 15, 2023
d11e5a8
replace '' with ""
true-real-michael Nov 15, 2023
0d4753e
fix optional
true-real-michael Nov 15, 2023
124ea79
fix errors raised and better test readability
true-real-michael Nov 15, 2023
bd36e0e
fix type annotations in octree_manager.py
true-real-michael Nov 15, 2023
8f3f5b2
fix type annotations in octree_manager.py
true-real-michael Nov 15, 2023
c3914c1
when inserting into OctreeManager, copy subdivision scheme from exist…
true-real-michael Nov 15, 2023
a4fd195
fix OctreeBase: add abstractmethod annotations
true-real-michael Nov 16, 2023
edea0d5
OctreeManager: add _scheme_octree and make all octrees in the manager…
true-real-michael Nov 16, 2023
7e22bf7
fix comments
true-real-michael Nov 16, 2023
82c8e6e
fix comments
true-real-michael Nov 16, 2023
1401a8a
fix visualization pose numbers discovery
true-real-michael Nov 16, 2023
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
183 changes: 92 additions & 91 deletions octreelib/grid/grid.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from dataclasses import dataclass, field
import random
from typing import List, Dict, Callable
from typing import List, Dict, Callable, Optional, Type

import k3d
import numpy as np
Expand All @@ -10,19 +11,30 @@
GridVisualizationType,
VisualizationConfig,
)
from octreelib.internal.point import (
RawPointCloud,
RawPoint,
PointCloud,
)
from octreelib.internal.voxel import Voxel
from octreelib.octree.multi_pose_octree import MultiPoseOctree
from octreelib.internal.typing import T
from octreelib.internal.point import PointCloud
from octreelib.internal.voxel import Voxel, VoxelBase
from octreelib.octree import Octree, OctreeConfig

__all__ = ["Grid", "GridConfig"]


@dataclass
class GridConfig(GridConfigBase):
pmokeev marked this conversation as resolved.
Show resolved Hide resolved
_compatible_octree_types = [MultiPoseOctree]
"""
Config for Grid

octree_manager_type: type of OctreeManager used.
OctreeManager is responsible for managing octrees for different poses.
octree_type: type of Octree used
Octree is the data structure used for storing points.
octree_config: This config will be forwarded to the octrees.
debug: True enables debug mode.
voxel_edge_length: Initial size of voxels.
corner: Corner of a grid.
"""

pass


class Grid(GridBase):
Expand All @@ -38,105 +50,120 @@ class Grid(GridBase):
def __init__(self, grid_config: GridConfig):
super().__init__(grid_config)

# {pose -> list of voxel coordinates}
self.__pose_voxel_coordinates: Dict[int, List[RawPoint]] = {}
# {pose -> list of voxels}
self.__pose_voxel_coordinates: Dict[int, List[VoxelBase]] = {}

# {voxel coordinates hash -> octree}
self.__octrees: Dict[int, grid_config.octree_type] = {}
# {voxel -> octree manager}
self.__octrees: Dict[VoxelBase, grid_config.octree_manager_type] = {}

def insert_points(self, pose_number: int, points: RawPointCloud):
def insert_points(self, pose_number: int, points: PointCloud):
"""
Insert points to the grid
:param pose_number: pose to which the cloud is inserted
:param points: point cloud
Insert points to the according octree.
If an octree for this pos does not exist, a new octree is created
:param pose_number: Pose number to which the cloud is inserted.
:param points: Point cloud to be inserted.
"""
if pose_number in self.__pose_voxel_coordinates:
raise ValueError(f"Cannot insert points to existing pose {pose_number}")

# register pose
self.__pose_voxel_coordinates[pose_number] = []

# convert points to PointsWithPose, which is a subclass of np.ndarray
points = PointCloud(points)

for point in points:
# get coords of voxel into which the point is inserted
voxel_coordinates = self.__get_voxel_for_point(point)
voxel_coordinates_hash = hash(voxel_coordinates)

# create octree in the voxel if it does not exist yet
if voxel_coordinates_hash not in self.__octrees:
self.__octrees[voxel_coordinates_hash] = self._grid_config.octree_type(
# distribute points to voxels
voxel_indices = (
((points - self._grid_config.corner) // self._grid_config.voxel_edge_length)
* self._grid_config.voxel_edge_length
).astype(int)
distributed_points = {}
unique_indices = np.unique(voxel_indices, axis=0)
for unique_id in unique_indices:
mask = np.where((voxel_indices == unique_id).all(axis=1))
distributed_points[tuple(unique_id)] = points[mask]

# insert points to octrees
for voxel_coordinates, voxel_points in distributed_points.items():
target_voxel = VoxelBase(
np.array(voxel_coordinates),
self._grid_config.voxel_edge_length,
)
if target_voxel not in self.__octrees:
self.__octrees[target_voxel] = self._grid_config.octree_manager_type(
self._grid_config.octree_type,
self._grid_config.octree_config,
voxel_coordinates,
self._grid_config.grid_voxel_edge_length,
np.array(voxel_coordinates),
self._grid_config.voxel_edge_length,
)

self.__octrees[voxel_coordinates_hash].insert_points(
[point.with_pose(pose_number)]
)
self.__pose_voxel_coordinates[pose_number].append(target_voxel)
self.__octrees[target_voxel].insert_points(pose_number, voxel_points)

def map_leaf_points(self, function: Callable[[RawPointCloud], RawPointCloud]):
def map_leaf_points(self, function: Callable[[PointCloud], PointCloud]):
"""
Transforms point cloud in each leaf node of each octree using the function
:param function: transformation function PointCloud -> PointCloud
Transform point cloud in each node of each octree using the function
:param function: Transformation function PointCloud -> PointCloud. It is applied to each leaf node.
"""
for voxel_coordinates in self.__octrees:
self.__octrees[voxel_coordinates].map_leaf_points(function)

def get_leaf_points(self, pose_number: int) -> List[Voxel]:
"""
:param pose_number: Pose number.
:param pose_number: The desired pose number.
:return: List of voxels. Each voxel is a representation of a leaf node.
Each voxel has the same corner, edge_length and points as one of the leaf nodes.
"""
return sum(
[
octree.get_leaf_points_by_pose_number(pose_number)
for octree in self.__octrees.values()
self.__octrees[voxel_coordinates].get_leaf_points(pose_number)
for voxel_coordinates in self.__pose_voxel_coordinates[pose_number]
],
[],
)

def get_points(self, pose_number: int) -> RawPointCloud:
def get_points(self, pose_number: int) -> PointCloud:
"""
:param pose_number: Pose number.
:return: All points inside the grid.
Returns points for a specific pose number.
:param pose_number: The desired pose number.
:return: Points belonging to the pose.
"""
return np.vstack(
[
octree.get_points_by_pose_number(pose_number)
for octree in self.__octrees.values()
]
[octree.get_points(pose_number) for octree in self.__octrees.values()]
)

def subdivide(self, subdivision_criteria: List[Callable[[RawPointCloud], bool]]):
def subdivide(
self,
subdivision_criteria: List[Callable[[PointCloud], bool]],
pose_numbers: Optional[List[int]] = None,
):
"""
Subdivides all octrees based on all points and given subdivision criteria.
:param pose_numbers: List of pose numbers to subdivide.
:param subdivision_criteria: List of bool functions which represent criteria for subdivision.
If any of the criteria returns **true**, the octree node is subdivided.
"""
for voxel_coordinates in self.__octrees:
self.__octrees[voxel_coordinates].subdivide(subdivision_criteria)
self.__octrees[voxel_coordinates].subdivide(
subdivision_criteria, pose_numbers
)

def filter(self, filtering_criteria: List[Callable[[RawPointCloud], bool]]):
def filter(self, filtering_criteria: List[Callable[[PointCloud], bool]]):
"""
Filters nodes of each octree with points by criteria
:param filtering_criteria: Filtering Criteria
Filters nodes of each octree with points by criterion
:param filtering_criteria: List of bool functions which represent criteria for filtering.
If any of the criteria returns **false**, the point cloud in octree leaf is removed.
"""
for voxel_coordinates in self.__octrees:
self.__octrees[voxel_coordinates].filter(filtering_criteria)

def visualize(self, config: VisualizationConfig) -> None:
def visualize(self, config: VisualizationConfig = VisualizationConfig()) -> None:
"""
Produces `.html` file with Grid
"""
plot = k3d.Plot()
random.seed(config.seed)
poses_number = len(self.__octrees.keys()) + 1
poses_numbers = self.__pose_voxel_coordinates.keys()

if config.type is GridVisualizationType.POSE:
for pose_number in range(poses_number):
for pose_number in poses_numbers:
color = random.randrange(0, 0xFFFFFF)
points = self.get_points(pose_number=pose_number)

Expand All @@ -147,7 +174,7 @@ def visualize(self, config: VisualizationConfig) -> None:
)
elif config.type is GridVisualizationType.VOXEL:
voxels_colors = {}
for pose_number in range(poses_number):
for pose_number in poses_numbers:
leaves = self.get_leaf_points(pose_number=pose_number)
for leaf in leaves:
if leaf.id not in voxels_colors.keys():
Expand All @@ -161,7 +188,7 @@ def visualize(self, config: VisualizationConfig) -> None:
)

vertices = []
for pose_number in range(poses_number):
for pose_number in poses_numbers:
# TODO: Draw full grid including empty voxels
leaves = self.get_leaf_points(pose_number=pose_number)
for leaf in leaves:
Expand Down Expand Up @@ -190,47 +217,21 @@ def visualize(self, config: VisualizationConfig) -> None:

def n_leaves(self, pose_number: int) -> int:
"""
:param pose_number: Pose number.
:return: Number of leaves in all octrees, which store points for given pose.
:param pose_number: The desired pose number.
:return: Number of leaf nodes in the octree for given pose number.
"""
return sum(
[
octree.n_leaves_by_pose_number(pose_number)
for octree in self.__octrees.values()
]
)
return sum([octree.n_leaves(pose_number) for octree in self.__octrees.values()])

def n_points(self, pose_number: int) -> int:
"""
:param pose_number: Pose number.
:return: Number of points for given pose.
:param pose_number: The desired pose number.
:return: Number of points of an octree for given pose number.
"""
return sum(
[
octree.n_points_by_pose_number(pose_number)
for octree in self.__octrees.values()
]
)
return sum([octree.n_points(pose_number) for octree in self.__octrees.values()])

def n_nodes(self, pose_number: int) -> int:
"""
:param pose_number: Pose number.
:return: Number of nodes in all octrees, which store points for given pose
(either themselves, or through their child nodes).
"""
return sum(
[
octree.n_nodes_by_pose_number(pose_number)
for octree in self.__octrees.values()
]
)

def __get_voxel_for_point(self, point: RawPoint) -> RawPoint:
"""
Method to get coordinates of a voxel where the given point would be stored.
:param point: Point.
:return: Corner of the voxel in the grid, where an appropriate octree for the point resides.
:param pose_number: The desired pose number.
:return: Number of nodes of an octree for given pose number.
"""
point = point[:3]
grid_voxel_edge_length = self._grid_config.grid_voxel_edge_length
return point // grid_voxel_edge_length * grid_voxel_edge_length
return sum([octree.n_nodes(pose_number) for octree in self.__octrees.values()])
Loading