Source code for pylas.point.format

from itertools import zip_longest
from typing import Optional, Iterable

import numpy as np

from . import dims
from ..errors import PylasError


[docs]class ExtraBytesParams: """All parameters needed to create extra bytes""" def __init__( self, name: str, type: str, description: str = "", offsets: Optional[np.ndarray] = None, scales: Optional[np.ndarray] = None, ) -> None: self.name = name """ The name of the extra dimension """ self.type = type """ The type of the extra dimension """ self.description = description """ A description of the extra dimension """ self.offsets = offsets """ The offsets to use if its a 'scaled dimension', can be none """ self.scales = scales """ The scales to use if its a 'scaled dimension', can be none """
[docs]class PointFormat: """Class that contains the informations about the dimensions that forms a PointFormat. A PointFormat has 'standard' dimensions (dimensions defined in the LAS standard, each point format has its set of dimensions), but it can also have extra (non-standard) dimensions defined by the user) >>> fmt = PointFormat(3) >>> all(dim.is_standard for dim in fmt.dimensions) True >>> dim = fmt.dimension_by_name("classification") # or fmt["classification"] >>> dim.max 31 >>> dim.min 0 >>> dim.num_bits 5 """ def __init__( self, point_format_id: int, ): """ Parameters ---------- point_format_id: int point format id """ self.id = point_format_id self.dimensions = [] composed_dims = dims.COMPOSED_FIELDS[self.id] for dim_name in dims.ALL_POINT_FORMATS_DIMENSIONS[self.id]: try: sub_fields = composed_dims[dim_name] except KeyError: dimension = dims.DimensionInfo.from_type_str( dim_name, dims.DIMENSIONS_TO_TYPE[dim_name], is_standard=True ) self.dimensions.append(dimension) else: for sub_field in sub_fields: dimension = dims.DimensionInfo.from_bitmask( sub_field.name, sub_field.mask, is_standard=True ) self.dimensions.append(dimension) @property def standard_dimensions(self) -> Iterable[dims.DimensionInfo]: """Returns an iterable of the standard dimensions >>> fmt = PointFormat(0) >>> standard_dims = list(fmt.standard_dimensions) >>> len(standard_dims) 15 >>> standard_dims[4].name 'return_number' """ return (dim for dim in self.dimensions if dim.is_standard) @property def extra_dimensions(self) -> Iterable[dims.DimensionInfo]: return (dim for dim in self.dimensions if dim.is_standard is False) @property def dimension_names(self) -> Iterable[str]: """Returns the names of the dimensions contained in the point format""" return (dim.name for dim in self.dimensions) @property def standard_dimension_names(self) -> Iterable[str]: """Returns the names of the extra dimensions in this point format""" return (dim.name for dim in self.standard_dimensions) @property def extra_dimension_names(self) -> Iterable[str]: """Returns the names of the extra dimensions in this point format""" return (dim.name for dim in self.extra_dimensions) @property def size(self) -> int: """Returns the number of bytes (standard + extra) a point takes >>> PointFormat(3).size 34 >>> fmt = PointFormat(3) >>> fmt.add_extra_dimension(ExtraBytesParams("codification", "uint64")) >>> fmt.size 42 """ return int(sum(dim.num_bits for dim in self.dimensions) // 8) @property def num_standard_bytes(self) -> int: """Returns the number of bytes used by standard dims >>> fmt = PointFormat(3) >>> fmt.add_extra_dimension(ExtraBytesParams("codification", "uint64")) >>> fmt.num_standard_bytes 34 """ return int(sum(dim.num_bits for dim in self.standard_dimensions) // 8) @property def num_extra_bytes(self) -> int: """Returns the number of extra bytes >>> fmt = PointFormat(3) >>> fmt.add_extra_dimension(ExtraBytesParams("codification", "uint64")) >>> fmt.num_extra_bytes 8 """ return int(sum(dim.num_bits for dim in self.extra_dimensions) // 8) @property def has_waveform_packet(self): """Returns True if the point format has waveform packet dimensions""" dimensions = set(self.dimension_names) return all(name in dimensions for name in dims.WAVEFORM_FIELDS_NAMES)
[docs] def dimension_by_name(self, name: str) -> dims.DimensionInfo: """Returns the dimension info for the dimension by name ValueError is raised if the dimension does not exist un the point format >>> info = PointFormat(2).dimension_by_name('number_of_returns') >>> info.name == 'number_of_returns' True >>> info.num_bits == 3 True >>> info = PointFormat(2).dimension_by_name('gps_time') Traceback (most recent call last): ... ValueError: Dimension 'gps_time' does not exist """ for dim in self.dimensions: if dim.name == name: return dim raise ValueError(f"Dimension '{name}' does not exist")
[docs] def add_extra_dimension(self, param: ExtraBytesParams) -> None: """Add an extra, user-defined dimension""" dim_info = dims.DimensionInfo.from_type_str( param.name, param.type, is_standard=False, description=param.description, offsets=param.offsets, scales=param.scales, ) if ( dim_info.num_elements > 3 and dim_info.kind != dims.DimensionKind.UnsignedInteger ): raise PylasError("Extra Dimensions do not support more than 3 elements") self.dimensions.append(dim_info)
[docs] def dtype(self): """Returns the numpy.dtype used to store the point records in a numpy array .. note:: The dtype corresponds to the dtype with sub_fields *packed* into their composed fields """ dtype = dims.ALL_POINT_FORMATS_DTYPE[self.id] descr = dtype.descr for extra_dim in self.extra_dimensions: descr.append((extra_dim.name, extra_dim.type_str())) return np.dtype(descr)
def __getitem__(self, item): if isinstance(item, str): return self.dimension_by_name(item) return self.dimensions[item] def __eq__(self, other): if self.id != other.id: return False for my_eb, ot_eb in zip_longest(self.extra_dimensions, other.extra_dimensions): if my_eb != ot_eb: return False return True def __repr__(self): return "<PointFormat({}, {} bytes of extra dims)>".format( self.id, self.num_extra_bytes )
[docs]def lost_dimensions(point_fmt_in, point_fmt_out): """Returns a list of the names of the dimensions that will be lost when converting from point_fmt_in to point_fmt_out """ dimensions_in = set(PointFormat(point_fmt_in).dimension_names) dimensions_out = set(PointFormat(point_fmt_out).dimension_names) completely_lost = [] for dim_name in dimensions_in: if dim_name not in dimensions_out: completely_lost.append(dim_name) return completely_lost