OpenDroneMap-ODM/opendm/dem/ground_rectification/point_cloud.py

102 wiersze
4.3 KiB
Python
Executable File

import numpy as np
from numpy.lib.recfunctions import append_fields
class PointCloud:
"""Representation of a 3D point cloud"""
def __init__(self, xy, z, classification, rgb, indices, extra_dimensions, extra_dimensions_metadata):
self.xy = xy
self.z = z
self.classification = classification
self.rgb = rgb
self.indices = indices
self.extra_dimensions = extra_dimensions
self.extra_dimensions_metadata = extra_dimensions_metadata
@staticmethod
def with_dimensions(x, y, z, classification, red, green, blue, indices=None):
xy = np.column_stack((x, y))
rgb = np.column_stack((red, green, blue))
indices = indices if indices is not None else np.arange(0, len(x))
return PointCloud(xy, z, classification, rgb, indices, { }, { })
@staticmethod
def with_xy(xy):
[x, y] = np.hsplit(xy, 2)
empty = np.empty(xy.shape[0])
return PointCloud.with_dimensions(x.ravel(), y.ravel(), empty, np.empty(xy.shape[0], dtype=np.uint8), empty, empty, empty)
def __getitem__(self, mask):
masked_dimensions = { name: values[mask] for name, values in self.extra_dimensions.items() }
return PointCloud(self.xy[mask], self.z[mask], self.classification[mask], self.rgb[mask], self.indices[mask], masked_dimensions, self.extra_dimensions_metadata)
def concatenate(self, other_cloud):
for name, dimension in self.extra_dimensions_metadata.items():
if name not in other_cloud.extra_dimensions:
dimension.assign_default(other_cloud)
for name, dimension in other_cloud.extra_dimensions_metadata.items():
if name not in self.extra_dimensions:
dimension.assign_default(self)
new_indices = np.arange(len(self.indices), len(self.indices) + len(other_cloud.indices))
self.xy = np.concatenate((self.xy, other_cloud.xy))
self.z = np.concatenate((self.z, other_cloud.z))
self.classification = np.concatenate((self.classification, other_cloud.classification))
self.rgb = np.concatenate((self.rgb, other_cloud.rgb))
self.indices = np.concatenate((self.indices, new_indices))
self.extra_dimensions = { name: np.concatenate((values, other_cloud.extra_dimensions[name])) for name, values in self.extra_dimensions.items() }
def update(self, other_cloud):
for name, dimension in self.extra_dimensions_metadata.items():
if name not in other_cloud.extra_dimensions:
dimension.assign_default(other_cloud)
for name, dimension in other_cloud.extra_dimensions_metadata.items():
if name not in self.extra_dimensions:
dimension.assign_default(self)
self.xy[other_cloud.indices] = other_cloud.xy
self.z[other_cloud.indices] = other_cloud.z
self.classification[other_cloud.indices] = other_cloud.classification
self.rgb[other_cloud.indices] = other_cloud.rgb
for name, values in self.extra_dimensions.items():
values[other_cloud.indices] = other_cloud.extra_dimensions[name]
def add_dimension(self, dimension, values):
self.extra_dimensions[dimension.get_name()] = values
self.extra_dimensions_metadata[dimension.get_name()] = dimension
def get_xy(self):
return self.xy
def get_z(self):
return self.z
def len(self):
return len(self.z)
def get_extra_dimension_values(self, name):
return self.extra_dimensions[name]
def get_bounding_box(self):
[x_min, y_min] = np.amin(self.xy, axis=0)
[x_max, y_max] = np.amax(self.xy, axis=0)
z_min = min(self.z)
z_max = max(self.z)
return BoundingBox3D(x_min, x_max, y_min, y_max, z_min, z_max)
class BoundingBox3D:
def __init__(self, x_min, x_max, y_min, y_max, z_min, z_max):
self.x_min = x_min
self.x_max = x_max
self.y_min = y_min
self.y_max = y_max
self.z_min = z_min
self.z_max = z_max
def keep_points_inside(self, point_cloud):
min = np.array([self.x_min, self.y_min, self.z_min])
max = np.array([self.x_max, self.y_max, self.z_max])
arr = np.column_stack((point_cloud.get_xy(), point_cloud.get_z()))
mask = np.all(np.logical_and(min <= arr, arr <= max), axis=1)
return point_cloud[mask]