Source code for py3dtiles.feature_table

from enum import Enum
import json

import numpy as np


[docs] class Feature: def __init__(self): self.positions = {} self.colors = {}
[docs] def to_array(self): pos_arr = np.array([(self.positions['X'], self.positions['Y'], self.positions['Z'])]).view(np.uint8)[0] if len(self.colors): col_arr = np.array([(self.colors['Red'], self.colors['Green'], self.colors['Blue'])]).view(np.uint8)[0] else: col_arr = np.array([]) return [pos_arr, col_arr]
[docs] @staticmethod def from_values(x, y, z, red=None, green=None, blue=None): f = Feature() f.positions = {'X': x, 'Y': y, 'Z': z} if red or green or blue: f.colors = {'Red': red, 'Green': green, 'Blue': blue} else: f.colors = {} return f
[docs] @staticmethod def from_array(positions_dtype, positions, colors_dtype=None, colors=None): """ Parameters ---------- positions_dtype : numpy.dtype positions : numpy.array Array of uint8. colors_dtype : numpy.dtype colors : numpy.array Array of uint8. Returns ------- f : Feature """ f = Feature() # extract positions f.positions = {} off = 0 for d in positions_dtype.names: dt = positions_dtype[d] data = np.array(positions[off:off + dt.itemsize]).view(dt)[0] off += dt.itemsize f.positions[d] = data # extract colors f.colors = {} if colors_dtype is not None: off = 0 for d in colors_dtype.names: dt = colors_dtype[d] data = np.array(colors[off:off + dt.itemsize]).view(dt)[0] off += dt.itemsize f.colors[d] = data return f
[docs] class SemanticPoint(Enum): NONE = 0 POSITION = 1 POSITION_QUANTIZED = 2 RGBA = 3 RGB = 4 RGB565 = 5 NORMAL = 6 NORMAL_OCT16P = 7 BATCH_ID = 8
[docs] class FeatureTableHeader: def __init__(self): # point semantics self.positions = SemanticPoint.POSITION self.positions_offset = 0 self.positions_dtype = None self.colors = SemanticPoint.NONE self.colors_offset = 0 self.colors_dtype = None self.normal = SemanticPoint.NONE self.normal_offset = 0 self.normal_dtype = None # global semantics self.points_length = 0 self.rtc = None
[docs] def to_array(self): jsond = self.to_json() json_str = json.dumps(jsond).replace(" ", "") n = len(json_str) + 28 if n % 8 != 0: json_str += ' ' * (8 - n % 8) return np.frombuffer(json_str.encode('utf-8'), dtype=np.uint8)
[docs] def to_json(self): jsond = {} # length jsond['POINTS_LENGTH'] = self.points_length # rtc if self.rtc: jsond['RTC_CENTER'] = self.rtc # positions offset = {'byteOffset': self.positions_offset} if self.positions == SemanticPoint.POSITION: jsond['POSITION'] = offset elif self.positions == SemanticPoint.POSITION_QUANTIZED: jsond['POSITION_QUANTIZED'] = offset # colors offset = {'byteOffset': self.colors_offset} if self.colors == SemanticPoint.RGB: jsond['RGB'] = offset return jsond
[docs] @staticmethod def from_dtype(positions_dtype, colors_dtype, nb_points): """ Parameters ---------- positions_dtype : numpy.dtype Numpy description of a positions. colors_dtype : numpy.dtype Numpy description of a colors. Returns ------- fth : FeatureTableHeader """ fth = FeatureTableHeader() fth.points_length = nb_points # search positions names = positions_dtype.names if ('X' in names) and ('Y' in names) and ('Z' in names): dtx = positions_dtype['X'] dty = positions_dtype['Y'] dtz = positions_dtype['Z'] fth.positions_offset = 0 if dtx == np.float32 and dty == np.float32 and dtz == np.float32: fth.positions = SemanticPoint.POSITION fth.positions_dtype = np.dtype([('X', np.float32), ('Y', np.float32), ('Z', np.float32)]) elif dtx == np.uint16 and dty == np.uint16 and dtz == np.uint16: fth.positions = SemanticPoint.POSITION_QUANTIZED fth.positions_dtype = np.dtype([('X', np.uint16), ('Y', np.uint16), ('Z', np.uint16)]) # search colors if colors_dtype is not None and fth.positions_dtype: names = colors_dtype.names if ('Red' in names) and ('Green' in names) and ('Blue' in names): if 'Alpha' in names: fth.colors = SemanticPoint.RGBA fth.colors_dtype = np.dtype([('Red', np.uint8), ('Green', np.uint8), ('Blue', np.uint8), ('Alpha', np.uint8)]) else: fth.colors = SemanticPoint.RGB fth.colors_dtype = np.dtype([('Red', np.uint8), ('Green', np.uint8), ('Blue', np.uint8)]) fth.colors_offset = (fth.positions_offset + nb_points * fth.positions_dtype.itemsize) else: fth.colors = SemanticPoint.NONE fth.colors_dtype = None return fth
[docs] @staticmethod def from_array(array): """ Parameters ---------- array : numpy.array Json in 3D Tiles format. See py3dtiles/doc/semantics.json for an example. Returns ------- fth : FeatureTableHeader """ jsond = json.loads(array.tobytes().decode('utf-8')) fth = FeatureTableHeader() # search position if "POSITION" in jsond: fth.positions = SemanticPoint.POSITION fth.positions_offset = jsond['POSITION']['byteOffset'] fth.positions_dtype = np.dtype([('X', np.float32), ('Y', np.float32), ('Z', np.float32)]) elif "POSITION_QUANTIZED" in jsond: fth.positions = SemanticPoint.POSITION_QUANTIZED fth.positions_offset = jsond['POSITION_QUANTIZED']['byteOffset'] fth.positions_dtype = np.dtype([('X', np.uint16), ('Y', np.uint16), ('Z', np.uint16)]) else: fth.positions = SemanticPoint.NONE fth.positions_offset = 0 fth.positions_dtype = None # search colors if "RGB" in jsond: fth.colors = SemanticPoint.RGB fth.colors_offset = jsond['RGB']['byteOffset'] fth.colors_dtype = np.dtype([('Red', np.uint8), ('Green', np.uint8), ('Blue', np.uint8)]) else: fth.colors = SemanticPoint.NONE fth.colors_offset = 0 fth.colors_dtype = None # points length if "POINTS_LENGTH" in jsond: fth.points_length = jsond["POINTS_LENGTH"] # RTC (Relative To Center) if "RTC_CENTER" in jsond: fth.rtc = jsond['RTC_CENTER'] else: fth.rtc = None return fth
[docs] class FeatureTableBody: def __init__(self): self.positions_arr = [] self.positions_itemsize = 0 self.colors_arr = [] self.colors_itemsize = 0
[docs] def to_array(self): arr = self.positions_arr if len(self.colors_arr): arr = np.concatenate((self.positions_arr, self.colors_arr)) if len(arr) % 8 != 0: padding_str = ' ' * (8 - len(arr) % 8) arr = np.concatenate(( arr, np.frombuffer(padding_str.encode('utf-8'), dtype=np.uint8) )) return arr
[docs] @staticmethod def from_features(fth, features): b = FeatureTableBody() # extract positions b.positions_itemsize = fth.positions_dtype.itemsize b.positions_arr = np.array([], dtype=np.uint8) if fth.colors_dtype is not None: b.colors_itemsize = fth.colors_dtype.itemsize b.colors_arr = np.array([], dtype=np.uint8) for f in features: fpos, fcol = f.to_array() b.positions_arr = np.concatenate((b.positions_arr, fpos)) if fth.colors_dtype is not None: b.colors_arr = np.concatenate((b.colors_arr, fcol)) return b
[docs] @staticmethod def from_array(fth, array): """ Parameters ---------- header : FeatureTableHeader array : numpy.array Returns ------- ftb : FeatureTableBody """ b = FeatureTableBody() nb_points = fth.points_length # extract positions pos_size = fth.positions_dtype.itemsize pos_offset = fth.positions_offset b.positions_arr = array[pos_offset:pos_offset + nb_points * pos_size] b.positions_itemsize = pos_size # extract colors if fth.colors != SemanticPoint.NONE: col_size = fth.colors_dtype.itemsize col_offset = fth.colors_offset b.colors_arr = array[col_offset:col_offset + col_size * nb_points] b.colors_itemsize = col_size return b
[docs] def positions(self, n): itemsize = self.positions_itemsize return self.positions_arr[n * itemsize:(n + 1) * itemsize]
[docs] def colors(self, n): if len(self.colors_arr): itemsize = self.colors_itemsize return self.colors_arr[n * itemsize:(n + 1) * itemsize] return []
[docs] class FeatureTable: def __init__(self): self.header = FeatureTableHeader() self.body = FeatureTableBody()
[docs] def nb_points(self): return self.header.points_length
[docs] def to_array(self): fth_arr = self.header.to_array() ftb_arr = self.body.to_array() return np.concatenate((fth_arr, ftb_arr))
[docs] @staticmethod def from_array(th, array): """ Parameters ---------- th : TileContentHeader array : numpy.array Returns ------- ft : FeatureTable """ # build feature table header fth_len = th.ft_json_byte_length fth_arr = array[0:fth_len] fth = FeatureTableHeader.from_array(fth_arr) # build feature table body ftb_len = th.ft_bin_byte_length ftb_arr = array[fth_len:fth_len + ftb_len] ftb = FeatureTableBody.from_array(fth, ftb_arr) # build feature table ft = FeatureTable() ft.header = fth ft.body = ftb return ft
[docs] @staticmethod def from_features(pdtype, cdtype, features): """ pdtype : numpy.dtype Numpy description for positions. cdtype : numpy.dtype Numpy description for colors. features : Feature[] Returns ------- ft : FeatureTable """ fth = FeatureTableHeader.from_dtype(pdtype, cdtype, len(features)) ftb = FeatureTableBody.from_features(fth, features) ft = FeatureTable() ft.header = fth ft.body = ftb return ft
[docs] def feature(self, n): pos = self.body.positions(n) col = self.body.colors(n) return Feature.from_array(self.header.positions_dtype, pos, self.header.colors_dtype, col)