#
#*******************************************************************************
#
#  Copyright 2025 RIEGL Laser Measurement Systems
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
#  SPDX-License-Identifier: Apache-2.0
#
#*******************************************************************************
#

"""
FILE:    riegl_system_description.py
AUTHOR:  RIEGL LMS GmbH, Austria
PURPOSE: Tools for handling RIEGL System Descriptions
VERSION: 1.3.4-aeac3835, file generated on 2025-03-25 at 13:00:18

DEPENDENCIES:
  - NumPy (https://numpy.org/): optional, used by PoseCalculator.execute()
"""

import functools
import json
import math
import re
import threading
import uuid
import warnings
from datetime import datetime, timezone
from typing import overload

try:
    import numpy as np
    _NUMPY_PACKAGE_AVAILABLE = True
except ImportError:
    _NUMPY_PACKAGE_AVAILABLE = False


# ______________________________________________________________________________
#
# Version information
#
RIEGL_RISD_TITLE   = "RIEGL System Description"
RIEGL_RISD_VERSION = "1.3.4"
RIEGL_RISD_COMMIT  = "aeac3835"
RIEGL_RISD_DATE    = "2025-03-25"
RIEGL_RISD_TIME    = "13:00:18"


# ______________________________________________________________________________
#
def deprecated(version: str):
    """
    Decorator for deprecated properties and functions
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            message = f"{func.__name__} is deprecated since version {version}, may be removed in future versions"
            warnings.warn(message, DeprecationWarning, stacklevel=2)
            return func(*args, **kwargs)
        return wrapper
    return decorator


# ______________________________________________________________________________
#
class Object:
    r"""
    Base class

    This is the base class for all other system description classes.

    Derived classes:
      - CameraDeviceAxes
      - CameraDeviceFieldOfView
      - CameraDeviceGeometry
      - CameraDeviceGeometryPixelToPixel
      - CameraDeviceGeometryPixelToRay
      - CameraDeviceGeometryRayToPixel
      - Document
      - LidarDeviceBeamGeometry
      - LidarDeviceGeometry
      - LidarDeviceGeometryUnits
      - NamedObject
      - PanoramaCameraDeviceFieldOfView
      - Pose
      - PoseAdjustmentSettings
      - RawDataPacket
      - TrajectoryOffsets
      - TypePlate

    Since version 1.0
    """

    __slots__ = ["__additional_properties"]

    def __init__(self):
        super().__init__()
        self.__additional_properties = str()

    def __repr__(self) -> str:
        result = list()
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def additional_properties(self) -> str:
        r"""
        Additional properties used by JSON read/write functions

        This variable serves as a cache for unrecognized properties read in
        from the JSON format. When the object is later output again in JSON
        format, these properties are also included.

        Type: String

        Since version 1.0.1
        """
        return self.__additional_properties

    @additional_properties.setter
    def additional_properties(self, value: str):
        self.__additional_properties = str(value)


# ______________________________________________________________________________
#
class NamedObject(Object):
    r"""
    Named object

    This class adds a name to the Object class. The name must be unique
    in the entire system, that is, two objects in a system description
    must not have the same name regardless of their position in the
    component tree. Please note that the name can be changed by the
    user at any time.

    Derived classes:
      - Group
      - System

    Since version 1.0
    """

    __slots__ = ["__name"]

    def __init__(self):
        super().__init__()
        self.__name = str()

    def __repr__(self) -> str:
        result = list()
        result.append("name=" + repr(self.name))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def name(self):
        r"""
        Object's unique name

        Names must not contain backslashes (``\``) and following
        names are not allowed: ``.``, ``..``, ``*`` and ``**``.

        Type: String

        Since version 1.0
        """
        return self.__name

    @name.setter
    def name(self, value):
        if value is None:
            raise ValueError("field \"name\" is required")

        value = str(value)
        self.__name = value


# ______________________________________________________________________________
#
class Group(NamedObject):
    r"""
    Components group

    This class provides a container for subcomponents (i.e., instances of classes that are derived from Component).

    Derived classes:
      - Component

    Since version 1.0
    """

    __slots__ = ["__components"]

    def __init__(self):
        super().__init__()
        self.__components = list()

    def __repr__(self) -> str:
        result = list()
        result.append("name=" + repr(self.name))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def components(self):
        r"""
        List of subcomponents

        Type: List of Component

        Since version 1.0
        """
        return self.__components

    @components.setter
    def components(self, value):
        if value is None:
            self.__components = None
            return

        value = list(filter(None, value))
        if not all(isinstance(item, Component) for item in value):
            raise ValueError("expected instance of Component")

        self.__components = value


# ______________________________________________________________________________
#
class TypePlate(Object):
    r"""
    Component identification

    The type plate identifies a component by information such as manufacturer,
    device type and serial number.

    Since version 1.0
    """

    __slots__ = [
        "__manufacturer",
        "__device_type",
        "__device_build",
        "__channel_number",
        "__serial_number",
        "__part_number",
        "__model_name"
    ]

    def __init__(self):
        super().__init__()
        self.__manufacturer = "unknown"
        self.__device_type = "unknown"
        self.__device_build = ""
        self.__channel_number = None
        self.__serial_number = "unknown"
        self.__part_number = ""
        self.__model_name = None

    def __repr__(self) -> str:
        result = list()
        result.append("manufacturer=" + repr(self.manufacturer))
        result.append("device_type=" + repr(self.device_type))
        result.append("device_build=" + repr(self.device_build))
        result.append("channel_number=" + repr(self.channel_number))
        result.append("serial_number=" + repr(self.serial_number))
        result.append("part_number=" + repr(self.part_number))
        result.append("model_name=" + repr(self.model_name))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def manufacturer(self):
        r"""
        Manufacturer's name (e.g. RIEGL)

        Type: String

        Since version 1.0
        """
        return self.__manufacturer

    @manufacturer.setter
    def manufacturer(self, value):
        if value is None:
            raise ValueError("field \"manufacturer\" is required")

        value = str(value)
        self.__manufacturer = value

    @property
    def device_type(self):
        r"""
        Unique type identifier (e.g. VQ-1560i)

        Type: String

        Since version 1.0
        """
        return self.__device_type

    @device_type.setter
    def device_type(self, value):
        if value is None:
            raise ValueError("field \"device_type\" is required")

        value = str(value)
        self.__device_type = value

    @property
    def device_build(self):
        r"""
        Optional device build variant (empty: unknown/unused)

        Type: String

        Since version 1.0
        """
        return self.__device_build

    @device_build.setter
    def device_build(self, value):
        if value is None:
            raise ValueError("field \"device_build\" is required")

        value = str(value)
        self.__device_build = value

    @property
    def channel_number(self):
        r"""
        Channel number (not defined or 0: single channel device, 1: 1st channel, ...)

        Type: Optional Int32

        Since version 1.0
        """
        return self.__channel_number

    @channel_number.setter
    def channel_number(self, value):
        if value is None:
            self.__channel_number = None
            return

        value = int(value)
        self.__channel_number = value

    @property
    def serial_number(self):
        r"""
        Unique device serial number

        Type: String

        Since version 1.0
        """
        return self.__serial_number

    @serial_number.setter
    def serial_number(self, value):
        if value is None:
            raise ValueError("field \"serial_number\" is required")

        value = str(value)
        self.__serial_number = value

    @property
    def part_number(self):
        r"""
        Optional part number (empty: unknown/unused)

        Type: String

        Since version 1.2.6
        """
        return self.__part_number

    @part_number.setter
    def part_number(self, value):
        if value is None:
            raise ValueError("field \"part_number\" is required")

        value = str(value)
        self.__part_number = value

    @property
    def model_name(self):
        r"""
        Optional name of a model for visualization purposes

        This property contains the name of a virtual model (e.g. a 3D mesh or
        an image) used to visualize the object. If no model name is specified,
        then the device type is used as model name. If an empty model name is
        specified, then the object is not visualized.

        Type: Optional String

        Since version 1.0
        """
        return self.__model_name

    @model_name.setter
    def model_name(self, value):
        if value is None:
            self.__model_name = None
            return

        value = str(value)
        self.__model_name = value


# ______________________________________________________________________________
#
class Pose(Object):
    r"""
    Position and orientation

    This class defines the position and orientation of a component in relation
    to its immediate parent. The information is divided into two parts. The
    first part defines the component's approximate position and orientation
    as designed (see ``rotation_angles`` and ``position_vector``). The second part
    defines additional displacements and rotations resulting from a system
    calibration (see ``calibration_angles`` and ``calibration_shifts``).

    The point coordinates in the coordinate system of the parent are calculated
    from the coordinates in the coordinate system of the component as follows:

      :math:`\vec{c} = \vec{cs} + R_z(ca_z) \cdot R_y(ca_y) \cdot R_x(ca_x) \cdot \vec{l}`

      :math:`\vec{p} = \vec{pv} + R_3(ra_3) \cdot R_2(ra_2) \cdot R_1(ra_1) \cdot \vec{c}`

    with:

    * :math:`\vec{l}` = x, y, z point coordinates in the coordinate system of the component
    * :math:`\vec{c}` = x, y, z point coordinates after applying component calibration
    * :math:`\vec{p}` = x, y, z point coordinates in the coordinate system of the parent
    * :math:`ca_{x,y,z}` = ``calibration_angles[0, 1, 2]``
    * :math:`ra_{1,2,3}` = ``rotation_angles[0, 1, 2]`` (see ``rotation_axes``)
    * :math:`\vec{cs}` = ``calibration_shifts[0, 1, 2]``
    * :math:`\vec{pv}` = ``position_vector[0, 1, 2]``
    * :math:`R_{1,2,3}(\alpha)` = one of the following elemental rotation matrices:
    * :math:`R_x(\alpha) = \begin{pmatrix} 1 & 0 & 0 \\ 0 & cos(\alpha) & -sin(\alpha) \\ 0 & sin(\alpha) & cos(\alpha) \end{pmatrix}` = rotation about x axis
    * :math:`R_y(\alpha) = \begin{pmatrix} cos(\alpha) & 0 & sin(\alpha) \\ 0 & 1 & 0 \\ -sin(\alpha) & 0 & cos(\alpha) \end{pmatrix}` = rotation about y axis
    * :math:`R_z(\alpha) = \begin{pmatrix} cos(\alpha) & -sin(\alpha) & 0 \\ sin(\alpha) & cos(\alpha) & 0 \\ 0 & 0 & 1 \end{pmatrix}` = rotation about z axis

    Derived classes:
      - NamedPose

    Since version 1.0
    """

    __slots__ = [
        "__rotation_axes",
        "__rotation_angles",
        "__position_vector",
        "__calibration_angles",
        "__calibration_shifts",
        "__adjustment_settings",
        "__adjustable"
    ]

    def __init__(self):
        super().__init__()
        self.__rotation_axes = None
        self.__rotation_angles = None
        self.__position_vector = None
        self.__calibration_angles = None
        self.__calibration_shifts = None
        self.__adjustment_settings = None
        self.__adjustable = False

    def __repr__(self) -> str:
        result = list()
        result.append("rotation_axes=" + repr(self.rotation_axes))
        result.append("rotation_angles=" + repr(self.rotation_angles))
        result.append("position_vector=" + repr(self.position_vector))
        result.append("calibration_angles=" + repr(self.calibration_angles))
        result.append("calibration_shifts=" + repr(self.calibration_shifts))
        result.append("adjustment_settings=" + repr(self.adjustment_settings))
        result.append("adjustable=" + repr(self.adjustable))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def rotation_axes(self):
        r"""
        Names of the 1st, 2nd and 3rd rotation axes

        This defines the sequence of the rotations, i.e. about which axes and in
        which order the rotations should take place. Allowed axes names are: "x",
        "y" and "z" (lowercase); the same axis name may occur more than once. The
        default order is ["x", "y", "z"]. The order ["z", "x", "z"] might be used
        for e.g. camera devices. Please note that the second and third rotations
        are around the rotated axes, not around the original ones (see also:
        https://en.wikipedia.org/wiki/Euler_angles#Conventions).

        Type: Optional List[3] of String

        Since version 1.0
        """
        return self.__rotation_axes

    @rotation_axes.setter
    def rotation_axes(self, value):
        if value is None:
            self.__rotation_axes = None
            return

        value = list(str(item) for item in value)
        if len(value) != 3:
            raise ValueError("expected exactly 3 values")

        self.__rotation_axes = value

    @property
    def rotation_angles(self):
        r"""
        Rotation angles about the 1st, 2nd and 3rd axes

        Type: Optional List[3] of Float64
        Unit: deg

        Since version 1.0
        """
        return self.__rotation_angles

    @rotation_angles.setter
    def rotation_angles(self, value):
        if value is None:
            self.__rotation_angles = None
            return

        value = list(float(item) for item in value)
        if len(value) != 3:
            raise ValueError("expected exactly 3 values")

        self.__rotation_angles = value

    @property
    def position_vector(self):
        r"""
        Position on the x, y and z axes

        Type: Optional List[3] of Float64
        Unit: m

        Since version 1.0
        """
        return self.__position_vector

    @position_vector.setter
    def position_vector(self, value):
        if value is None:
            self.__position_vector = None
            return

        value = list(float(item) for item in value)
        if len(value) != 3:
            raise ValueError("expected exactly 3 values")

        self.__position_vector = value

    @property
    def calibration_angles(self):
        r"""
        Tilt angles about the x, y and z axes

        Type: Optional List[3] of Float64
        Unit: deg

        Since version 1.0
        """
        return self.__calibration_angles

    @calibration_angles.setter
    def calibration_angles(self, value):
        if value is None:
            self.__calibration_angles = None
            return

        value = list(float(item) for item in value)
        if len(value) != 3:
            raise ValueError("expected exactly 3 values")

        self.__calibration_angles = value

    @property
    def calibration_shifts(self):
        r"""
        Shifts along the x, y and z axes

        Type: Optional List[3] of Float64
        Unit: m

        Since version 1.0
        """
        return self.__calibration_shifts

    @calibration_shifts.setter
    def calibration_shifts(self, value):
        if value is None:
            self.__calibration_shifts = None
            return

        value = list(float(item) for item in value)
        if len(value) != 3:
            raise ValueError("expected exactly 3 values")

        self.__calibration_shifts = value

    @property
    def adjustment_settings(self):
        r"""
        Defines optional settings for the adjustment/calibration procedure

        Type: Optional PoseAdjustmentSettings

        Since version 1.2.11
        """
        return self.__adjustment_settings

    @adjustment_settings.setter
    def adjustment_settings(self, value):
        if value is None:
            self.__adjustment_settings = None
            return

        if not isinstance(value, PoseAdjustmentSettings):
            raise ValueError("expected instance of PoseAdjustmentSettings")

        self.__adjustment_settings = value

    @property
    def adjustable(self):
        r"""
        Indicates whether calibration is variable during adjustment/calibration procedure

        Type: Boolean

        Since version 1.0
        """
        return self.__adjustable

    @adjustable.setter
    def adjustable(self, value):
        if value is None:
            raise ValueError("field \"adjustable\" is required")

        value = bool(value)
        self.__adjustable = value


# ______________________________________________________________________________
#
class NamedPose(Pose):
    r"""
    Named pose

    This class adds a name to the Pose class. The name is unique in a list
    of named poses, but does not have to be unique in the entire system.

    Since version 1.0
    """

    __slots__ = ["__name"]

    def __init__(self):
        super().__init__()
        self.__name = str()

    def __repr__(self) -> str:
        result = list()
        result.append("name=" + repr(self.name))
        result.append("rotation_axes=" + repr(self.rotation_axes))
        result.append("rotation_angles=" + repr(self.rotation_angles))
        result.append("position_vector=" + repr(self.position_vector))
        result.append("calibration_angles=" + repr(self.calibration_angles))
        result.append("calibration_shifts=" + repr(self.calibration_shifts))
        result.append("adjustment_settings=" + repr(self.adjustment_settings))
        result.append("adjustable=" + repr(self.adjustable))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def name(self):
        r"""
        Pose's unique name

        Type: String

        Since version 1.0
        """
        return self.__name

    @name.setter
    def name(self, value):
        if value is None:
            raise ValueError("field \"name\" is required")

        value = str(value)
        self.__name = value


# ______________________________________________________________________________
#
class PoseAdjustmentSettings(Object):
    r"""
    Pose adjustment settings for the adjustment/calibration procedure

    For each adjustable parameter, a floating point value defines what is to be done as follows:

    - **value is less than zero**: the parameter can be adjusted freely (without limits)
    - **value is exactly zero**: the parameter shall not be adjusted at all (= fixed)
    - **any other value** is interpreted as a-priori standard deviation (confidence) and has a retracting effect

    Since version 1.2.11
    """

    __slots__ = ["__calibration_angles", "__calibration_shifts"]

    def __init__(self):
        super().__init__()
        self.__calibration_angles = [None] * 3
        self.__calibration_angles[0] = 0
        self.__calibration_angles[1] = 0
        self.__calibration_angles[2] = 0
        self.__calibration_shifts = [None] * 3
        self.__calibration_shifts[0] = 0
        self.__calibration_shifts[1] = 0
        self.__calibration_shifts[2] = 0

    def __repr__(self) -> str:
        result = list()
        result.append("calibration_angles=" + repr(self.calibration_angles))
        result.append("calibration_shifts=" + repr(self.calibration_shifts))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def calibration_angles(self):
        r"""
        Settings for adjusting the calibration angles

        Type: List[3] of Float64
        Unit: deg

        Since version 1.2.11
        """
        return self.__calibration_angles

    @calibration_angles.setter
    def calibration_angles(self, value):
        if value is None:
            raise ValueError("field \"calibration_angles\" is required")

        value = list(float(item) for item in value)
        if len(value) != 3:
            raise ValueError("expected exactly 3 values")

        self.__calibration_angles = value

    @property
    def calibration_shifts(self):
        r"""
        Settings for adjusting the calibration shifts

        Type: List[3] of Float64
        Unit: m

        Since version 1.2.11
        """
        return self.__calibration_shifts

    @calibration_shifts.setter
    def calibration_shifts(self, value):
        if value is None:
            raise ValueError("field \"calibration_shifts\" is required")

        value = list(float(item) for item in value)
        if len(value) != 3:
            raise ValueError("expected exactly 3 values")

        self.__calibration_shifts = value


# ______________________________________________________________________________
#
class TrajectoryOffsets(Object):
    r"""
    Trajectory offsets

    As the navigation device establishes the BODY coordinate system, data
    transformed into this coordinate system is subsequently "geo-referenced"
    by transforming the data further by information provided by the trajectory,
    which is the output of post-processing software. This post-processing
    software generates the trajectory from the raw data provided by the
    subcomponents of the navigation device and from GNSS reference data
    or correction data provided by, e.g., a GNSS base station.

    The actual transformation of a single 3D data point is done with data from
    the trajectory at the same timestamp. However, to account for errors in
    timing or trajectory data some offsets can be added to the trajectory data
    before the actual transformation.

    Since version 1.0
    """

    __slots__ = ["__time", "__east", "__north", "__height", "__roll", "__pitch", "__yaw"]

    def __init__(self):
        super().__init__()
        self.__time = 0
        self.__east = 0
        self.__north = 0
        self.__height = 0
        self.__roll = 0
        self.__pitch = 0
        self.__yaw = 0

    def __repr__(self) -> str:
        result = list()
        result.append("time=" + repr(self.time))
        result.append("east=" + repr(self.east))
        result.append("north=" + repr(self.north))
        result.append("height=" + repr(self.height))
        result.append("roll=" + repr(self.roll))
        result.append("pitch=" + repr(self.pitch))
        result.append("yaw=" + repr(self.yaw))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def time(self):
        r"""
        Offset in time

        Type: Float64
        Unit: s

        Since version 1.0
        """
        return self.__time

    @time.setter
    def time(self, value):
        if value is None:
            raise ValueError("field \"time\" is required")

        value = float(value)
        self.__time = value

    @property
    def east(self):
        r"""
        Offset in east direction at current location

        Type: Float64
        Unit: m

        Since version 1.0
        """
        return self.__east

    @east.setter
    def east(self, value):
        if value is None:
            raise ValueError("field \"east\" is required")

        value = float(value)
        self.__east = value

    @property
    def north(self):
        r"""
        Offset in north direction at current location

        Type: Float64
        Unit: m

        Since version 1.0
        """
        return self.__north

    @north.setter
    def north(self, value):
        if value is None:
            raise ValueError("field \"north\" is required")

        value = float(value)
        self.__north = value

    @property
    def height(self):
        r"""
        Offset in height direction at current location

        Type: Float64
        Unit: m

        Since version 1.0
        """
        return self.__height

    @height.setter
    def height(self, value):
        if value is None:
            raise ValueError("field \"height\" is required")

        value = float(value)
        self.__height = value

    @property
    def roll(self):
        r"""
        Offset to the roll angle of the platform

        Type: Float64
        Unit: deg

        Since version 1.0
        """
        return self.__roll

    @roll.setter
    def roll(self, value):
        if value is None:
            raise ValueError("field \"roll\" is required")

        value = float(value)
        self.__roll = value

    @property
    def pitch(self):
        r"""
        Offset to the pitch angle of the platform

        Type: Float64
        Unit: deg

        Since version 1.0
        """
        return self.__pitch

    @pitch.setter
    def pitch(self, value):
        if value is None:
            raise ValueError("field \"pitch\" is required")

        value = float(value)
        self.__pitch = value

    @property
    def yaw(self):
        r"""
        Offset to the yaw angle of the platform

        Type: Float64
        Unit: deg

        Since version 1.0
        """
        return self.__yaw

    @yaw.setter
    def yaw(self, value):
        if value is None:
            raise ValueError("field \"yaw\" is required")

        value = float(value)
        self.__yaw = value


# ______________________________________________________________________________
#
class RawDataPacket(Object):
    r"""
    Raw data packet

    Representation of a raw data packet (RXP) with packet identification and packet content.

    Derived classes:
      - LidarDeviceGeometryPacket

    Since version 1.2.10
    """

    __slots__ = ["__id", "__content"]

    def __init__(self):
        super().__init__()
        self.__id = [None] * 2
        self.__id[0] = 0
        self.__id[1] = 0
        self.__content = list()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("content=" + repr(self.content))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def id(self):
        r"""
        Packet identification in accordance with the RiVLib

        Type: List[2] of UInt32

        Since version 1.0
        """
        return self.__id

    @id.setter
    def id(self, value):
        if value is None:
            raise ValueError("field \"id\" is required")

        value = list(int(item) for item in value)
        if len(value) != 2:
            raise ValueError("expected exactly 2 values")

        self.__id = value

    @property
    def content(self):
        r"""
        Packet parameters in sequential order

        Type: List of Float64

        Since version 1.0
        """
        return self.__content

    @content.setter
    def content(self, value):
        if value is None:
            raise ValueError("field \"content\" is required")

        value = list(float(item) for item in value)
        self.__content = value


# ______________________________________________________________________________
#
class LidarDeviceGeometryPacket(RawDataPacket):
    r"""
    Lidar device geometry packet

    Raw data packet that contains the geometry of the lidar device.

    Since version 1.0
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("content=" + repr(self.content))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class LidarDeviceGeometryUnits(Object):
    r"""
    Laser device geometry units

    Provides information on the conversion of angular raw measurements into
    SI units for both axes of a 3D laser scanner.

    Since version 1.0
    """

    __slots__ = ["__line_circle_count", "__frame_circle_count"]

    def __init__(self):
        super().__init__()
        self.__line_circle_count = 0
        self.__frame_circle_count = 0

    def __repr__(self) -> str:
        result = list()
        result.append("line_circle_count=" + repr(self.line_circle_count))
        result.append("frame_circle_count=" + repr(self.frame_circle_count))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def line_circle_count(self):
        r"""
        Number of ticks of the raw line scan angle angle corresponding to 360 deg (full rotation)

        Type: UInt32
        Unit: 1

        Since version 1.0
        """
        return self.__line_circle_count

    @line_circle_count.setter
    def line_circle_count(self, value):
        if value is None:
            raise ValueError("field \"line_circle_count\" is required")

        value = int(value)
        self.__line_circle_count = value

    @property
    def frame_circle_count(self):
        r"""
        Number of ticks of the raw frame scan angle angle corresponding to 360 deg (full rotation)

        Type: UInt32
        Unit: 1

        Since version 1.0
        """
        return self.__frame_circle_count

    @frame_circle_count.setter
    def frame_circle_count(self, value):
        if value is None:
            raise ValueError("field \"frame_circle_count\" is required")

        value = int(value)
        self.__frame_circle_count = value


# ______________________________________________________________________________
#
class LidarDeviceBeamGeometry(Object):
    r"""
    Lidar device beam geometry

    Provides information on laser beam properties. The diameter of laser
    footprint at distance :math:`R`, :math:`d(R)`, can be calculated from
    beam diameter at the exit aperture, :math:`d_0`, and the beam divergence
    :math:`\theta` as:

    :math:`d(R) = \sqrt{d_0^2 + R^2 \theta^2}`

    Note: The :math:`1/e`-diameter, :math:`d_1`, is only 71% of the
    :math:`1/e^2`-diameter. The FWHM-diameter, :math:`d_{FWHM}`, (Full
    Width Half Maximum) is only 59% of the :math:`1/e^2`-diameter. Same
    applies to the definition of the beam divergence, which is sometimes
    specified via the :math:`1/e`-diameter.

    Since version 1.0
    """

    __slots__ = ["__beam_diameter_exit_aperture", "__beam_divergence", "__beam_waist_distance"]

    def __init__(self):
        super().__init__()
        self.__beam_diameter_exit_aperture = 0
        self.__beam_divergence = 0
        self.__beam_waist_distance = 0

    def __repr__(self) -> str:
        result = list()
        result.append("beam_diameter_exit_aperture=" + repr(self.beam_diameter_exit_aperture))
        result.append("beam_divergence=" + repr(self.beam_divergence))
        result.append("beam_waist_distance=" + repr(self.beam_waist_distance))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def beam_diameter_exit_aperture(self):
        r"""
        :math:`1/e^2`-diameter of laser beam at exit aperture

        Type: Float64
        Unit: m

        Since version 1.0
        """
        return self.__beam_diameter_exit_aperture

    @beam_diameter_exit_aperture.setter
    def beam_diameter_exit_aperture(self, value):
        if value is None:
            raise ValueError("field \"beam_diameter_exit_aperture\" is required")

        value = float(value)
        self.__beam_diameter_exit_aperture = value

    @property
    def beam_divergence(self):
        r"""
        :math:`1/e^2`-beam divergence of laser beam

        Type: Float64
        Unit: rad

        Since version 1.0
        """
        return self.__beam_divergence

    @beam_divergence.setter
    def beam_divergence(self, value):
        if value is None:
            raise ValueError("field \"beam_divergence\" is required")

        value = float(value)
        self.__beam_divergence = value

    @property
    def beam_waist_distance(self):
        r"""
        Distance of the waist of the laser beam from the exit aperture

        Type: Float64
        Unit: m

        Since version 1.0
        """
        return self.__beam_waist_distance

    @beam_waist_distance.setter
    def beam_waist_distance(self, value):
        if value is None:
            raise ValueError("field \"beam_waist_distance\" is required")

        value = float(value)
        self.__beam_waist_distance = value


# ______________________________________________________________________________
#
class LidarDeviceGeometry(Object):
    r"""
    Lidar device geometry

    Lidar device geometry provides all the information required to convert
    geometrical lidar raw data, i.e., raw range and up to two angular measurement
    results, into Cartesian coordinates in the scanner's (lidar's) own coordinate
    system.

    Since version 1.0
    """

    __slots__ = ["__name", "__laser_wavelength", "__beam", "__primary", "__secondary", "__amu"]

    def __init__(self):
        super().__init__()
        self.__name = ""
        self.__laser_wavelength = 0.0
        self.__beam = LidarDeviceBeamGeometry()
        self.__primary = LidarDeviceGeometryPacket()
        self.__secondary = None
        self.__amu = LidarDeviceGeometryUnits()

    def __repr__(self) -> str:
        result = list()
        result.append("name=" + repr(self.name))
        result.append("laser_wavelength=" + repr(self.laser_wavelength))
        result.append("beam=" + repr(self.beam))
        result.append("primary=" + repr(self.primary))
        result.append("secondary=" + repr(self.secondary))
        result.append("amu=" + repr(self.amu))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def name(self):
        r"""
        Optional name or short description

        The name can be used to distinguish and access device geometry
        objects when multiple objects are defined for a single lidar
        device. See class LidarDevice for details.

        Type: String

        Since version 1.0
        """
        return self.__name

    @name.setter
    def name(self, value):
        if value is None:
            raise ValueError("field \"name\" is required")

        value = str(value)
        self.__name = value

    @property
    def laser_wavelength(self):
        r"""
        Wavelength of the laser in units of nm

        Type: Float64
        Unit: nm

        Since version 1.0
        """
        return self.__laser_wavelength

    @laser_wavelength.setter
    def laser_wavelength(self, value):
        if value is None:
            raise ValueError("field \"laser_wavelength\" is required")

        value = float(value)
        self.__laser_wavelength = value

    @property
    def beam(self):
        r"""
        Models the laser beam

        Type: LidarDeviceBeamGeometry

        Since version 1.0
        """
        return self.__beam

    @beam.setter
    def beam(self, value):
        if value is None:
            raise ValueError("field \"beam\" is required")

        if not isinstance(value, LidarDeviceBeamGeometry):
            raise ValueError("expected instance of LidarDeviceBeamGeometry")

        self.__beam = value

    @property
    def primary(self):
        r"""
        Models the scan mechanism itself

        Type: LidarDeviceGeometryPacket

        Since version 1.0
        """
        return self.__primary

    @primary.setter
    def primary(self, value):
        if value is None:
            raise ValueError("field \"primary\" is required")

        if not isinstance(value, LidarDeviceGeometryPacket):
            raise ValueError("expected instance of LidarDeviceGeometryPacket")

        self.__primary = value

    @property
    def secondary(self):
        r"""
        Frequently models the impact of the exit window pane

        Type: Optional LidarDeviceGeometryPacket

        Since version 1.0
        """
        return self.__secondary

    @secondary.setter
    def secondary(self, value):
        if value is None:
            self.__secondary = None
            return

        if not isinstance(value, LidarDeviceGeometryPacket):
            raise ValueError("expected instance of LidarDeviceGeometryPacket")

        self.__secondary = value

    @property
    def amu(self):
        r"""
        Provides information on how to transform measurements in internal units into SI units

        Type: LidarDeviceGeometryUnits

        Since version 1.0
        """
        return self.__amu

    @amu.setter
    def amu(self, value):
        if value is None:
            raise ValueError("field \"amu\" is required")

        if not isinstance(value, LidarDeviceGeometryUnits):
            raise ValueError("expected instance of LidarDeviceGeometryUnits")

        self.__amu = value


# ______________________________________________________________________________
#
class CameraDeviceAxes(Object):
    r"""
    Camera coordinate system axes

    This class defines the directions of the camera coordinate system
    (CMCS) axes. Each axis may have one of the following directions:

      - ``left-to-right``
      - ``top-to-bottom``
      - ``camera-to-object``

    The terms 'left', 'right', 'top' and 'bottom' refer to the edges of
    the original, non-rotated image, 'camera' refers to the camera body
    and 'object' refers to the object that can be seen in the image.

    Since version 1.0
    """

    __slots__ = ["__x", "__y", "__z"]

    def __init__(self):
        super().__init__()
        self.__x = "left-to-right"
        self.__y = "top-to-bottom"
        self.__z = "camera-to-object"

    def __repr__(self) -> str:
        result = list()
        result.append("x=" + repr(self.x))
        result.append("y=" + repr(self.y))
        result.append("z=" + repr(self.z))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def x(self):
        r"""
        Direction of x-axis (usually 'left-to-right')

        Type: String

        Since version 1.0
        """
        return self.__x

    @x.setter
    def x(self, value):
        if value is None:
            raise ValueError("field \"x\" is required")

        value = str(value)
        self.__x = value

    @property
    def y(self):
        r"""
        Direction of y-axis (usually 'top-to-bottom')

        Type: String

        Since version 1.0
        """
        return self.__y

    @y.setter
    def y(self, value):
        if value is None:
            raise ValueError("field \"y\" is required")

        value = str(value)
        self.__y = value

    @property
    def z(self):
        r"""
        Direction of z-axis (usually 'camera-to-object')

        Type: String

        Since version 1.0
        """
        return self.__z

    @z.setter
    def z(self, value):
        if value is None:
            raise ValueError("field \"z\" is required")

        value = str(value)
        self.__z = value


# ______________________________________________________________________________
#
class CameraDeviceFieldOfView(Object):
    r"""
    Camera device field of view

    This class contains the camera's angular field of view. The angles
    are usually derived from the sensor and lens parameters (see class
    CameraDeviceGeometry) during the calibration procedure, so that the
    sum of the ``left`` and ``right`` angles gives the horizontal angle
    of view and the sum of the ``top`` and ``bottom`` angles gives the
    vertical angle of view.

    The terms 'left', 'right', 'top' and 'bottom' refer to the edges of
    the original, non-rotated image.

    Since version 1.2.5
    """

    __slots__ = ["__left", "__right", "__top", "__bottom"]

    def __init__(self):
        super().__init__()
        self.__left = 0
        self.__right = 0
        self.__top = 0
        self.__bottom = 0

    def __repr__(self) -> str:
        result = list()
        result.append("left=" + repr(self.left))
        result.append("right=" + repr(self.right))
        result.append("top=" + repr(self.top))
        result.append("bottom=" + repr(self.bottom))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def left(self):
        r"""
        left part of the horizontal angle of view

        Type: Float64
        Unit: deg

        Since version 1.2.5
        """
        return self.__left

    @left.setter
    def left(self, value):
        if value is None:
            raise ValueError("field \"left\" is required")

        value = float(value)
        self.__left = value

    @property
    def right(self):
        r"""
        right part of the horizontal angle of view

        Type: Float64
        Unit: deg

        Since version 1.2.5
        """
        return self.__right

    @right.setter
    def right(self, value):
        if value is None:
            raise ValueError("field \"right\" is required")

        value = float(value)
        self.__right = value

    @property
    def top(self):
        r"""
        top part of the vertical angle of view

        Type: Float64
        Unit: deg

        Since version 1.2.5
        """
        return self.__top

    @top.setter
    def top(self, value):
        if value is None:
            raise ValueError("field \"top\" is required")

        value = float(value)
        self.__top = value

    @property
    def bottom(self):
        r"""
        bottom part of the vertical angle of view

        Type: Float64
        Unit: deg

        Since version 1.2.5
        """
        return self.__bottom

    @bottom.setter
    def bottom(self, value):
        if value is None:
            raise ValueError("field \"bottom\" is required")

        value = float(value)
        self.__bottom = value


# ______________________________________________________________________________
#
class CameraDeviceGeometryPixelToPixel(Object):
    r"""
    Base class for a additional shifting in pixel coordinates after or prior to applying the analytical distortion modelling

    With analytical lens distortion modelling usually some residual errors between ray directions and pixel coordinates
    remain. This pixel-to-pixel shift significantly reduces these errors. This pixel-to-pixel shift is based on a smooth
    model for shifting pixel coordinates over the entire plane of pixel coordinates. Depending on the analytical modelling
    direction, CameraDeviceGeometryRayToPixel or CameraDeviceGeometryPixelToRay, the pixel-to-pixel shift is applied either
    after the transformation by the analytical model (ray to pixel) or before the analytical model (pixel to ray).

    Derived classes:
      - CameraDeviceGeometryPixelToPixelRIEGL

    Since version 1.0
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class CameraDeviceGeometryPixelToPixelRIEGL(CameraDeviceGeometryPixelToPixel):
    r"""
    Specific implementation of a pixel-to-pixel shift based on bicubic splines for each of the pixel coordinates

    The specific implementation is based on the evaluation function BISPEV from FITPACK. We follow the
    description given at https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.bisplev.html
    and state the "tck-tuple" containing the nodes on the u-axis and the v-axis, the spline coefficients matrix
    as a 1-D array, and the degree of the spline, i.e., degree 3. As we model shifts independently for the u-
    and v-coordinates, we provide two sets of spline coefficients. Note that the first and last node for each
    axis is stated 4 times. For example, for nu x nv nodes, the t-arrays have nu+6 and nv+6 values. The number
    of coefficients is (nu+2) x (nv+2).

    Since version 1.0
    """

    __slots__ = ["__u_nodes", "__v_nodes", "__u_shift_coef", "__v_shift_coef", "__k"]

    def __init__(self):
        super().__init__()
        self.__u_nodes = list()
        self.__v_nodes = list()
        self.__u_shift_coef = list()
        self.__v_shift_coef = list()
        self.__k = list()

    def __repr__(self) -> str:
        result = list()
        result.append("u_nodes=" + repr(self.u_nodes))
        result.append("v_nodes=" + repr(self.v_nodes))
        result.append("u_shift_coef=" + repr(self.u_shift_coef))
        result.append("v_shift_coef=" + repr(self.v_shift_coef))
        result.append("k=" + repr(self.k))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def u_nodes(self):
        r"""
        List of u-nodes, corresponding to tx in tck-tuple of BISPEV, length: nu+6

        Type: List of Float64

        Since version 1.0
        """
        return self.__u_nodes

    @u_nodes.setter
    def u_nodes(self, value):
        if value is None:
            raise ValueError("field \"u_nodes\" is required")

        value = list(float(item) for item in value)
        self.__u_nodes = value

    @property
    def v_nodes(self):
        r"""
        List of v-nodes, corresponding to ty in tck-tuple of BISPEV, length: nv+6

        Type: List of Float64

        Since version 1.0
        """
        return self.__v_nodes

    @v_nodes.setter
    def v_nodes(self, value):
        if value is None:
            raise ValueError("field \"v_nodes\" is required")

        value = list(float(item) for item in value)
        self.__v_nodes = value

    @property
    def u_shift_coef(self):
        r"""
        List of coefficients for the shift in u, corresponding to c in tck-tuple of BISPEV, length: (nu+2) x (nv+2)

        Type: List of Float64

        Since version 1.0
        """
        return self.__u_shift_coef

    @u_shift_coef.setter
    def u_shift_coef(self, value):
        if value is None:
            raise ValueError("field \"u_shift_coef\" is required")

        value = list(float(item) for item in value)
        self.__u_shift_coef = value

    @property
    def v_shift_coef(self):
        r"""
        List of coefficients for the shift in v, corresponding to c in tck-tuple of BISPEV, length: (nu+2) x (nv+2)

        Type: List of Float64

        Since version 1.0
        """
        return self.__v_shift_coef

    @v_shift_coef.setter
    def v_shift_coef(self, value):
        if value is None:
            raise ValueError("field \"v_shift_coef\" is required")

        value = list(float(item) for item in value)
        self.__v_shift_coef = value

    @property
    def k(self):
        r"""
        Degree of spline, corresponding to kx, ky in tck-tuple of BISPEV

        Type: List[2] of UInt32

        Since version 1.0
        """
        return self.__k

    @k.setter
    def k(self, value):
        if value is None:
            raise ValueError("field \"k\" is required")

        value = list(int(item) for item in value)
        if len(value) != 2:
            raise ValueError("expected exactly 2 values")

        self.__k = value


# ______________________________________________________________________________
#
class CameraDeviceGeometryRayToPixel(Object):
    r"""
    Convert ray to pixel coordinates

    This is the base for all classes that provide details on how to convert
    a ray given in camera coordinate system (CMCS) to pixel coordinates.

    Derived classes:
      - CameraDeviceGeometryRayToPixelRIEGL

    Since version 1.0
    """

    __slots__ = ["__pixel_to_pixel"]

    def __init__(self):
        super().__init__()
        self.__pixel_to_pixel = None

    def __repr__(self) -> str:
        result = list()
        result.append("pixel_to_pixel=" + repr(self.pixel_to_pixel))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def pixel_to_pixel(self):
        r"""
        Shifts to apply after the analytical model

        Type: Optional CameraDeviceGeometryPixelToPixel

        Since version 1.0
        """
        return self.__pixel_to_pixel

    @pixel_to_pixel.setter
    def pixel_to_pixel(self, value):
        if value is None:
            self.__pixel_to_pixel = None
            return

        if not isinstance(value, CameraDeviceGeometryPixelToPixel):
            raise ValueError("expected instance of CameraDeviceGeometryPixelToPixel")

        self.__pixel_to_pixel = value


# ______________________________________________________________________________
#
class CameraDeviceGeometryRayToPixelRIEGL(CameraDeviceGeometryRayToPixel):
    r"""
    Convert ray to pixel coordinates

    This class contains the RIEGL model for converting a ray given in camera
    coordinate system (CMCS) to pixel coordinates.

    The internal parameters include the camera's intrinsic matrix A:

      :math:`A = \begin{pmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{pmatrix}`

    Where :math:`f_x`, :math:`f_y` are the focal lengths by the axes x and y in pixels and :math:`c_x`, :math:`c_y` are the coordinates of the principal point in pixels.

    Depending on the property ``version``, the "undistorted" **pixel coordinates** :math:`u`, :math:`v` are computed from point coordinates in CMCS by:

    * if ``version`` is 0 or 2:

      :math:`\begin{pmatrix} u' \\ v' \\ w' \end{pmatrix} = A \cdot \begin{pmatrix} X_{CMCS} \\ Y_{CMCS} \\ Z_{CMCS} \end{pmatrix}`

      :math:`u = {u' \over w'}` and :math:`v = {v' \over w'}`

    * if ``version`` is 4 or 5:

      :math:`rng2d = \sqrt{x^2 + y^2}`

      :math:`rng3d = \sqrt{x^2 + y^2 + z^2}`

      :math:`theta = \arccos{z \over rng3d}`

      :math:`u = x / rng2d \cdot fx \cdot 2 \cdot theta / \pi + cx`

      :math:`v = y / rng2d \cdot fy \cdot 2 \cdot theta / \pi + cy`

    Depending on the property ``version``, the **direction vector** :math:`d` in CMCS is computed from the "undistorted" pixel coordinates :math:`u`, :math:`v` by:

    * if ``version`` is 0 or 2:

      :math:`x' = {{u - c_x} \over f_x}`

      :math:`y' = {{v - c_y} \over f_y}`

      :math:`d = \begin{pmatrix} x' \\ y' \\ 1 \end{pmatrix}`

      :math:`d = { d \over { \lVert d \lVert } }`

    * if ``version`` is 4 or 5:

      :math:`x' = {{u - c_x} \over f_x}`

      :math:`y' = {{v - c_y} \over f_y}`

      :math:`phi = \arctan2(y', x')`

      :math:`theta = {\pi \over 2} \cdot \sqrt{x'^2 + y'^2}`

      :math:`d = \begin{pmatrix} {\cos(phi) \cdot \sin(theta)} \\ {\sin(phi) \cdot \sin(theta)} \\  {\cos(theta)} \end{pmatrix}`

    The **lens distortion** is modelled by at least two radial and two tangential coefficients: :math:`k_1`, :math:`k_2`, :math:`k_3`, :math:`k_4`, :math:`p_1`, :math:`p_2`.

    With :math:`x = {{u - c_x} \over f_x}` and :math:`y = {{v - c_y} \over f_y}`, the distorted pixel coordinates :math:`u_d`, :math:`v_d` are computed by:

      :math:`u_d = u + x \cdot f_x \cdot (k_1 \cdot r^2 + k_2 \cdot r^4 + k_3 \cdot r^6 + k_4 \cdot r^8) + 2 \cdot f_x \cdot x \cdot y \cdot p_1 + p_2 \cdot f_x \cdot (r^2 + 2 \cdot x^2)`

      :math:`v_d = v + y \cdot f_y \cdot (k_1 \cdot r^2 + k_2 \cdot r^4 + k_3 \cdot r^6 + k_4 \cdot r^8) + 2 \cdot f_y \cdot x \cdot y \cdot p_2 + p_1 \cdot f_y \cdot (r^2 + 2 \cdot y^2)`

    Depending on the property ``version``, :math:`r^2` is calculated as follows:

    * if ``version`` is 0 or 4 then :math:`r^2 = x^2 + y^2`
    * if ``version`` is 2 or 5 then :math:`r^2 = (\arctan{\sqrt{x^2 + y^2}})^2`

    Since version 1.0
    """

    __slots__ = ["__version", "__fx", "__fy", "__cx", "__cy", "__k1", "__k2", "__k3", "__k4", "__p1", "__p2"]

    def __init__(self):
        super().__init__()
        self.__version = 2
        self.__fx = 0
        self.__fy = 0
        self.__cx = 0
        self.__cy = 0
        self.__k1 = 0
        self.__k2 = 0
        self.__k3 = 0
        self.__k4 = 0
        self.__p1 = 0
        self.__p2 = 0

    def __repr__(self) -> str:
        result = list()
        result.append("version=" + repr(self.version))
        result.append("fx=" + repr(self.fx))
        result.append("fy=" + repr(self.fy))
        result.append("cx=" + repr(self.cx))
        result.append("cy=" + repr(self.cy))
        result.append("k1=" + repr(self.k1))
        result.append("k2=" + repr(self.k2))
        result.append("k3=" + repr(self.k3))
        result.append("k4=" + repr(self.k4))
        result.append("p1=" + repr(self.p1))
        result.append("p2=" + repr(self.p2))
        result.append("pixel_to_pixel=" + repr(self.pixel_to_pixel))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def version(self):
        r"""
        RIEGL Lens distortion model version

        Type: UInt32

        Since version 1.0
        """
        return self.__version

    @version.setter
    def version(self, value):
        if value is None:
            raise ValueError("field \"version\" is required")

        value = int(value)
        self.__version = value

    @property
    def fx(self):
        r"""
        Focal length (x-component)

        Type: Float64
        Unit: pix

        Since version 1.0
        """
        return self.__fx

    @fx.setter
    def fx(self, value):
        if value is None:
            raise ValueError("field \"fx\" is required")

        value = float(value)
        self.__fx = value

    @property
    def fy(self):
        r"""
        Focal length (y-component)

        Type: Float64
        Unit: pix

        Since version 1.0
        """
        return self.__fy

    @fy.setter
    def fy(self, value):
        if value is None:
            raise ValueError("field \"fy\" is required")

        value = float(value)
        self.__fy = value

    @property
    def cx(self):
        r"""
        Image center (x-component)

        Type: Float64
        Unit: pix

        Since version 1.0
        """
        return self.__cx

    @cx.setter
    def cx(self, value):
        if value is None:
            raise ValueError("field \"cx\" is required")

        value = float(value)
        self.__cx = value

    @property
    def cy(self):
        r"""
        Image center (y-component)

        Type: Float64
        Unit: pix

        Since version 1.0
        """
        return self.__cy

    @cy.setter
    def cy(self, value):
        if value is None:
            raise ValueError("field \"cy\" is required")

        value = float(value)
        self.__cy = value

    @property
    def k1(self):
        r"""
        Radial distortion parameter 1

        Type: Float64

        Since version 1.0
        """
        return self.__k1

    @k1.setter
    def k1(self, value):
        if value is None:
            raise ValueError("field \"k1\" is required")

        value = float(value)
        self.__k1 = value

    @property
    def k2(self):
        r"""
        Radial distortion parameter 2

        Type: Float64

        Since version 1.0
        """
        return self.__k2

    @k2.setter
    def k2(self, value):
        if value is None:
            raise ValueError("field \"k2\" is required")

        value = float(value)
        self.__k2 = value

    @property
    def k3(self):
        r"""
        Radial distortion parameter 3

        Type: Float64

        Since version 1.0
        """
        return self.__k3

    @k3.setter
    def k3(self, value):
        if value is None:
            raise ValueError("field \"k3\" is required")

        value = float(value)
        self.__k3 = value

    @property
    def k4(self):
        r"""
        Radial distortion parameter 4

        Type: Float64

        Since version 1.0
        """
        return self.__k4

    @k4.setter
    def k4(self, value):
        if value is None:
            raise ValueError("field \"k4\" is required")

        value = float(value)
        self.__k4 = value

    @property
    def p1(self):
        r"""
        Tangential distortion parameter 1

        Type: Float64

        Since version 1.0
        """
        return self.__p1

    @p1.setter
    def p1(self, value):
        if value is None:
            raise ValueError("field \"p1\" is required")

        value = float(value)
        self.__p1 = value

    @property
    def p2(self):
        r"""
        Tangential distortion parameter 2

        Type: Float64

        Since version 1.0
        """
        return self.__p2

    @p2.setter
    def p2(self, value):
        if value is None:
            raise ValueError("field \"p2\" is required")

        value = float(value)
        self.__p2 = value


# ______________________________________________________________________________
#
class CameraDeviceGeometryPixelToRay(Object):
    r"""
    Convert pixel coordinates to ray

    This is the base for all classes that provide details on how to convert
    given pixel coordinates to a ray in camera coordinate system (CMCS).

    Since version 1.0
    """

    __slots__ = ["__pixel_to_pixel"]

    def __init__(self):
        super().__init__()
        self.__pixel_to_pixel = None

    def __repr__(self) -> str:
        result = list()
        result.append("pixel_to_pixel=" + repr(self.pixel_to_pixel))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def pixel_to_pixel(self):
        r"""
        Shifts to apply before the analytical model

        Type: Optional CameraDeviceGeometryPixelToPixel

        Since version 1.0
        """
        return self.__pixel_to_pixel

    @pixel_to_pixel.setter
    def pixel_to_pixel(self, value):
        if value is None:
            self.__pixel_to_pixel = None
            return

        if not isinstance(value, CameraDeviceGeometryPixelToPixel):
            raise ValueError("expected instance of CameraDeviceGeometryPixelToPixel")

        self.__pixel_to_pixel = value


# ______________________________________________________________________________
#
class CameraDeviceGeometry(Object):
    r"""
    Camera device geometry

    This class contains the image and pixel sizes as well as two optional
    objects that provide details on how to convert a ray given in camera
    coordinate system (CMCS) to pixel coordinates and, if available, the
    other way round.

    Since version 1.0
    """

    __slots__ = ["__nx", "__ny", "__dx", "__dy", "__ray_to_pixel", "__pixel_to_ray"]

    def __init__(self):
        super().__init__()
        self.__nx = 0
        self.__ny = 0
        self.__dx = 0
        self.__dy = 0
        self.__ray_to_pixel = None
        self.__pixel_to_ray = None

    def __repr__(self) -> str:
        result = list()
        result.append("nx=" + repr(self.nx))
        result.append("ny=" + repr(self.ny))
        result.append("dx=" + repr(self.dx))
        result.append("dy=" + repr(self.dy))
        result.append("ray_to_pixel=" + repr(self.ray_to_pixel))
        result.append("pixel_to_ray=" + repr(self.pixel_to_ray))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def nx(self):
        r"""
        Image width (size along x-axis)

        Type: UInt32
        Unit: pix

        Since version 1.0
        """
        return self.__nx

    @nx.setter
    def nx(self, value):
        if value is None:
            raise ValueError("field \"nx\" is required")

        value = int(value)
        self.__nx = value

    @property
    def ny(self):
        r"""
        Image height (size along y-axis)

        Type: UInt32
        Unit: pix

        Since version 1.0
        """
        return self.__ny

    @ny.setter
    def ny(self, value):
        if value is None:
            raise ValueError("field \"ny\" is required")

        value = int(value)
        self.__ny = value

    @property
    def dx(self):
        r"""
        Size of pixel on chip along x-axis

        Type: Float64
        Unit: m

        Since version 1.0
        """
        return self.__dx

    @dx.setter
    def dx(self, value):
        if value is None:
            raise ValueError("field \"dx\" is required")

        value = float(value)
        self.__dx = value

    @property
    def dy(self):
        r"""
        Size of pixel on chip along y-axis

        Type: Float64
        Unit: m

        Since version 1.0
        """
        return self.__dy

    @dy.setter
    def dy(self, value):
        if value is None:
            raise ValueError("field \"dy\" is required")

        value = float(value)
        self.__dy = value

    @property
    def ray_to_pixel(self):
        r"""
        Convert ray to pixel coordinates

        Type: Optional CameraDeviceGeometryRayToPixel

        Since version 1.0
        """
        return self.__ray_to_pixel

    @ray_to_pixel.setter
    def ray_to_pixel(self, value):
        if value is None:
            self.__ray_to_pixel = None
            return

        if not isinstance(value, CameraDeviceGeometryRayToPixel):
            raise ValueError("expected instance of CameraDeviceGeometryRayToPixel")

        self.__ray_to_pixel = value

    @property
    def pixel_to_ray(self):
        r"""
        Convert pixel coordinates to ray

        Type: Optional CameraDeviceGeometryPixelToRay

        Since version 1.0
        """
        return self.__pixel_to_ray

    @pixel_to_ray.setter
    def pixel_to_ray(self, value):
        if value is None:
            self.__pixel_to_ray = None
            return

        if not isinstance(value, CameraDeviceGeometryPixelToRay):
            raise ValueError("expected instance of CameraDeviceGeometryPixelToRay")

        self.__pixel_to_ray = value


# ______________________________________________________________________________
#
class PanoramaCameraDeviceFieldOfView(Object):
    r"""
    Panorama camera device field of view

    This class contains the angular field of view of a panorama camera. The field of
    view is specified in the fixed Cartesian and polar coordinate system associated
    with the PanoramaCameraDevice.

    The field of view is usually 360 degrees in azimuth (0 to 360 deg) but might be smaller
    and can be stated by ``phi_min``, ``phi_max``, ``theta_min`` and ``theta_max``. The
    azimuth angles ``phi_min`` and ``phi_max`` have to be in the range 0 to 720 deg with
    the conditions ``phi_max`` > ``phi_min`` and ``phi_max`` - ``phi_min`` <= 360 deg. The
    polar angles ``theta_min`` and ``theta_max`` have to be in the range 0 to 180 deg with
    the condition ``theta_max`` > ``theta_min``.

    Since version 1.2.9
    """

    __slots__ = ["__phi_min", "__phi_max", "__theta_min", "__theta_max"]

    def __init__(self):
        super().__init__()
        self.__phi_min = 0
        self.__phi_max = 360
        self.__theta_min = 0
        self.__theta_max = 180

    def __repr__(self) -> str:
        result = list()
        result.append("phi_min=" + repr(self.phi_min))
        result.append("phi_max=" + repr(self.phi_max))
        result.append("theta_min=" + repr(self.theta_min))
        result.append("theta_max=" + repr(self.theta_max))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def phi_min(self):
        r"""
        minimum azimuth angle

        Type: Float64
        Unit: deg

        Since version 1.2.9
        """
        return self.__phi_min

    @phi_min.setter
    def phi_min(self, value):
        if value is None:
            raise ValueError("field \"phi_min\" is required")

        value = float(value)
        self.__phi_min = value

    @property
    def phi_max(self):
        r"""
        maximum azimuth angle

        Type: Float64
        Unit: deg

        Since version 1.2.9
        """
        return self.__phi_max

    @phi_max.setter
    def phi_max(self, value):
        if value is None:
            raise ValueError("field \"phi_max\" is required")

        value = float(value)
        self.__phi_max = value

    @property
    def theta_min(self):
        r"""
        minimum polar angle

        Type: Float64
        Unit: deg

        Since version 1.2.9
        """
        return self.__theta_min

    @theta_min.setter
    def theta_min(self, value):
        if value is None:
            raise ValueError("field \"theta_min\" is required")

        value = float(value)
        self.__theta_min = value

    @property
    def theta_max(self):
        r"""
        maximum polar angle

        Type: Float64
        Unit: deg

        Since version 1.2.9
        """
        return self.__theta_max

    @theta_max.setter
    def theta_max(self, value):
        if value is None:
            raise ValueError("field \"theta_max\" is required")

        value = float(value)
        self.__theta_max = value


# ______________________________________________________________________________
#
class Component(Group):
    r"""
    Base component class

    This class represents a component of the system or device.

    Derived classes:
      - AdjustableMount
      - BaseCameraDevice
      - CameraSystem
      - CarrierPlatform
      - DMI
      - EventDevice
      - GNSSAntenna
      - GNSSReceiver
      - Housing
      - IMU
      - LidarDevice
      - MirrorFacet
      - MountingInterface
      - NavigationDevice
      - StaticMount

    Since version 1.0
    """

    __slots__ = [
        "__id",
        "__device",
        "__comments",
        "__changelog",
        "__thumbnail",
        "__pose",
        "__weight",
        "__permanent_magnetic_field"
    ]

    def __init__(self):
        super().__init__()
        self.__id = generate_uuid()
        self.__device = TypePlate()
        self.__comments = None
        self.__changelog = str()
        self.__thumbnail = ""
        self.__pose = Pose()
        self.__weight = 0.0
        self.__permanent_magnetic_field = None

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    def __lshift__(self, components):
        """
        Append component (or list/tuple of components) to list of subcomponents.

        Since version 1.2.3
        """
        try:
            for component in components:
                self << component
        except TypeError:
            self.components.append(components)
        return self

    @property
    def id(self):
        r"""
        Component's unique identifier

        The ID must be unique in the entire system, that is, two components
        in a system description must not have the same ID regardless of their
        position in the component tree.
        Please note that the ID is intended to be a permanent reference to a
        component (e.g. a link stored in another file). It should therefore
        be constant, i.e. once created, it must not be changed later.

        Type: String

        Since version 1.0
        """
        return self.__id

    @id.setter
    def id(self, value):
        if value is None:
            raise ValueError("field \"id\" is required")

        value = str(value)
        self.__id = value

    @property
    def device(self):
        r"""
        Component identification

        Type: TypePlate

        Since version 1.0
        """
        return self.__device

    @device.setter
    def device(self, value):
        if value is None:
            raise ValueError("field \"device\" is required")

        if not isinstance(value, TypePlate):
            raise ValueError("expected instance of TypePlate")

        self.__device = value

    @property
    def comments(self):
        r"""
        Component comments/notes

        Type: Optional String

        Since version 1.0
        """
        return self.__comments

    @comments.setter
    def comments(self, value):
        if value is None:
            self.__comments = None
            return

        value = str(value)
        self.__comments = value

    @property
    def changelog(self):
        r"""
        Summary of changes made to the component

        Each change is summarized in a separate line. The changelog is not
        automatically maintained by RiSD itself, but each application can
        decide for itself whether an entry is added and how it is formulated.

        Example: ::

           2024-01-16 08:41:23: Calibrated for H2228236 by RiSCAN PRO 2.19
           2024-02-05 16:32:34: Automatic Camera Mounting Adjustment by RiSCAN PRO 2.19

        Messages can be appended to the changelog with ``new_changelog_entry()``.

        Type: String

        Since version 1.2.12
        """
        return self.__changelog

    @changelog.setter
    def changelog(self, value):
        if value is None:
            raise ValueError("field \"changelog\" is required")

        value = str(value)
        self.__changelog = value

    def new_changelog_entry(self, text):
        """
        Append message to the changelog

        This method can be used to append a message to the changelog. The
        current date and time is automatically inserted before the message.

        Since version 1.2.12
        """
        text = str(text).strip()
        time = datetime.now().replace(microsecond=0).isoformat().replace("T", " ")
        self.__changelog = (self.__changelog + "\n" + time + ": " + text).strip()

    @property
    def thumbnail(self):
        r"""
        Preview image of the component (empty: none)

        The image is expected to be saved in PNG or JPEG format and embedded according
        to the data URI scheme using the Base64 standard encoding (i.e. the text begins
        with ``data:image/png;base64,`` or ``data:image/jpeg;base64,``). Of course, the
        image should be as small as possible to keep the overall file size at a minimum.

        Type: String

        Since version 1.3.3
        """
        return self.__thumbnail

    @thumbnail.setter
    def thumbnail(self, value):
        if value is None:
            raise ValueError("field \"thumbnail\" is required")

        value = str(value)
        self.__thumbnail = value

    @property
    def pose(self):
        r"""
        Position and orientation

        Type: Pose

        Since version 1.0
        """
        return self.__pose

    @pose.setter
    def pose(self, value):
        if value is None:
            raise ValueError("field \"pose\" is required")

        if not isinstance(value, Pose):
            raise ValueError("expected instance of Pose")

        self.__pose = value

    @property
    def weight(self):
        r"""
        Component weight

        Type: Float64
        Unit: kg

        Since version 1.3.3
        """
        return self.__weight

    @weight.setter
    def weight(self, value):
        if value is None:
            raise ValueError("field \"weight\" is required")

        value = float(value)
        self.__weight = value

    @property
    def permanent_magnetic_field(self):
        r"""
        Magnetic flux density in micro-Tesla

        Some components of a system may have a permanent magnetic field, which
        must be taken into account when determining the north angle, for example.

        Type: Optional List[3] of Float64
        Unit: µT

        Since version 1.2.11
        """
        return self.__permanent_magnetic_field

    @permanent_magnetic_field.setter
    def permanent_magnetic_field(self, value):
        if value is None:
            self.__permanent_magnetic_field = None
            return

        value = list(float(item) for item in value)
        if len(value) != 3:
            raise ValueError("expected exactly 3 values")

        self.__permanent_magnetic_field = value


# ______________________________________________________________________________
#
class System(NamedObject):
    r"""
    System identification

    Provides a unique name of the system, usually the product type designator,
    a hint to the field of application and the root node of the component tree.

    Since version 1.0
    """

    __slots__ = ["__type_plate", "__field_of_application", "__comments", "__root_component"]

    def __init__(self):
        super().__init__()
        self.__type_plate = TypePlate()
        self.__field_of_application = "KLS"
        self.__comments = None
        self.__root_component = None

    def __repr__(self) -> str:
        result = list()
        result.append("name=" + repr(self.name))
        result.append("type_plate=" + repr(self.type_plate))
        result.append("field_of_application=" + repr(self.field_of_application))
        result.append("comments=" + repr(self.comments))
        result.append("root_component=" + repr(self.root_component))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def type_plate(self):
        r"""
        System identification

        Type: TypePlate

        Since version 1.0
        """
        return self.__type_plate

    @type_plate.setter
    def type_plate(self, value):
        if value is None:
            raise ValueError("field \"type_plate\" is required")

        if not isinstance(value, TypePlate):
            raise ValueError("expected instance of TypePlate")

        self.__type_plate = value

    @property
    def field_of_application(self):
        r"""
        Field of application

        Provides a hint about the usage of the system. One out of:

          - ``KLS`` (kinematic laser scanning, generic term for ALS, BLS, ULS and MLS)
          - ``ALS`` (airborne laser scanning)
          - ``BLS`` (bathymetry laser scanning)
          - ``ULS`` (UAV-based laser scanning)
          - ``MLS`` (mobile laser scanning)
          - ``TLS`` (terrestrial or static laser scanning)

        Type: String

        Since version 1.0
        """
        return self.__field_of_application

    @field_of_application.setter
    def field_of_application(self, value):
        if value is None:
            raise ValueError("field \"field_of_application\" is required")

        value = str(value)
        self.__field_of_application = value

    @property
    def comments(self):
        r"""
        System comments/notes

        Type: Optional String

        Since version 1.1.1
        """
        return self.__comments

    @comments.setter
    def comments(self, value):
        if value is None:
            self.__comments = None
            return

        value = str(value)
        self.__comments = value

    @property
    def root_component(self):
        r"""
        The root component of the component tree, usually a navigation device

        Type: Component

        Since version 1.0
        """
        return self.__root_component

    @root_component.setter
    def root_component(self, value):
        if value is None:
            self.__root_component = None
            return

        if not isinstance(value, Component):
            raise ValueError("expected instance of Component")

        self.__root_component = value


# ______________________________________________________________________________
#
class Document(Object):
    r"""
    Document

    This class represents a document that contains a RIEGL System Description
    together with some metadata like author name and modification time.

    Since version 1.0
    """

    __slots__ = ["__version", "__author", "__timestamp", "__modified_by", "__modified_on", "__system"]

    def __init__(self):
        super().__init__()
        self.__version = "1.3.4"
        self.__author = "unknown"
        self.__timestamp = current_date_time_rfc_3339()
        self.__modified_by = None
        self.__modified_on = None
        self.__system = System()

    def __repr__(self) -> str:
        result = list()
        result.append("version=" + repr(self.version))
        result.append("author=" + repr(self.author))
        result.append("timestamp=" + repr(self.timestamp))
        result.append("modified_by=" + repr(self.modified_by))
        result.append("modified_on=" + repr(self.modified_on))
        result.append("system=" + repr(self.system))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def version(self):
        r"""
        Document format version

        Type: String

        Since version 1.0
        """
        return self.__version

    @version.setter
    def version(self, value):
        if value is None:
            raise ValueError("field \"version\" is required")

        value = str(value)
        self.__version = value

    @property
    def author(self):
        r"""
        Name of the author that created the document

        Type: String

        Since version 1.0
        """
        return self.__author

    @author.setter
    def author(self, value):
        if value is None:
            raise ValueError("field \"author\" is required")

        value = str(value)
        self.__author = value

    @property
    def timestamp(self):
        r"""
        Date and time of creation (in RFC 3339 format, e.g. 2019-06-19T13:32:10+02:00)

        Type: String

        Since version 1.0
        """
        return self.__timestamp

    @timestamp.setter
    def timestamp(self, value):
        if value is None:
            raise ValueError("field \"timestamp\" is required")

        value = str(value)
        self.__timestamp = value

    @property
    def modified_by(self):
        r"""
        Name of the author that last changed the document

        Type: Optional String

        Since version 1.0
        """
        return self.__modified_by

    @modified_by.setter
    def modified_by(self, value):
        if value is None:
            self.__modified_by = None
            return

        value = str(value)
        self.__modified_by = value

    @property
    def modified_on(self):
        r"""
        Date and time of last modification (in RFC 3339 format, e.g. 2019-11-21T10:18:26+01:00)

        Type: Optional String

        Since version 1.0
        """
        return self.__modified_on

    @modified_on.setter
    def modified_on(self, value):
        if value is None:
            self.__modified_on = None
            return

        value = str(value)
        self.__modified_on = value

    @property
    def system(self):
        r"""
        The actual system description

        Type: System

        Since version 1.0
        """
        return self.__system

    @system.setter
    def system(self, value):
        if value is None:
            raise ValueError("field \"system\" is required")

        if not isinstance(value, System):
            raise ValueError("expected instance of System")

        self.__system = value


# ______________________________________________________________________________
#
class Housing(Component):
    r"""
    Housing

    This class represents the housing of a device or component. It does not
    introduce any additional properties and is mainly used for visualization.

    Since version 1.2.6
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class MirrorFacet(Component):
    r"""
    Mirror Facet

    This class represents a scan mirror or a scan mirror wheel facet. The
    component's X and Y axes define the mirror plane and the Z axis defines
    the normal of the mirror front.

    Since version 1.2.6
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class CarrierPlatform(Component):
    r"""
    Carrier Platform

    A carrier platform represents the platform, the kinematic lidar system is mounted to.
    It serves as a static mount to which, in addition, a GNSS antenna is attached to. It
    also carries an indicator that shows the kind of platform, e.g. ``fixed-wing aircraft``.
    The platform kind may support the processing of GNSS/IMU data.

    Since version 1.2.10
    """

    __slots__ = ["__kind"]

    def __init__(self):
        super().__init__()
        self.__kind = "unknown"

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("kind=" + repr(self.kind))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def kind(self):
        r"""
        Carrier platform kind

        Provides a hint about the platform the system was mounted on during data acquisition. One out of:

          - ``fixed-wing aircraft``
          - ``rotary-wing aircraft``
          - ``multi-rotor drone``
          - ``fixed-wing drone``
          - ``vertical-take-off-and-landing drone``
          - ``car``
          - ``truck``
          - ``bike``
          - ``backpack``
          - ``rail vehicle``
          - ``unknown``

        Type: String

        Since version 1.2.10
        """
        return self.__kind

    @kind.setter
    def kind(self, value):
        if value is None:
            raise ValueError("field \"kind\" is required")

        value = str(value)
        self.__kind = value


# ______________________________________________________________________________
#
class StaticMount(Component):
    r"""
    Static mount

    A static mount is a non-movable, non-rotatable carrier for subcomponents
    (e.g. a VMX Roof Mount).

    Since version 1.0
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class AdjustableMount(Component):
    r"""
    Base adjustable mount

    An adjustable mount is a movable and/or rotatable carrier for subcomponents
    (e.g. a VMQ Swivel Plate).

    Note: Do not use this class directly, but one of the derived classes.

    Derived classes:
      - ContinuousMount
      - DiscreteMount
      - LevellingMount

    Since version 1.2.0
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class LevellingMount(AdjustableMount):
    r"""
    Levelling mount

    A levelling mount is a motorized device frequently used in high-grade
    airborne laser scanning systems to compensate for unintended rotations
    in an aircraft due to wind and turbulences. The inner and stabilized
    part of a levelling mount - which actually is the mount for the Lidar
    system - changes its orientation actively with respect to the BODY
    coordinate system over time. The changes ensure, that the orientation
    of the Lidar system stays nearly stable with respect to a local levelled
    coordinate system. As the GNSS antenna is firmly affixed to the BODY
    coordinate system, also the so-called lever arm of the antenna does
    change over time with respect to the IMU. The actual orientation of
    the levelling mount over time has to be recorded by the navigation
    system via a data link in order to apply the antenna lever arm correctly.

    When making use of a levelling mount, the center of the BODY coordinate
    system has to coincide with the pivot point of the levelling mount. The
    translation vector of the IMU with respect to the levelling mount is in
    general non-zero.

    Since version 1.0
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class DiscreteMount(AdjustableMount):
    r"""
    Discrete mount

    A discrete mount pans or tilts a subsystem of a system, e.g. the
    swivel plate of a RIEGL VMQ-1HA allows operating of the system at
    distinct angles with respect to the driving direction.

    For each distinct position of the mount, a NamedPose provides a rigid
    transformation described by rotation angles and a translation vector.

    The actual position (pose index or name) must be recorded separately
    and must be applied when transforming point coordinates from a child
    component to the discrete mount's parent component as follows:

    1. Apply child component's Pose
    2. Apply NamedPose identified by index or name
    3. Apply discrete mount's Pose

    Since version 1.2.0
    """

    __slots__ = ["__poses", "__default_pose"]

    def __init__(self):
        super().__init__()
        self.__poses = list()
        self.__default_pose = None

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("poses=" + repr(self.poses))
        result.append("default_pose=" + repr(self.default_pose))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def poses(self):
        r"""
        Discrete mount poses (see class description)

        Type: List of NamedPose

        Since version 1.2.0
        """
        return self.__poses

    @poses.setter
    def poses(self, value):
        if value is None:
            raise ValueError("field \"poses\" is required")

        value = list(filter(None, value))
        if not all(isinstance(item, NamedPose) for item in value):
            raise ValueError("expected instance of NamedPose")

        self.__poses = value

    @property
    def default_pose(self):
        r"""
        Name of the default pose

        Depending on the intended use, it may be helpful to define
        a pose as the default pose. This pose is then used by the
        ``PoseCalculator`` if no pose has been explicitly selected
        for calculating the transformation matrix of the component.

        Type: Optional String

        Since version 1.2.12
        """
        return self.__default_pose

    @default_pose.setter
    def default_pose(self, value):
        if value is None:
            self.__default_pose = None
            return

        value = str(value)
        self.__default_pose = value


# ______________________________________________________________________________
#
class ContinuousMount(AdjustableMount):
    r"""
    Continuous mount

    A continuous mount rotates a subcomponent around a specific axis.

    In contrast to the DiscreteMount, the angle of rotation is continuous
    (stepless) and may change over time. Therefore, the angle must be recorded
    by a component of the system (usually a LidarDevice) and must be applied
    when transforming point coordinates from a child component to the continuous
    mount's parent component as follows:

    1. Apply child component's Pose
    2. Apply rotation (see equation below)
    3. Apply continuous mount's Pose

    :math:`R_n(\alpha) = \begin{pmatrix} n_x^2 (1 - cos(\alpha)) + cos(\alpha) & n_x n_y (1 - cos(\alpha)) - n_z sin(\alpha) & n_x n_z (1 - cos(\alpha)) + n_y sin(\alpha) \\ n_y n_x (1 - cos(\alpha)) + n_z sin(\alpha) & n_y^2 (1 - cos(\alpha)) + cos(\alpha) & n_y n_z (1 - cos(\alpha)) - n_x sin(\alpha) \\ n_z n_x (1 - cos(\alpha)) - n_y sin(\alpha) & n_z n_y (1 - cos(\alpha)) + n_x sin(\alpha) & n_z^2 (1 - cos(\alpha)) + cos(\alpha) \end{pmatrix}`

    with :math:`n` being the direction vector of the axis of rotation
    and :math:`\alpha` being the rotation angle

    Since version 1.2.0
    """

    __slots__ = ["__rotation_axis"]

    def __init__(self):
        super().__init__()
        self.__rotation_axis = [None] * 3
        self.__rotation_axis[0] = 0
        self.__rotation_axis[1] = 0
        self.__rotation_axis[2] = 1

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("rotation_axis=" + repr(self.rotation_axis))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def rotation_axis(self):
        r"""
        Direction vector of the axis of rotation

        Type: List[3] of Float64

        Since version 1.2.0
        """
        return self.__rotation_axis

    @rotation_axis.setter
    def rotation_axis(self, value):
        if value is None:
            raise ValueError("field \"rotation_axis\" is required")

        value = list(float(item) for item in value)
        if len(value) != 3:
            raise ValueError("expected exactly 3 values")

        self.__rotation_axis = value


# ______________________________________________________________________________
#
class MountingInterface(Component):
    r"""
    Mounting interface

    A mounting interface defines a mounting option for a subcomponent,
    e.g. a camera in a camera mount. The pose already defines the
    default coordinate system for the subcomponent to be mounted.

    Since version 1.2.7
    """

    __slots__ = ["__mounted_component"]

    def __init__(self):
        super().__init__()
        self.__mounted_component = "*"

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("mounted_component=" + repr(self.mounted_component))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def mounted_component(self):
        r"""
        Name of mounted subcomponent

        This specifies the name of the immediate subcomponent that is
        currently mounted on the interface. In addition to a component
        name, the following values are allowed:

            - an empty string = no subcomponent is currently mounted
            - an asterisk (``*``) = all subcomponents are currently mounted

        Type: String

        Since version 1.2.7
        """
        return self.__mounted_component

    @mounted_component.setter
    def mounted_component(self, value):
        if value is None:
            raise ValueError("field \"mounted_component\" is required")

        if isinstance(value, Component):
            value = value.name

        value = str(value)
        self.__mounted_component = value


# ______________________________________________________________________________
#
class IMU(Component):
    r"""
    Inertial Measurement Unit

    The inertial measurement unit is an integral part of a NavigationDevice. It
    provides 3-axes measurement data on acceleration and rotational speed.

    Since version 1.0
    """

    __slots__ = ["__device_geometry", "__statistical_model", "__timing_model"]

    def __init__(self):
        super().__init__()
        self.__device_geometry = None
        self.__statistical_model = None
        self.__timing_model = None

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("device_geometry=" + repr(self.device_geometry))
        result.append("statistical_model=" + repr(self.statistical_model))
        result.append("timing_model=" + repr(self.timing_model))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def device_geometry(self):
        r"""
        IMU device geometry

        Type: Optional List of RawDataPacket

        Since version 1.3.0
        """
        return self.__device_geometry

    @device_geometry.setter
    def device_geometry(self, value):
        if value is None:
            self.__device_geometry = None
            return

        value = list(filter(None, value))
        if not all(isinstance(item, RawDataPacket) for item in value):
            raise ValueError("expected instance of RawDataPacket")

        self.__device_geometry = value

    @property
    def statistical_model(self):
        r"""
        Statistical model of IMU

        Type: Optional RawDataPacket

        Since version 1.2.10
        """
        return self.__statistical_model

    @statistical_model.setter
    def statistical_model(self, value):
        if value is None:
            self.__statistical_model = None
            return

        if not isinstance(value, RawDataPacket):
            raise ValueError("expected instance of RawDataPacket")

        self.__statistical_model = value

    @property
    def timing_model(self):
        r"""
        Timing model of IMU

        Type: Optional RawDataPacket

        Since version 1.2.10
        """
        return self.__timing_model

    @timing_model.setter
    def timing_model(self, value):
        if value is None:
            self.__timing_model = None
            return

        if not isinstance(value, RawDataPacket):
            raise ValueError("expected instance of RawDataPacket")

        self.__timing_model = value


# ______________________________________________________________________________
#
class GNSSAntenna(Component):
    r"""
    GNSS Antenna

    Since version 1.0
    """

    __slots__ = ["__antex_code"]

    def __init__(self):
        super().__init__()
        self.__antex_code = "unknown"

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("antex_code=" + repr(self.antex_code))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def antex_code(self):
        r"""
        ANTEX code of antenna (e.g. TRMAV14)

        Type: String

        Since version 1.2.10
        """
        return self.__antex_code

    @antex_code.setter
    def antex_code(self, value):
        if value is None:
            raise ValueError("field \"antex_code\" is required")

        value = str(value)
        self.__antex_code = value


# ______________________________________________________________________________
#
class GNSSReceiver(Component):
    r"""
    GNSS Receiver

    Note: The primary and secondary GNSS antenna components can be direct
    subcomponents of the GNSS receiver, but can also be located elsewhere in
    the system description tree. To search for the component by its name, you must
    start at the root component of the system and traverse the tree recursively.

    Since version 1.0
    """

    __slots__ = ["__primary_gnss_antenna", "__secondary_gnss_antenna"]

    def __init__(self):
        super().__init__()
        self.__primary_gnss_antenna = str()
        self.__secondary_gnss_antenna = str()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("primary_gnss_antenna=" + repr(self.primary_gnss_antenna))
        result.append("secondary_gnss_antenna=" + repr(self.secondary_gnss_antenna))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def primary_gnss_antenna(self):
        r"""
        Name of primary GNSSAntenna component

        Type: String

        Since version 1.0
        """
        return self.__primary_gnss_antenna

    @primary_gnss_antenna.setter
    def primary_gnss_antenna(self, value):
        if value is None:
            raise ValueError("field \"primary_gnss_antenna\" is required")

        if isinstance(value, Component):
            value = value.name

        value = str(value)
        self.__primary_gnss_antenna = value

    @property
    def secondary_gnss_antenna(self):
        r"""
        Name of secondary GNSSAntenna component (empty: none)

        Type: String

        Since version 1.0
        """
        return self.__secondary_gnss_antenna

    @secondary_gnss_antenna.setter
    def secondary_gnss_antenna(self, value):
        if value is None:
            raise ValueError("field \"secondary_gnss_antenna\" is required")

        if isinstance(value, Component):
            value = value.name

        value = str(value)
        self.__secondary_gnss_antenna = value


# ______________________________________________________________________________
#
class DMI(Component):
    r"""
    Distance Measuring Instrument

    Usually used in Mobile Laser Scanning System to assist the NavigationDevice
    to achieve more accurate results in challenging GNSS environments.
    It consists mainly of an incremental angle encoder providing a distinct
    number of pulses per revolution. With the help of the wheel diameter the
    DMI is attached to, the pulses per revolution can easily be transformed
    into pulses per meter of covered distance. The ``position_vector`` of the
    pose represents the contact point of the wheel on the street surface. The
    pulses per meter are measured along the X axis of the device.

    Since version 1.0
    """

    __slots__ = ["__pulses_per_revolution", "__pulses_per_meter"]

    def __init__(self):
        super().__init__()
        self.__pulses_per_revolution = 0
        self.__pulses_per_meter = 0

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("pulses_per_revolution=" + repr(self.pulses_per_revolution))
        result.append("pulses_per_meter=" + repr(self.pulses_per_meter))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def pulses_per_revolution(self):
        r"""
        Pulses per revolution of the wheel the DMI is attached to (0: invalid)

        Type: UInt32
        Unit: 1

        Since version 1.0
        """
        return self.__pulses_per_revolution

    @pulses_per_revolution.setter
    def pulses_per_revolution(self, value):
        if value is None:
            raise ValueError("field \"pulses_per_revolution\" is required")

        value = int(value)
        self.__pulses_per_revolution = value

    @property
    def pulses_per_meter(self):
        r"""
        Pulses per meter of covered distance in X direction (0: invalid)

        Type: UInt32
        Unit: 1

        Since version 1.0
        """
        return self.__pulses_per_meter

    @pulses_per_meter.setter
    def pulses_per_meter(self, value):
        if value is None:
            raise ValueError("field \"pulses_per_meter\" is required")

        value = int(value)
        self.__pulses_per_meter = value


# ______________________________________________________________________________
#
class EventDevice(Component):
    r"""
    Event Device

    Events allow for determining the position and orientation of a kinematic
    lidar system at a series of points in time that relate to positions along
    the path of the lidar system. The time instants (time stamps) are typically
    provided by an external event device attached to the lidar system, which
    may be a distance sensor such as a calibrated wheel attached to the carrier
    platform and in contact with, for example, the rails of a railway track.

    Since version 1.2.12
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class NavigationDevice(Component):
    r"""
    Navigation device

    The Navigation Device itself may have a number of subcomponents, at least an
    IMU and a GNSSReceiver. In a mobile laser scanning system a DMI is frequently
    added. In high-grade airborne laser scanning systems a LevellingMount is
    utilized in order to control the continuity of lidar data acquisition on
    the ground. The LevellingMount is closely coupled to the Navigation Device
    and is thus modelled as an optional subcomponent of the Navigation Device.

    Note: The components IMU, DMI, GNSSReceiver and LevellingMount can be direct
    subcomponents of the navigation device, but can also be located elsewhere in
    the system description tree. To search for the component by its name, you must
    start at the root component of the system and traverse the tree recursively.

    The navigation device provides a trajectory, generally as a list of nodes
    over time. Each node has at least 7 parameters, i.e., a timestamp, longitude,
    latitude, height, roll, pitch and yaw.

    The roll, pitch and yaw angles transform from the body coordinate system into
    a locally levelled coordinate system - called NED - with the x-axis pointing
    towards north, y-axis towards east and z-axis pointing downwards.

    Transformation from BODY to NED is given as:

      :math:`R_{BODY2NED} = R_z(yaw) \cdot R_y(pitch) \cdot R_x(roll)`

    Transformation from NED to ECEF is given as:

      :math:`R_{NED2ECEF} = \begin{pmatrix} -cos(\lambda) sin(\phi) & -sin(\lambda) & -cos(\lambda) cos(\phi) \\ -sin(\lambda) sin(\phi) &  cos(\lambda) & -sin(\lambda) cos(\phi) \\ cos(\phi) & 0 & -sin(\phi) \end{pmatrix}`

      :math:`T_{NED2ECEF} = \begin{pmatrix} (vh + h) cos(\phi) cos(\lambda) \\ (vh + h) cos(\phi) sin(\lambda) \\ (vh (1 - f (2 - f)) + h) sin(\phi) \end{pmatrix}`

      :math:`vh = { a \over \sqrt{1 - f (2 - f) sin(\phi)^2} }`

    with :math:`\lambda`,  :math:`\phi` and :math:`h` being longitude, latitude and
    height and :math:`a`, :math:`f` being ellipsoid semi-major axis and flattening (e.g.
    WGS84 ellipsoid: :math:`a = 6378137` and :math:`f = 1/298.257223563`).

    Since version 1.0
    """

    __slots__ = [
        "__primary_imu",
        "__secondary_imu",
        "__dmi",
        "__gnss_receiver",
        "__ancillary_gnss_receiver",
        "__levelling_mount",
        "__trajectory_offsets",
        "__time_system"
    ]

    def __init__(self):
        super().__init__()
        self.__primary_imu = str()
        self.__secondary_imu = str()
        self.__dmi = str()
        self.__gnss_receiver = str()
        self.__ancillary_gnss_receiver = str()
        self.__levelling_mount = str()
        self.__trajectory_offsets = None
        self.__time_system = "UTC"

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("primary_imu=" + repr(self.primary_imu))
        result.append("secondary_imu=" + repr(self.secondary_imu))
        result.append("dmi=" + repr(self.dmi))
        result.append("gnss_receiver=" + repr(self.gnss_receiver))
        result.append("ancillary_gnss_receiver=" + repr(self.ancillary_gnss_receiver))
        result.append("levelling_mount=" + repr(self.levelling_mount))
        result.append("trajectory_offsets=" + repr(self.trajectory_offsets))
        result.append("time_system=" + repr(self.time_system))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def primary_imu(self):
        r"""
        Name of IMU component

        Type: String

        Since version 1.0
        """
        return self.__primary_imu

    @primary_imu.setter
    def primary_imu(self, value):
        if value is None:
            raise ValueError("field \"primary_imu\" is required")

        if isinstance(value, Component):
            value = value.name

        value = str(value)
        self.__primary_imu = value

    @property
    def secondary_imu(self):
        r"""
        Name of optional IMU component (empty: none)

        Type: String

        Since version 1.0
        """
        return self.__secondary_imu

    @secondary_imu.setter
    def secondary_imu(self, value):
        if value is None:
            raise ValueError("field \"secondary_imu\" is required")

        if isinstance(value, Component):
            value = value.name

        value = str(value)
        self.__secondary_imu = value

    @property
    def dmi(self):
        r"""
        Name of optional DMI component (empty: none)

        Type: String

        Since version 1.0
        """
        return self.__dmi

    @dmi.setter
    def dmi(self, value):
        if value is None:
            raise ValueError("field \"dmi\" is required")

        if isinstance(value, Component):
            value = value.name

        value = str(value)
        self.__dmi = value

    @property
    def gnss_receiver(self):
        r"""
        Name of GNSSReceiver component

        Type: String

        Since version 1.0
        """
        return self.__gnss_receiver

    @gnss_receiver.setter
    def gnss_receiver(self, value):
        if value is None:
            raise ValueError("field \"gnss_receiver\" is required")

        if isinstance(value, Component):
            value = value.name

        value = str(value)
        self.__gnss_receiver = value

    @property
    def ancillary_gnss_receiver(self):
        r"""
        Name of optional ancillary GNSSReceiver component

        Type: String

        Since version 1.2.4
        """
        return self.__ancillary_gnss_receiver

    @ancillary_gnss_receiver.setter
    def ancillary_gnss_receiver(self, value):
        if value is None:
            raise ValueError("field \"ancillary_gnss_receiver\" is required")

        if isinstance(value, Component):
            value = value.name

        value = str(value)
        self.__ancillary_gnss_receiver = value

    @property
    def levelling_mount(self):
        r"""
        Name of optional LevellingMount component (empty: none)

        Type: String

        Since version 1.0
        """
        return self.__levelling_mount

    @levelling_mount.setter
    def levelling_mount(self, value):
        if value is None:
            raise ValueError("field \"levelling_mount\" is required")

        if isinstance(value, Component):
            value = value.name

        value = str(value)
        self.__levelling_mount = value

    @property
    def trajectory_offsets(self):
        r"""
        Offsets to be applied to the trajectory generated by the navigation device

        Type: Optional TrajectoryOffsets

        Since version 1.0
        """
        return self.__trajectory_offsets

    @trajectory_offsets.setter
    def trajectory_offsets(self, value):
        if value is None:
            self.__trajectory_offsets = None
            return

        if not isinstance(value, TrajectoryOffsets):
            raise ValueError("expected instance of TrajectoryOffsets")

        self.__trajectory_offsets = value

    @property
    def time_system(self):
        r"""
        Time system (time standard)

        Typical time bases of a navigation device are either UTC (universal
        time coordinated) or GPS time, starting with the January 6th, 1980.

        Type: String

        Since version 1.0
        """
        return self.__time_system

    @time_system.setter
    def time_system(self, value):
        if value is None:
            raise ValueError("field \"time_system\" is required")

        value = str(value)
        self.__time_system = value


# ______________________________________________________________________________
#
class LidarDevice(Component):
    r"""
    Laser scanner

    This class represents a laser scanner.

    Note: Do not use this class directly, but one of the derived classes.

    Derived classes:
      - LidarDevice2D
      - LidarDevice3D

    Since version 1.0
    """

    __slots__ = ["__device_geometry", "__signature"]

    def __init__(self):
        super().__init__()
        self.__device_geometry = list()
        self.__signature = None

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("device_geometry=" + repr(self.device_geometry))
        result.append("signature=" + repr(self.signature))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def device_geometry(self):
        r"""
        Lidar device geometry

        Depending on the device type, there may be multiple device geometry
        objects for a single lidar device, with each object corresponding to
        a specific device configuration.

        Type: List of LidarDeviceGeometry

        Since version 1.0
        """
        return self.__device_geometry

    @device_geometry.setter
    def device_geometry(self, value):
        if value is None:
            raise ValueError("field \"device_geometry\" is required")

        value = list(filter(None, value))
        if not all(isinstance(item, LidarDeviceGeometry) for item in value):
            raise ValueError("expected instance of LidarDeviceGeometry")

        self.__device_geometry = value

    @property
    def signature(self):
        r"""
        Optional digital signature

        The signature can be used to detect modifications of the device geometry.
        Details see ``create_signature()`` and ``verify_signature()`` functions.

        Type: Optional String

        Deprecated since version 1.3.0, may be removed in future versions
        """
        return self.__signature

    @signature.setter
    def signature(self, value):
        if value is None:
            self.__signature = None
            return

        value = str(value)
        self.__signature = value


# ______________________________________________________________________________
#
class LidarDevice2D(LidarDevice):
    r"""
    2D laser scanner

    This class represents a 2D laser scanner, e.g. RIEGL VUX-240

    Since version 1.0
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("device_geometry=" + repr(self.device_geometry))
        result.append("signature=" + repr(self.signature))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class LidarDevice3D(LidarDevice):
    r"""
    3D laser scanner

    This class represents a 3D laser scanner, e.g. RIEGL VZ-400i

    Since version 1.0
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("device_geometry=" + repr(self.device_geometry))
        result.append("signature=" + repr(self.signature))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class BaseCameraDevice(Component):
    r"""
    Base camera device

    Note: Do not use this class directly, but one of the derived classes.

    Derived classes:
      - CameraDevice
      - HyperspectralCameraDevice
      - PanoramaCameraDevice

    Since version 1.3.3
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class CameraDevice(BaseCameraDevice):
    r"""
    Base camera device

    This class represents a frame camera (with a 2D sensor).

    Note: Do not use this class directly, but one of the derived classes.

    Derived classes:
      - CameraDeviceThermal
      - CameraDeviceTruecolor

    Since version 1.3.3
    """

    __slots__ = [
        "__lens",
        "__shutter",
        "__axes",
        "__field_of_view",
        "__device_geometry",
        "__mirror_facet",
        "__calibrated"
    ]

    def __init__(self):
        super().__init__()
        self.__lens = TypePlate()
        self.__shutter = "global-shutter"
        self.__axes = CameraDeviceAxes()
        self.__field_of_view = None
        self.__device_geometry = CameraDeviceGeometry()
        self.__mirror_facet = ""
        self.__calibrated = None

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("lens=" + repr(self.lens))
        result.append("shutter=" + repr(self.shutter))
        result.append("axes=" + repr(self.axes))
        result.append("field_of_view=" + repr(self.field_of_view))
        result.append("device_geometry=" + repr(self.device_geometry))
        result.append("mirror_facet=" + repr(self.mirror_facet))
        result.append("calibrated=" + repr(self.calibrated))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def lens(self):
        r"""
        Camera lens identification

        Type: TypePlate

        Since version 1.3.3
        """
        return self.__lens

    @lens.setter
    def lens(self, value):
        if value is None:
            raise ValueError("field \"lens\" is required")

        if not isinstance(value, TypePlate):
            raise ValueError("expected instance of TypePlate")

        self.__lens = value

    @property
    def shutter(self):
        r"""
        Camera shutter type

        Indicates the type of shutter. One out of:

          - ``leaf-shutter`` (hardware shutter; operating from the center)
          - ``focal-plane-shutter`` (hardware shutter; operating from top to bottom)
          - ``global-shutter`` (electronic shutter; a full frame is transferred at one time)
          - ``rolling-shutter`` (electronic shutter; scans the image in a line-by-line fashion)

        Type: String

        Since version 1.2.2
        """
        return self.__shutter

    @shutter.setter
    def shutter(self, value):
        if value is None:
            raise ValueError("field \"shutter\" is required")

        value = str(value)
        self.__shutter = value

    @property
    def axes(self):
        r"""
        Camera coordinate system axes

        Type: CameraDeviceAxes

        Since version 1.3.3
        """
        return self.__axes

    @axes.setter
    def axes(self, value):
        if value is None:
            raise ValueError("field \"axes\" is required")

        if not isinstance(value, CameraDeviceAxes):
            raise ValueError("expected instance of CameraDeviceAxes")

        self.__axes = value

    @property
    def field_of_view(self):
        r"""
        Camera device field of view

        Type: Optional CameraDeviceFieldOfView

        Since version 1.2.5
        """
        return self.__field_of_view

    @field_of_view.setter
    def field_of_view(self, value):
        if value is None:
            self.__field_of_view = None
            return

        if not isinstance(value, CameraDeviceFieldOfView):
            raise ValueError("expected instance of CameraDeviceFieldOfView")

        self.__field_of_view = value

    @property
    def device_geometry(self):
        r"""
        Camera device geometry

        Type: CameraDeviceGeometry

        Since version 1.3.3
        """
        return self.__device_geometry

    @device_geometry.setter
    def device_geometry(self, value):
        if value is None:
            raise ValueError("field \"device_geometry\" is required")

        if not isinstance(value, CameraDeviceGeometry):
            raise ValueError("expected instance of CameraDeviceGeometry")

        self.__device_geometry = value

    @property
    def mirror_facet(self):
        r"""
        Name of optional MirrorFacet component (empty: none)

        The mirror facet is required for some lidar devices with integrated cameras (e.g. VZ-4000),
        where the images are captured via the scan mirror. In order to calculate the (virtual)
        camera pose for an image, the mirror facet, line angle and frame angle must be provided
        to the ``PoseCalculator`` class.

        Type: String

        Since version 1.3.2
        """
        return self.__mirror_facet

    @mirror_facet.setter
    def mirror_facet(self, value):
        if value is None:
            raise ValueError("field \"mirror_facet\" is required")

        if isinstance(value, Component):
            value = value.name

        value = str(value)
        self.__mirror_facet = value

    @property
    def calibrated(self):
        r"""
        Calibration state

        Indicates whether the `pose` and `device_geometry` properties
        have been determined or adjusted by a calibration process.

          - ``true``: The camera has been calibrated.
          - ``false``: The camera has not yet been calibrated.
          - ``undefined``: If no value is specified, the calibration state
            is unknown. This can occur with files that were created before
            the "calibrated" property was introduced in version 1.2.12.

        Type: Optional Boolean

        Since version 1.2.12
        """
        return self.__calibrated

    @calibrated.setter
    def calibrated(self, value):
        if value is None:
            self.__calibrated = None
            return

        value = bool(value)
        self.__calibrated = value


# ______________________________________________________________________________
#
class CameraDeviceTruecolor(CameraDevice):
    r"""
    True color camera device

    This class represents a frame camera that provides true color images.

    Since version 1.3.3
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("lens=" + repr(self.lens))
        result.append("shutter=" + repr(self.shutter))
        result.append("axes=" + repr(self.axes))
        result.append("field_of_view=" + repr(self.field_of_view))
        result.append("device_geometry=" + repr(self.device_geometry))
        result.append("mirror_facet=" + repr(self.mirror_facet))
        result.append("calibrated=" + repr(self.calibrated))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class CameraDeviceThermal(CameraDevice):
    r"""
    Thermal camera device

    This class represents a frame camera that provides thermal (infrared) images.

    Since version 1.3.3
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("lens=" + repr(self.lens))
        result.append("shutter=" + repr(self.shutter))
        result.append("axes=" + repr(self.axes))
        result.append("field_of_view=" + repr(self.field_of_view))
        result.append("device_geometry=" + repr(self.device_geometry))
        result.append("mirror_facet=" + repr(self.mirror_facet))
        result.append("calibrated=" + repr(self.calibrated))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
class HyperspectralCameraDevice(BaseCameraDevice):
    r"""
    Hyperspectral camera device

    This class represents a push-broom-type hyperspectral camera that provides
    hyperspectral data. The data represent spatially a single-line-camera with
    spectral information in a large number of consecutive wavelength bands for
    every pixel.

    Since version 1.3.4
    """

    __slots__ = [
        "__lens",
        "__axes",
        "__field_of_view",
        "__device_geometry",
        "__minimum_wavelength",
        "__maximum_wavelength",
        "__number_of_channels"
    ]

    def __init__(self):
        super().__init__()
        self.__lens = TypePlate()
        self.__axes = CameraDeviceAxes()
        self.__field_of_view = None
        self.__device_geometry = None
        self.__minimum_wavelength = 0.0
        self.__maximum_wavelength = 0.0
        self.__number_of_channels = 0

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("lens=" + repr(self.lens))
        result.append("axes=" + repr(self.axes))
        result.append("field_of_view=" + repr(self.field_of_view))
        result.append("device_geometry=" + repr(self.device_geometry))
        result.append("minimum_wavelength=" + repr(self.minimum_wavelength))
        result.append("maximum_wavelength=" + repr(self.maximum_wavelength))
        result.append("number_of_channels=" + repr(self.number_of_channels))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def lens(self):
        r"""
        Camera lens identification

        Type: TypePlate

        Since version 1.3.4
        """
        return self.__lens

    @lens.setter
    def lens(self, value):
        if value is None:
            raise ValueError("field \"lens\" is required")

        if not isinstance(value, TypePlate):
            raise ValueError("expected instance of TypePlate")

        self.__lens = value

    @property
    def axes(self):
        r"""
        Camera coordinate system axes

        Type: CameraDeviceAxes

        Since version 1.3.4
        """
        return self.__axes

    @axes.setter
    def axes(self, value):
        if value is None:
            raise ValueError("field \"axes\" is required")

        if not isinstance(value, CameraDeviceAxes):
            raise ValueError("expected instance of CameraDeviceAxes")

        self.__axes = value

    @property
    def field_of_view(self):
        r"""
        Camera device field of view

        Type: Optional CameraDeviceFieldOfView

        Since version 1.3.4
        """
        return self.__field_of_view

    @field_of_view.setter
    def field_of_view(self, value):
        if value is None:
            self.__field_of_view = None
            return

        if not isinstance(value, CameraDeviceFieldOfView):
            raise ValueError("expected instance of CameraDeviceFieldOfView")

        self.__field_of_view = value

    @property
    def device_geometry(self):
        r"""
        Camera device geometry

        Type: Optional CameraDeviceGeometry

        Since version 1.3.4
        """
        return self.__device_geometry

    @device_geometry.setter
    def device_geometry(self, value):
        if value is None:
            self.__device_geometry = None
            return

        if not isinstance(value, CameraDeviceGeometry):
            raise ValueError("expected instance of CameraDeviceGeometry")

        self.__device_geometry = value

    @property
    def minimum_wavelength(self):
        r"""
        Minimum wavelength of the spectral range in units of nm

        Type: Float64
        Unit: nm

        Since version 1.3.4
        """
        return self.__minimum_wavelength

    @minimum_wavelength.setter
    def minimum_wavelength(self, value):
        if value is None:
            raise ValueError("field \"minimum_wavelength\" is required")

        value = float(value)
        self.__minimum_wavelength = value

    @property
    def maximum_wavelength(self):
        r"""
        Maximum wavelength of the spectral range in units of nm

        Type: Float64
        Unit: nm

        Since version 1.3.4
        """
        return self.__maximum_wavelength

    @maximum_wavelength.setter
    def maximum_wavelength(self, value):
        if value is None:
            raise ValueError("field \"maximum_wavelength\" is required")

        value = float(value)
        self.__maximum_wavelength = value

    @property
    def number_of_channels(self):
        r"""
        Number of spectral channels

        Type: UInt32

        Since version 1.3.4
        """
        return self.__number_of_channels

    @number_of_channels.setter
    def number_of_channels(self, value):
        if value is None:
            raise ValueError("field \"number_of_channels\" is required")

        value = int(value)
        self.__number_of_channels = value


# ______________________________________________________________________________
#
class PanoramaCameraDevice(BaseCameraDevice):
    r"""
    Base panorama camera device

    This class represents a (pseudo) panorama camera.

    The coordinate system is always defined as a Cartesian coordinate system with
    a corresponding polar coordinate system with an azimuth angle "phi" around the
    z-axis, starting from the positive x-axis towards the y-axis, and a polar angle
    "theta" starting from the positive z-axis towards the negative z-axis.

    Note: Do not use this class directly, but one of the derived classes.

    Derived classes:
      - PanoramaCameraDeviceCubic
      - PanoramaCameraDeviceCylindrical
      - PanoramaCameraDeviceSpherical

    Since version 1.2.9
    """

    __slots__ = ["__field_of_view"]

    def __init__(self):
        super().__init__()
        self.__field_of_view = PanoramaCameraDeviceFieldOfView()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("field_of_view=" + repr(self.field_of_view))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def field_of_view(self):
        r"""
        Panorama camera device field of view

        Type: PanoramaCameraDeviceFieldOfView

        Since version 1.2.9
        """
        return self.__field_of_view

    @field_of_view.setter
    def field_of_view(self, value):
        if value is None:
            raise ValueError("field \"field_of_view\" is required")

        if not isinstance(value, PanoramaCameraDeviceFieldOfView):
            raise ValueError("expected instance of PanoramaCameraDeviceFieldOfView")

        self.__field_of_view = value


# ______________________________________________________________________________
#
class PanoramaCameraDeviceSpherical(PanoramaCameraDevice):
    r"""
    Spherical panorama camera device

    This class represents a panorama camera that projects the scene onto a sphere
    to capture a 360-degree panoramic view.

    Spherical projection (point coordinates to pixel coordinates):

      :math:`r = \sqrt{x^2 + y^2 + z^2}`

      :math:`phi = \arctan2(y, x)`

    If `phi` is less than or equal to `phi_min`, 360 degrees are added so that `phi_min < phi <= phi_max` applies.

      :math:`theta = \arccos{z \over r}`

    Make sure that `theta_min <= theta < theta_max` applies.

      :math:`u = {{phi_{max} - phi} \over {phi_{max} - phi_{min}}} \cdot n_x`

      :math:`v = {{theta - theta_{min}} \over {theta_{max} - theta_{min}}} \cdot n_y`

    The origin of the u and v axes is in the upper left corner of the image,
    the u axis runs from left to right and the v axis runs from top to bottom.

    Inverse projection (pixel coordinates to direction vector):

      :math:`phi = phi_{max} - {{phi_{max} - phi_{min}} \over {n_x}} \cdot u`

      :math:`theta = theta_{min} + {{theta_{max} - theta_{min}} \over {n_y}} \cdot v`

      :math:`d = \begin{pmatrix} \cos(phi) \cdot \sin(theta) \\ \sin(phi) \cdot \sin(theta) \\ \cos(theta) \end{pmatrix}`

    Since version 1.2.9
    """

    __slots__ = ["__image_width", "__image_height"]

    def __init__(self):
        super().__init__()
        self.__image_width = 0
        self.__image_height = 0

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("field_of_view=" + repr(self.field_of_view))
        result.append("image_width=" + repr(self.image_width))
        result.append("image_height=" + repr(self.image_height))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def image_width(self):
        r"""
        Panorama image width (nx)

        Type: UInt32
        Unit: pix

        Since version 1.2.9
        """
        return self.__image_width

    @image_width.setter
    def image_width(self, value):
        if value is None:
            raise ValueError("field \"image_width\" is required")

        value = int(value)
        self.__image_width = value

    @property
    def image_height(self):
        r"""
        Panorama image height (ny)

        Type: UInt32
        Unit: pix

        Since version 1.2.9
        """
        return self.__image_height

    @image_height.setter
    def image_height(self, value):
        if value is None:
            raise ValueError("field \"image_height\" is required")

        value = int(value)
        self.__image_height = value


# ______________________________________________________________________________
#
class PanoramaCameraDeviceCylindrical(PanoramaCameraDevice):
    r"""
    Cylindrical panorama camera device

    This class represents a panorama camera that projects the scene onto a cylinder
    to create a panoramic image that captures a wide horizontal field of view, but
    not a full 360-degree view around the vertical axis.

    Cylindrical projection (point coordinates to pixel coordinates):

      :math:`r = \sqrt{x^2 + y^2}`

      :math:`phi = \arctan2(y, x)`

    If `phi` is less than or equal to `phi_min`, 360 degrees are added so that `phi_min < phi <= phi_max` applies.

      :math:`h = {z \over r}`

      :math:`h_{max} = \tan({\pi \over 2} - theta_{min})`

      :math:`h_{min} = \tan({\pi \over 2} - theta_{max})`

    Make sure that `h_min < h <= h_max` applies.

      :math:`u = {{phi_{max} - phi} \over {phi_{max} - phi_{min}}} \cdot n_x`

      :math:`v = {{h_{max} - h} \over {h_{max} - h_{min}}} \cdot n_y`

    The origin of the u and v axes is in the upper left corner of the image,
    the u axis runs from left to right and the v axis runs from top to bottom.

    Inverse projection (pixel coordinates to direction vector):

      :math:`phi = phi_{max} - {{phi_{max} - phi_{min}} \over {n_x}} \cdot u`

      :math:`h = h_{max} - {{h_{max} - h_{min}} \over {n_y}} \cdot v`

      :math:`d = \begin{pmatrix} \cos(phi) \\ \sin(phi) \\ h \end{pmatrix}`

    Since version 1.2.9
    """

    __slots__ = ["__image_width", "__image_height"]

    def __init__(self):
        super().__init__()
        self.__image_width = 0
        self.__image_height = 0

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("field_of_view=" + repr(self.field_of_view))
        result.append("image_width=" + repr(self.image_width))
        result.append("image_height=" + repr(self.image_height))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def image_width(self):
        r"""
        Panorama image width (nx)

        Type: UInt32
        Unit: pix

        Since version 1.2.9
        """
        return self.__image_width

    @image_width.setter
    def image_width(self, value):
        if value is None:
            raise ValueError("field \"image_width\" is required")

        value = int(value)
        self.__image_width = value

    @property
    def image_height(self):
        r"""
        Panorama image height (ny)

        Type: UInt32
        Unit: pix

        Since version 1.2.9
        """
        return self.__image_height

    @image_height.setter
    def image_height(self, value):
        if value is None:
            raise ValueError("field \"image_height\" is required")

        value = int(value)
        self.__image_height = value


# ______________________________________________________________________________
#
class PanoramaCameraDeviceCubic(PanoramaCameraDevice):
    r"""
    Cubic panorama camera device

    This class represents a panorama camera that projects the scene onto a cube
    to capture multiple panoramic views, one for each face of the cube, allowing
    for a full 360-degree view both horizontally and vertically.

    Cube map layout (horizontal cross layout): ::

       o-- u         +-------------+
       |             |     TOP     |
       v             |             |
                     |-x    z      |
                     | |           |
                     | o--  y      |
       +-------------+-------------+-------------+-------------+
       |    LEFT     |    FRONT    |    RIGHT    |    BACK     |
       |             |             |             |             |
       | z    y      | z    x      | z   -y      | z   -x      |
       | |           | |           | |           | |           |
       | o-- -x      | o--  y      | o--  x      | o-- -y      |
       +-------------+-------------+-------------+-------------+
                     |   BOTTOM    |
                     |             |
                     | x   -z      |
                     | |           |
                     | o--  y      |
                     +-------------+

       Note: The x, y and z axes are as seen from the outside of the cube.

       The origin of the u and v axes is in the upper left corner of the image,
       the u axis runs from left to right and the v axis runs from top to bottom.

    Example Python code for cube map projection:

    .. code-block:: python
       :linenos:

       def xyz_to_uv(x: float, y: float, z: float) -> Tuple[float, float]:
           '''
           Convert point coordinates to normalized pixel coordinates
           '''
           absx, absy, absz = abs(x), abs(y), abs(z)

           if absx >= absy and absx >= absz:
               if x >= 0.0:
                   u, v, w, du, dv = -y, -z, absx, 1.0, 1.0  # FRONT
               else:
                   u, v, w, du, dv = y, -z, absx, 3.0, 1.0  # BACK
           elif absy >= absx and absy >= absz:
               if y >= 0.0:
                   u, v, w, du, dv = x, -z, absy, 0.0, 1.0  # LEFT
               else:
                   u, v, w, du, dv = -x, -z, absy, 2.0, 1.0  # RIGHT
           elif absz >= absx and absz >= absy:
               if z >= 0.0:
                   u, v, w, du, dv = -y, x, absz, 1.0, 0.0  # TOP
               else:
                   u, v, w, du, dv = -y, -x, absz, 1.0, 2.0  # BOTTOM

           u = (du + (u / w + 1.0) / 2.0) / 4.0
           v = (dv + (v / w + 1.0) / 2.0) / 3.0
           return u, v  # both in range 0 to 1

       def uv_to_xyz(u: float, v: float) -> Tuple[float, float, float]:
           '''
           Convert normalized pixel coordinates to direction vector
           '''
           u *= 4.0
           v *= 3.0

           if u <= 1.0:
               v -= 1.0
               x, z, y = u, -v, 1.0  # LEFT
           elif u <= 2.0:
               u -= 1.0
               if v <= 1.0:
                   y, x, z = -u, v, 1.0  # TOP
               elif v <= 2.0:
                   v -= 1.0
                   y, z, x = -u, -v, 1.0  # FRONT
               elif v <= 3.0:
                   v -= 2.0
                   y, x, z = -u, -v, 0.0  # BOTTOM
           elif u <= 3.0:
               u -= 2.0
               v -= 1.0
               x, z, y = -u, -v, 0.0  # RIGHT
           elif u <= 4.0:
               u -= 3.0
               v -= 1.0
               y, z, x = u, -v, 0.0  # BACK

           x = (abs(x) * 2.0 - 1.0) * math.copysign(1.0, x)
           y = (abs(y) * 2.0 - 1.0) * math.copysign(1.0, y)
           z = (abs(z) * 2.0 - 1.0) * math.copysign(1.0, z)
           w = math.sqrt(x * x + y * y + z * z)
           return x / w, y / w, z / w

    Since version 1.2.9
    """

    __slots__ = ["__image_size"]

    def __init__(self):
        super().__init__()
        self.__image_size = 0

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("field_of_view=" + repr(self.field_of_view))
        result.append("image_size=" + repr(self.image_size))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"

    @property
    def image_size(self):
        r"""
        Edge length (n) of a single cube face image

        Type: UInt32
        Unit: pix

        Since version 1.2.9
        """
        return self.__image_size

    @image_size.setter
    def image_size(self, value):
        if value is None:
            raise ValueError("field \"image_size\" is required")

        value = int(value)
        self.__image_size = value


# ______________________________________________________________________________
#
class CameraSystem(Component):
    r"""
    Camera system consisting of more than a single CameraDevice

    Using a camera system in a RIEGL System Description is of advantage, e.g.,
    in case of modelling pseudo-panoramic cameras. A pseudo-panoramic camera
    is a set of non-parametric cameras oriented differently to cover in total
    a very wide field of view, e.g., 360 deg x 135 deg. Every camera itself is
    usually calibrated and its pose is usually long-term stable with respect
    to the coordinate system of the CameraSystem. Each non-parametric camera
    is modelled by a CameraDevice as part of the property ``components``, with
    its own pose transforming into the coordinate system of the CameraSystem.

    Since version 1.0
    """

    __slots__ = []

    def __init__(self):
        super().__init__()

    def __repr__(self) -> str:
        result = list()
        result.append("id=" + repr(self.id))
        result.append("name=" + repr(self.name))
        result.append("device=" + repr(self.device))
        result.append("comments=" + repr(self.comments))
        result.append("changelog=" + repr(self.changelog))
        result.append("pose=" + repr(self.pose))
        result.append("weight=" + repr(self.weight))
        result.append("permanent_magnetic_field=" + repr(self.permanent_magnetic_field))
        result.append("components=" + repr(len(self.components or [])))
        return type(self).__name__ + "(" + str(", ").join(result) + ")"


# ______________________________________________________________________________
#
def class_name(thing: Object, pretty: bool = False) -> str:
    """
    Get class name of given object

    Returns the technical class name of an object. To get a string
    that can be displayed to the end user, set 'pretty' to 'True'.

    Examples:
      class_name(LidarDevice3D, False) = "LidarDevice3D"
      class_name(LidarDevice3D, True)  = "Lidar Device 3D"

    Since version 1.2.3
    """
    if not thing:
        return str()
    if thing.__class__ is CameraSystem:
        return "Camera System" if pretty else "CameraSystem"
    if thing.__class__ is PanoramaCameraDeviceCubic:
        return "Panorama Camera Device Cubic" if pretty else "PanoramaCameraDeviceCubic"
    if thing.__class__ is PanoramaCameraDeviceCylindrical:
        return "Panorama Camera Device Cylindrical" if pretty else "PanoramaCameraDeviceCylindrical"
    if thing.__class__ is PanoramaCameraDeviceSpherical:
        return "Panorama Camera Device Spherical" if pretty else "PanoramaCameraDeviceSpherical"
    if thing.__class__ is PanoramaCameraDevice:
        return "Panorama Camera Device" if pretty else "PanoramaCameraDevice"
    if thing.__class__ is HyperspectralCameraDevice:
        return "Hyperspectral Camera Device" if pretty else "HyperspectralCameraDevice"
    if thing.__class__ is CameraDeviceThermal:
        return "Camera Device Thermal" if pretty else "CameraDeviceThermal"
    if thing.__class__ is CameraDeviceTruecolor:
        return "Camera Device Truecolor" if pretty else "CameraDeviceTruecolor"
    if thing.__class__ is CameraDevice:
        return "Camera Device" if pretty else "CameraDevice"
    if thing.__class__ is BaseCameraDevice:
        return "Base Camera Device" if pretty else "BaseCameraDevice"
    if thing.__class__ is LidarDevice3D:
        return "Lidar Device 3D" if pretty else "LidarDevice3D"
    if thing.__class__ is LidarDevice2D:
        return "Lidar Device 2D" if pretty else "LidarDevice2D"
    if thing.__class__ is LidarDevice:
        return "Lidar Device" if pretty else "LidarDevice"
    if thing.__class__ is NavigationDevice:
        return "Navigation Device" if pretty else "NavigationDevice"
    if thing.__class__ is EventDevice:
        return "Event Device" if pretty else "EventDevice"
    if thing.__class__ is DMI:
        return "DMI" if pretty else "DMI"
    if thing.__class__ is GNSSReceiver:
        return "GNSS Receiver" if pretty else "GNSSReceiver"
    if thing.__class__ is GNSSAntenna:
        return "GNSS Antenna" if pretty else "GNSSAntenna"
    if thing.__class__ is IMU:
        return "IMU" if pretty else "IMU"
    if thing.__class__ is MountingInterface:
        return "Mounting Interface" if pretty else "MountingInterface"
    if thing.__class__ is ContinuousMount:
        return "Continuous Mount" if pretty else "ContinuousMount"
    if thing.__class__ is DiscreteMount:
        return "Discrete Mount" if pretty else "DiscreteMount"
    if thing.__class__ is LevellingMount:
        return "Levelling Mount" if pretty else "LevellingMount"
    if thing.__class__ is AdjustableMount:
        return "Adjustable Mount" if pretty else "AdjustableMount"
    if thing.__class__ is StaticMount:
        return "Static Mount" if pretty else "StaticMount"
    if thing.__class__ is CarrierPlatform:
        return "Carrier Platform" if pretty else "CarrierPlatform"
    if thing.__class__ is MirrorFacet:
        return "Mirror Facet" if pretty else "MirrorFacet"
    if thing.__class__ is Housing:
        return "Housing" if pretty else "Housing"
    if thing.__class__ is Document:
        return "Document" if pretty else "Document"
    if thing.__class__ is System:
        return "System" if pretty else "System"
    if thing.__class__ is Component:
        return "Component" if pretty else "Component"
    if thing.__class__ is PanoramaCameraDeviceFieldOfView:
        return "Panorama Camera Device Field Of View" if pretty else "PanoramaCameraDeviceFieldOfView"
    if thing.__class__ is CameraDeviceGeometry:
        return "Camera Device Geometry" if pretty else "CameraDeviceGeometry"
    if thing.__class__ is CameraDeviceGeometryPixelToRay:
        return "Camera Device Geometry Pixel To Ray" if pretty else "CameraDeviceGeometryPixelToRay"
    if thing.__class__ is CameraDeviceGeometryRayToPixelRIEGL:
        return "Camera Device Geometry Ray To Pixel RIEGL" if pretty else "CameraDeviceGeometryRayToPixelRIEGL"
    if thing.__class__ is CameraDeviceGeometryRayToPixel:
        return "Camera Device Geometry Ray To Pixel" if pretty else "CameraDeviceGeometryRayToPixel"
    if thing.__class__ is CameraDeviceGeometryPixelToPixelRIEGL:
        return "Camera Device Geometry Pixel To Pixel RIEGL" if pretty else "CameraDeviceGeometryPixelToPixelRIEGL"
    if thing.__class__ is CameraDeviceGeometryPixelToPixel:
        return "Camera Device Geometry Pixel To Pixel" if pretty else "CameraDeviceGeometryPixelToPixel"
    if thing.__class__ is CameraDeviceFieldOfView:
        return "Camera Device Field Of View" if pretty else "CameraDeviceFieldOfView"
    if thing.__class__ is CameraDeviceAxes:
        return "Camera Device Axes" if pretty else "CameraDeviceAxes"
    if thing.__class__ is LidarDeviceGeometry:
        return "Lidar Device Geometry" if pretty else "LidarDeviceGeometry"
    if thing.__class__ is LidarDeviceBeamGeometry:
        return "Lidar Device Beam Geometry" if pretty else "LidarDeviceBeamGeometry"
    if thing.__class__ is LidarDeviceGeometryUnits:
        return "Lidar Device Geometry Units" if pretty else "LidarDeviceGeometryUnits"
    if thing.__class__ is LidarDeviceGeometryPacket:
        return "Lidar Device Geometry Packet" if pretty else "LidarDeviceGeometryPacket"
    if thing.__class__ is RawDataPacket:
        return "Raw Data Packet" if pretty else "RawDataPacket"
    if thing.__class__ is TrajectoryOffsets:
        return "Trajectory Offsets" if pretty else "TrajectoryOffsets"
    if thing.__class__ is PoseAdjustmentSettings:
        return "Pose Adjustment Settings" if pretty else "PoseAdjustmentSettings"
    if thing.__class__ is NamedPose:
        return "Named Pose" if pretty else "NamedPose"
    if thing.__class__ is Pose:
        return "Pose" if pretty else "Pose"
    if thing.__class__ is TypePlate:
        return "Type Plate" if pretty else "TypePlate"
    if thing.__class__ is Group:
        return "Group" if pretty else "Group"
    if thing.__class__ is NamedObject:
        return "Named Object" if pretty else "NamedObject"
    if thing.__class__ is Object:
        return "Object" if pretty else "Object"
    return "unknown"


# ______________________________________________________________________________
#
def document_parse_json(document: str, path: str = "") -> Document:
    """
    Parse system description

    Parse system description from JSON format and return result document.
    Throws an exception in case of JSON parsing errors (corrupt format).

    The optional string 'path' defines which object of the JSON document
    contains the system description (JSON pointer syntax as per RFC 6901).
    """
    return _private_read_json(
        _private_resolve_json(json.loads(document), path),
        None, "Document", "/document"
    )


# ______________________________________________________________________________
#
def document_store_json(document: Document, minify: bool = False) -> str:
    """
    Create system description

    Serialize system description to JSON format.

    Set 'minify' to True to get a compact output without superfluous whitespace.
    """
    return json.dumps(
        _private_post_json(document),
        indent=None if minify else 4,
        separators=(',', ':') if minify else None,
        ensure_ascii=False
    )


# ______________________________________________________________________________
#
def document_compare(one: Document, two: Document) -> bool:
    """
    Compare system description documents

    Returns True if documents 'one' and 'two' are equal.
    """
    return document_store_json(one) == document_store_json(two)


# ______________________________________________________________________________
#
def component_parse_json(component: str, path: str = "") -> Component:
    """
    Parse component description

    Parse component description from JSON format and return result component.
    Throws an exception in case of JSON parsing errors (corrupt format).

    The optional string 'path' defines which object of the JSON document
    contains the component description (JSON pointer syntax as per RFC 6901).
    """
    return _private_read_json(
        _private_resolve_json(json.loads(component), path),
        None, "Component", "/component"
    )


# ______________________________________________________________________________
#
def component_store_json(component: Component, minify: bool = False) -> str:
    """
    Create component description

    Serialize component description to JSON format.

    Set 'minify' to True to get a compact output without superfluous whitespace.
    """
    return json.dumps(
        _private_post_json(component),
        indent=None if minify else 4,
        separators=(',', ':') if minify else None,
        ensure_ascii=False
    )


# ______________________________________________________________________________
#
def component_compare(one: Component, two: Component) -> bool:
    """
    Compare system description components

    Returns True if components 'one' and 'two' are equal.
    """
    return component_store_json(one) == component_store_json(two)


# ______________________________________________________________________________
#
def component_tree(start: Object) -> str:
    """
    Get text representation of the component tree

    Returns a string that contains the name and class name of the given
    component as well as all its (grand)children and their hierarchy.

    Arguments:
      - start: a Document, System or Component instance

    Example:
      INS-GNSS 1 (NavigationDevice)
      |- IMU (IMU)
      |- RCV1 (GNSSReceiver)
      |  |- ANT1 (GNSSAntenna)
      |- DMI (DMI)
      |- Scanner 1 (LidarDevice2D)
      |- Scanner 2 (LidarDevice2D)
      |- Camera Left Front (CameraDeviceTruecolor)
      |- Camera Right Front (CameraDeviceTruecolor)

    Since version 1.2.3
    """

    if not start:
        return str()

    if isinstance(start, Document):
        start = start.system

    if isinstance(start, System):
        start = start.root_component

    if isinstance(start, Component):
        result = str()
        stack = [(0, start)]
        while stack:
            level, component = stack.pop(0)
            if component:
                stack = [(level + 1, child) for child in component.components] + stack
                result += ("|  " * (level - 1)) + ("|- " * (level > 0)) + (
                    component.name + " (" + class_name(component) + ")"
                ) + "\n"
        return result.strip()

    raise TypeError("Expecting 'start' to be a Document, System or Component instance")


# ______________________________________________________________________________
#
def validate(document: Document):
    """
    Validate system description

    Check system description document and throw exception in case of errors.
    """
    component_ids = set()
    component_names = set()

    def link(link, name, required):
        if required and not link:
            raise RuntimeError(name + " not defined")
        if link and not find(document.system, by_name(link), all_components()):
            raise RuntimeError(name + " not found: " + link)

    def scan(component, path):
        if not component:
            return

        try:
            # check component id:
            if not component.id:
                raise RuntimeError("no id defined")
            if component.id in component_ids:
                raise RuntimeError("id already defined: " + component.id)
            component_ids.add(component.id)

            # check component name:
            if not component.name:
                raise RuntimeError("no name defined")
            if component.name in component_names:
                raise RuntimeError("name already defined: " + component.name)
            component_names.add(component.name)

            # check pose rotation axes:
            if component.pose.rotation_axes:
                for axis in component.pose.rotation_axes:
                    if axis not in ("x", "y", "z"):
                        raise RuntimeError("invalid rotation axis: " + axis)

            # check camera device axes:
            if isinstance(component, CameraDevice):
                for axis in (component.axes.x, component.axes.y, component.axes.z):
                    if axis not in ("left-to-right", "top-to-bottom", "camera-to-object"):
                        raise RuntimeError("invalid camera axis: " + axis)

            # check panorama camera field of view:
            if isinstance(component, PanoramaCameraDevice) and component.field_of_view:
                if component.field_of_view.phi_min < 0:
                    raise RuntimeError("field of view: phi min must be at least 0 deg")
                if component.field_of_view.phi_min > 720:
                    raise RuntimeError("field of view: phi min must not exceed 720 deg")
                if component.field_of_view.phi_max < 0:
                    raise RuntimeError("field of view: phi max must be at least 0 deg")
                if component.field_of_view.phi_max > 720:
                    raise RuntimeError("field of view: phi max must not exceed 720 deg")
                if component.field_of_view.phi_min >= component.field_of_view.phi_max:
                    raise RuntimeError("field of view: phi min must be smaller than phi max")
                if component.field_of_view.phi_max - component.field_of_view.phi_min > 360:
                    raise RuntimeError("field of view: phi max - phi min must not exceed 360 deg")

                if component.field_of_view.theta_min < 0:
                    raise RuntimeError("field of view: theta min must be at least 0 deg")
                if component.field_of_view.theta_min > 180:
                    raise RuntimeError("field of view: theta min must not exceed 180 deg")
                if component.field_of_view.theta_max < 0:
                    raise RuntimeError("field of view: theta max must be at least 0 deg")
                if component.field_of_view.theta_max > 180:
                    raise RuntimeError("field of view: theta max must not exceed 180 deg")
                if component.field_of_view.theta_min >= component.field_of_view.theta_max:
                    raise RuntimeError("field of view: theta min must be smaller than theta max")

            # check GNSS receiver links:
            if isinstance(component, GNSSReceiver):
                link(component.primary_gnss_antenna,   "primary_gnss_antenna",   True)
                link(component.secondary_gnss_antenna, "secondary_gnss_antenna", False)

            # check navigation device links:
            if isinstance(component, NavigationDevice):
                link(component.primary_imu,     "primary_imu",     True)
                link(component.secondary_imu,   "secondary_imu",   False)
                link(component.dmi,             "dmi",             False)
                link(component.gnss_receiver,   "gnss_receiver",   True)
                link(component.levelling_mount, "levelling_mount", False)

            # check discrete mounts:
            if isinstance(component, DiscreteMount):
                if len(component.poses) == 0:
                    raise RuntimeError("no mount poses defined")
                for index, pose in enumerate(component.poses, start=1):
                    if not pose.name:
                        raise RuntimeError("pose #" + str(index) + ": no pose name defined")
                if component.default_pose:
                    for pose in component.poses:
                        if pose.name == component.default_pose:
                            break
                    else:
                        raise RuntimeError("invalid pose name: " + component.default_pose)

            # check continuous mounts:
            if isinstance(component, ContinuousMount):
                if sum(map(abs, component.rotation_axis)) < 1e-6:
                    raise RuntimeError("no rotation axis defined")

            # check mounting interfaces:
            if isinstance(component, MountingInterface):
                name = component.mounted_component
                if name != "" and name != "*":  # not "none" or "all":
                    if not any([item.name == name for item in component.components]):
                        raise RuntimeError("mounted_component not found: " + name)

        except Exception as error:
            raise RuntimeError("component " + path + ": " + str(error)) from error

        # check child components:
        for index, child in enumerate(component.components):
            scan(child, path + "/" + str(index))

    scan(document.system.root_component, "/0")


# ______________________________________________________________________________
#
def parents(start: Object, component: Component) -> list:
    """
    Find all parent components of a component

    The function returns a list of all parent components (up to and including
    the root component of the system) of the given component. The first item
    of the list is always the root component and the last item is the given
    component. If the component isn't part of the system component tree, an
    empty list is returned.

    Arguments:
      - start: a Document, System or Component instance
      - component: the component to get all parents of
    """
    def scan(current, component):
        if not current:
            return []

        if current is component:
            return [current]

        for child in current.components:
            result = scan(child, component)
            if result:
                return [current] + result

        return []

    if not start:
        return []

    if isinstance(start, Document):
        start = start.system

    if isinstance(start, System):
        start = start.root_component

    if isinstance(start, Component):
        return scan(start, component)

    raise TypeError("Expecting 'start' to be a Document, System or Component instance")


# ______________________________________________________________________________
#
def path(start: Object, source: Component, target: Component, top: bool = False):
    """
    Find all components on the path from the source to the target component

    The function returns a list of all components visited on the way from the
    source to the target component. The first item of the list is the source
    component and the last item is the target component. If source and target
    components are not part of the same system component tree, an empty list
    is returned.

    If 'top' is set to 'True', the result is a tuple where the first value is
    the list described above and the second value is the nearest common ancestor
    of the source and target components.

    Arguments:
      - start: a Document, System or Component instance
      - source: the component to begin at
      - target: the component to stop at
      - top: return nearest common ancestor

    Example:
      a
      |- b        path(system, a, c, True) = ([a, b, c], a)
      |  |- c     path(system, g, a, True) = ([g, e, a], a)
      |  |- d     path(system, c, d, True) = ([c, b, d], b)
      |- e        path(system, d, e, True) = ([d, b, a, e], a)
      |  |- f     path(system, c, f, True) = ([c, b, a, e, f], a)
      |  |- g

    Since version 1.2.3
    """
    psrc = parents(start, source)
    pdst = parents(start, target)
    i = 0  # find common ancestor of components:
    while (i < len(psrc)) and (i < len(pdst)) and (psrc[i] == pdst[i]):
        i += 1
    parent = psrc[i-1] if i > 0 else None
    result = psrc[:i-1:-1] + pdst[i-1::1] if i > 0 else []
    return (result, parent) if top else result


# ______________________________________________________________________________
#
class ComponentFilter:
    """
    Wrapper for filter function used by the search() and find() functions

    The function returns 'True' if the component matches the filter criteria.
    """

    def __init__(self, filter):
        """
        Initialize component filter function wrapper
        """
        self.filter = filter

    def __call__(self, component):
        """
        Evaluate filter function for component
        """
        return self.filter(component)

    def __add__(self, that):
        """
        Logical 'and' operator for component filters

        Example:
          find(system, by_type("VUX-1HA") + by_name("Scanner 1"))
        """
        return ComponentFilter(lambda component: self.filter(component) and that.filter(component))

    def __or__(self, that):
        """
        Logical 'or' operator for component filters

        Example:
          search(system, by_name("Scanner 1") | by_name("Scanner 2"))
        """
        return ComponentFilter(lambda component: self.filter(component) or that.filter(component))

    def __neg__(self):
        """
        Logical 'not' operator for component filters

        Example:
          search(system, -by_type("VUX-1HA"))
        """
        return ComponentFilter(lambda component: not self.filter(component))


# ______________________________________________________________________________
#
def current_parent() -> Component:
    """
    Get current parent component

    This function returns the parent of the component that is currently being
    evaluated by the filter function. It will return nothing (i.e. None) if the
    current component is the root component, or if the function is not called
    from within one of the search() or find() functions.
    """
    try:
        return _private_lookup_context.current_parent
    except AttributeError:
        return None


# ______________________________________________________________________________
#
def current_path() -> str:
    """
    Get current component path

    This function returns the path of the component that is currently being
    evaluated by the filter function. It will return nothing (empty string)
    if the function is not called from within one of the search() or find()
    functions.
    """
    try:
        return _private_lookup_context.current_path
    except AttributeError:
        return ""


# ______________________________________________________________________________
#
def search(start: Object, filter: ComponentFilter, prefilter: ComponentFilter = None) -> list:
    """
    Search for all components that match the filter criteria

    The components are processed and returned in the order they appear in the
    component tree (depth-first search). If no component matches both 'filter'
    and 'prefilter', nothing (empty list) is returned.

    Example:
      search(system, by_class(LidarDevice))       # finds all lidar devices
      search(system, by_type("VUX-1HA"))          # finds all VUX-1HA components
      search(system, by_type("*", "*", "RIEGL"))  # finds all RIEGL components

    Arguments:
      - start: a Document, System or Component instance to start searching at
      - filter: a function that receives a Component and returns a bool, see by_*()
      - prefilter: a function that receives a Component and returns a bool, see by_*()

    Note:
      If 'filter' is not specified, 'all_components()' is used instead.
      If 'prefilter' is not specified, 'mounted_components()' is used instead.
    """
    def scan(current, parent, path):
        _private_lookup_context.current_parent = parent
        _private_lookup_context.current_path = path + ((("\\" if path else "") + (current.name or "?")) if current else "")
        try:
            if not current:
                return []

            if not prefilter(current):
                return []

            result = [current] if filter(current) else []

            for child in current.components:
                result = result + scan(child, current, current_path())

            return result
        finally:
            _private_lookup_context.current_parent = None
            _private_lookup_context.current_path = ""

    if not start:
        return []

    if isinstance(start, Document):
        start = start.system

    if isinstance(start, System):
        start = start.root_component

    if isinstance(start, Component):
        filter, prefilter = _private_get_filters(filter, prefilter)
        return scan(start, None, "")

    raise TypeError("Expecting 'start' to be a Document, System or Component instance")


# ______________________________________________________________________________
#
def find(start: Object, filter: ComponentFilter, prefilter: ComponentFilter = None) -> Component:
    """
    Find the first component that matches the filter criteria

    The components are processed and returned in the order they appear in the
    component tree (depth-first search). If no component matches both 'filter'
    and 'prefilter', nothing (None) is returned.

    Example:
      find(system, by_name("INS-GNSS 1"))  # finds the INS-GNSS 1 component

    Arguments:
      - start: a Document, System or Component instance to start searching at
      - filter: a function that receives a Component and returns a bool, see by_*()
      - prefilter: a function that receives a Component and returns a bool, see by_*()

    Note:
      If 'filter' is not specified, 'all_components()' is used instead.
      If 'prefilter' is not specified, 'mounted_components()' is used instead.
    """
    def scan(current, parent, path):
        _private_lookup_context.current_parent = parent
        _private_lookup_context.current_path = path + ((("\\" if path else "") + (current.name or "?")) if current else "")
        try:
            if not current:
                return None

            if not prefilter(current):
                return None

            if filter(current):
                return current

            for child in current.components:
                result = scan(child, current, current_path())
                if result:
                    return result

            return None
        finally:
            _private_lookup_context.current_parent = None
            _private_lookup_context.current_path = ""

    if not start:
        return None

    if isinstance(start, Document):
        start = start.system

    if isinstance(start, System):
        start = start.root_component

    if isinstance(start, Component):
        filter, prefilter = _private_get_filters(filter, prefilter)
        return scan(start, None, "")

    raise TypeError("Expecting 'start' to be a Document, System or Component instance")


# ______________________________________________________________________________
#
def by_class(kind):
    """
    Filter to search for components by class

    Parameter 'kind' is expected to be a class derived from 'Component' or
    a string with the name of such a class.

    Example:
      search(system, by_class(LidarDevice))  # finds all lidar devices
    """
    if isinstance(kind, str) and kind in globals():
        kind = globals()[kind]
    if not (isinstance(kind, type) and issubclass(kind, Component)):
        raise TypeError("Expecting (name of) class derived from 'Component'.")
    return ComponentFilter(lambda component: isinstance(component, kind))


# ______________________________________________________________________________
#
def by_id(id: str):
    """
    Filter to search for components by id

    Example:
      find(system, by_id("550E8400-E29B-11D4-A716-446655440000"))
    """
    return ComponentFilter(lambda component: component.id == id)


# ______________________________________________________________________________
#
def by_name(name: str):
    """
    Filter to search for components by name

    Example:
      find(system, by_name("INS-GNSS 1"))  # finds the INS-GNSS 1 component
    """
    return ComponentFilter(lambda component: component.name == name)


# ______________________________________________________________________________
#
def by_path(path: str):
    """
    Filter to search for components by path

    Example:
      find(system, by_path("INS-GNSS 1\\Scanner 1"))  # finds Scanner 1 (child of INS-GNSS 1)

    Since version 1.2.11
    """
    return ComponentFilter(lambda component: current_path() == path)


# ______________________________________________________________________________
#
def by_permanent_magnetic_field():
    """
    Filter to search for components that have a permanent magnetic field

    Example:
      search(system, by_permanent_magnetic_field())

    Since version 1.2.11
    """
    return ComponentFilter(lambda component: component.permanent_magnetic_field is not None)


# ______________________________________________________________________________
#
def by_pose_adjustable(adjustable: bool):
    """
    Filter to search for components whose pose is (not) adjustable

    Example:
      search(system, by_pose_adjustable(True))

    Since version 1.2.12
    """
    return ComponentFilter(lambda component: component.pose.adjustable == adjustable)


# ______________________________________________________________________________
#
def by_pose_adjustment_settings():
    """
    Filter to search for components that have pose adjustment settings

    Example:
      search(system, by_pose_adjustment_settings())

    Since version 1.2.11
    """
    return ComponentFilter(lambda component: component.pose.adjustment_settings is not None)


# ______________________________________________________________________________
#
def by_thumbnail(state: bool = True):
    """
    Filter to search for components that have (or do not have) a thumbnail image

    Example:
      search(system, by_thumbnail(True))   # finds all components with thumbnails
      search(system, by_thumbnail(False))  # finds all components without thumbnails

    Since version 1.3.3
    """
    return ComponentFilter(lambda component: bool(component.thumbnail) == state)


# ______________________________________________________________________________
#
def by_calibrated_camera(state: bool):
    """
    Filter to search for camera devices by calibration state

    Example:
      search(system, by_calibrated_camera(True))   # finds all calibrated cameras
      search(system, by_calibrated_camera(False))  # finds all uncalibrated cameras
      search(system, by_calibrated_camera(None))   # finds all cameras with unknown calibration state

    Since version 1.2.12
    """
    return ComponentFilter(lambda component: isinstance(component, CameraDevice) and component.calibrated == state)


# ______________________________________________________________________________
#
@overload
def by_type(
    device_type: str,
    serial_number: str = "*",
    manufacturer: str = "*"
):
    """
    Filter to search for components by type, serial number and manufacturer

    Example:
      search(system, by_type("VUX-1HA"))          # finds all VUX-1HA components
      search(system, by_type("*", "*", "RIEGL"))  # finds all RIEGL components
    """
    pass  # for documentation only, implementation see below


# ______________________________________________________________________________
#
@overload
def by_type(
    device_type: str,
    serial_number: str = "*",
    channel_number: int = -1,
    manufacturer: str = "*"
):
    """
    Filter to search for components by type, serial number and manufacturer

    Example:
      search(system, by_type("VUX-1HA"))              # finds all VUX-1HA components
      search(system, by_type("*", "*", -1, "RIEGL"))  # finds all RIEGL components
    """
    pass  # for documentation only, implementation see below


# ______________________________________________________________________________
#
def by_type(
    device_type: str,
    serial_number: str = "*",
    manufacturer_or_channel_number="*",
    manufacturer: str = "*"
):
    channel_number = -1  # undefined
    if isinstance(manufacturer_or_channel_number, int):
        channel_number = manufacturer_or_channel_number
    elif isinstance(manufacturer_or_channel_number, str):
        manufacturer = manufacturer_or_channel_number

    return ComponentFilter(lambda component: (
        ((component.device.device_type    or "") == device_type    or device_type    == "*") and
        ((component.device.serial_number  or "") == serial_number  or serial_number  == "*") and
        ((component.device.channel_number or  0) == channel_number or channel_number <= -1 ) and
        ((component.device.manufacturer   or "") == manufacturer   or manufacturer   == "*")
    ))


# ______________________________________________________________________________
#
def all_components():
    """
    Filter to search for all components
    """
    return ComponentFilter(lambda component: True)


# ______________________________________________________________________________
#
def mounted_components():
    """
    Filter to search for all mounted components

    See class 'MountingInterface' for details.
    """
    def mounted_component(component) -> bool:
        if isinstance(current_parent(), MountingInterface):
            mounting_interface = current_parent()
            if mounting_interface.mounted_component == "":  # none
                return False
            if mounting_interface.mounted_component == "*":  # all
                return True
            if mounting_interface.mounted_component != component.name:  # one
                return False
        return True
    return ComponentFilter(mounted_component)


# ______________________________________________________________________________
#
@deprecated("1.3.0")
def create_signature(device: LidarDevice, key: str):
    """
    Create digital signature

    This function creates a new signature based on the following properties:

      - serial_number
      - device_geometry.name
      - device_geometry.beam
      - device_geometry.primary
      - device_geometry.secondary
      - device_geometry.amu

    The signature is stored in the "signature" field of the device.

    Arguments:
      - device: lidar device which is to be signed
      - key: signature encryption key as hex-string or iterable like list/bytes/bytearray (32 byte)

    Deprecated since version 1.3.0, may be removed in future versions
    """
    pass


# ______________________________________________________________________________
#
@deprecated("1.3.0")
def verify_signature(device: LidarDevice, key: str):
    """
    Verify digital signature

    This function checks whether the device properties (see above) still match
    the signature. If the properties have been changed or there is no signature
    at all, an exception is thrown.

    Arguments:
      - device: lidar device whose signature is to be verified
      - key: signature encryption key as hex-string or iterable like list/bytes/bytearray (32 byte)

    Deprecated since version 1.3.0, may be removed in future versions
    """
    pass


# ______________________________________________________________________________
#
class PoseCalculator:
    """
    Pose Calculator

    This class calculates a 4x4 transformation matrix that allows to convert
    point coordinates between the coordinate systems of two components. The
    matrix transforms from the given source component to the target component.
    If no target component is given (None), then the matrix transforms to the
    coordinate system of the system (the pose of the system's root component
    is also applied).

    If the path from the source component to the target component includes
    non-static mounts like components of classes DiscreteMount or ContinuousMount,
    then the mount position must be provided by calling set_mount_position()
    before execute() is called (otherwise, execute() will raise an exception).
    In the case of a DiscreteMount, the default position (see 'default_pose')
    is used if set_mount_position() is not called before execute().

    Hint: The target component is not required to be a (grand-) parent of the
          source component. In other words: execute(system, a, b) will return
          the inverse of execute(system, b, a).

    See also: pose_to_matrix(), matrix_to_pose()
    """

    def __init__(self):
        self.__mirror_facet = None
        self.__discrete_mount_poses = dict()     # mount=index(integer)
        self.__continuous_mount_angles = dict()  # mount=angle(float/deg)

    def execute(self, system: System, source: Component, target: Component = None):
        """
        Returns 4x4 transformation matrix (row-major order: matrix[row][column]).
        The return value is a numpy.array if NumPy is available, and nested Python
        lists otherwise.

        Details see class comments above.
        """

        # shortcuts for private helper functions
        eye, rot, mul, inv = _private_eye, _private_rot, _private_mul, _private_inv

        # get matrix for path
        def total(components, begin, end):
            result = eye()
            for i in range(end, begin, -1):
                current = components[i - 1]
                if isinstance(current, DiscreteMount):
                    mount = current
                    if len(mount.poses) > 0:
                        if mount in self.__discrete_mount_poses:
                            index = self.__discrete_mount_poses[mount]
                            result = mul(result, pose_to_matrix(mount.poses[index]))
                        elif mount.default_pose:
                            for entry in mount.poses:
                                if entry.name == mount.default_pose:
                                    result = mul(result, pose_to_matrix(entry))
                                    break
                            else:
                                raise ValueError(mount.name + ": invalid pose name: " + mount.default_pose)
                        else:
                            raise RuntimeError(mount.name + ": no pose defined")
                elif isinstance(current, ContinuousMount):
                    mount = current
                    if sum(map(abs, mount.rotation_axis)) > 1e-6:
                        if mount in self.__continuous_mount_angles:
                            angle = self.__continuous_mount_angles[mount]
                            result = mul(result, rot(angle, mount.rotation_axis))
                        else:
                            raise RuntimeError(mount.name + ": no angle defined")
                result = mul(result, pose_to_matrix(current.pose))
            return result

        # special case: mirror
        mirror_facet = self.__mirror_facet
        if mirror_facet is not None:
            try:
                self.__mirror_facet = None
                m1 = self.execute(system, source, mirror_facet)
                m2 = self.execute(system, mirror_facet, target)

                # to negate the x axis direction (make right-handed):
                f1, f2 = eye(), eye()
                if isinstance(source, CameraDevice):
                    f1[0][0] = -1.0
                elif isinstance(target, CameraDevice):
                    f2[0][0] = -1.0
                else:
                    raise ValueError("Either the source or the target must be a camera device.")

                result = eye()
                result[2][2] = -1.0  # to negate the z vector components (mirror matrix)
                result = mul(m1, result)
                result = mul(f1, result)
                result = mul(result, m2)
                result = mul(result, f2)
            finally:
                self.__mirror_facet = mirror_facet
        else:
            # get parents of source and target components:
            psrc = parents(system, source)
            pdst = parents(system, target)
            nsrc = len(psrc)
            ndst = len(pdst)
            i = 0  # find common ancestor of components:
            while (i < nsrc) and (i < ndst) and (psrc[i] == pdst[i]):
                i += 1

            # calculate result matrix:
            result = mul(total(psrc, i, nsrc), inv(total(pdst, i, ndst)))
        return np.array(result) if _NUMPY_PACKAGE_AVAILABLE else result

    def set_mirror_facet(self, mirror_facet):
        """
        Set mirror facet (use None to unset)

        Note: If set, then 'source' or 'target' must be of class CameraDevice.
        """
        self.__mirror_facet = mirror_facet

    def set_mount_position(self, mount, pose):
        """
        Set variable data for non-static mountings.

        Supported input parameter combinations:

          | mount           | pose  | description                              |
          |-----------------+------ +------------------------------------------|
          | DiscreteMount   | str   | set pose name of discrete mount          |
          | DiscreteMount   | int   | set pose index of discrete mount         |
          | ContinuousMount | float | set angle of continuous mount in degrees |

        See also: save_mount_positions(), load_mount_positions()
        """
        if isinstance(mount, DiscreteMount) and isinstance(pose, str):
            for index, entry in enumerate(mount.poses):
                if entry.name == pose:
                    self.set_mount_position(mount, index)
                    return
            raise ValueError(mount.name + ": invalid pose name: " + pose)

        if isinstance(mount, DiscreteMount) and isinstance(pose, int):
            if 0 == pose == len(mount.poses):
                return  # ignore empty discrete mounts
            if 0 <= pose < len(mount.poses):
                self.__discrete_mount_poses[mount] = pose
                return
            raise ValueError(mount.name + ": invalid pose index: " + str(pose))

        if isinstance(mount, ContinuousMount):
            angle = float(pose)
            if -720.0 <= angle <= 720.0:
                self.__continuous_mount_angles[mount] = angle
                return
            raise ValueError(mount.name + ": invalid rotation angle: " + str(angle))

        raise TypeError("invalid input parameters")

    def save_mount_positions(self, minify: bool = False) -> str:
        """
        Serialize mount positions table to JSON format.

        The result is a JSON object that contains an entry for each mount
        whose position was previously set by calling set_mount_position().
        The name of an entry is the name of the mount and the value is the
        name of the pose (for discrete mounts) or the rotation angle in
        degrees (for continuous mounts).

        Example output:
          {
              "Discrete Mount": "Pose 1",
              "Continuous Mount": 90.0
          }

        Set 'minify' to True to get a compact output without superfluous whitespace.

        See also: load_mount_positions()

        Since version 1.3.0
        """
        result = {}
        for mount, index in self.__discrete_mount_poses.items():
            result[mount.name] = mount.poses[index].name
        for mount, angle in self.__continuous_mount_angles.items():
            result[mount.name] = angle
        return json.dumps(
            result,
            indent=None if minify else 4,
            separators=(',', ':') if minify else None,
            ensure_ascii=False
        )

    def load_mount_positions(self, start: Object, table: str):
        """
        Parse mount positions table from JSON format.

        The given table is expected to be a JSON object as created by
        save_mount_positions(). For each entry, the mount is searched
        for by name and, if found, set_mount_position() is called with
        the value of the entry (the mount position).

        Arguments:
          - start: a Document, System or Component instance to start searching at
          - table: mount positions table in JSON format, see save_mount_positions()

        See also: save_mount_positions()

        Since version 1.3.0
        """
        table = json.loads(table or "{}")
        if not isinstance(table, dict):
            raise TypeError("JSON object expected as root element.")
        for key, pose in table.items():
            mount = find(start, by_name(key)) or find(start, by_id(key))
            self.set_mount_position(mount, pose)


# ______________________________________________________________________________
#
def pose_to_matrix(pose: Pose):
    """
    Convert pose to matrix

    This function calculates a 4x4 transformation matrix from a pose object.
    If e.g. the pose of a component is given, the matrix transforms from the
    coordinate system of the component into that of the parent component.

    Returns 4x4 transformation matrix (row-major order: matrix[row][column]).
    The return value is a numpy.array if NumPy is available, and nested Python
    lists otherwise.

    See also: matrix_to_pose(), class PoseCalculator

    Since version 1.2.7
    """

    # shortcuts for private helper functions
    eye, rot, mul, axis = _private_eye, _private_rot, _private_mul, _private_axis

    result = eye()
    if pose.calibration_angles:
        result = mul(result, rot(pose.calibration_angles[0], axis("x")))
        result = mul(result, rot(pose.calibration_angles[1], axis("y")))
        result = mul(result, rot(pose.calibration_angles[2], axis("z")))

    if pose.calibration_shifts:
        result[0][3] += pose.calibration_shifts[0]
        result[1][3] += pose.calibration_shifts[1]
        result[2][3] += pose.calibration_shifts[2]

    if pose.rotation_angles:
        axes = pose.rotation_axes if pose.rotation_axes else ("x", "y", "z")
        result = mul(result, rot(pose.rotation_angles[0], axis(axes[0])))
        result = mul(result, rot(pose.rotation_angles[1], axis(axes[1])))
        result = mul(result, rot(pose.rotation_angles[2], axis(axes[2])))

    if pose.position_vector:
        result[0][3] += pose.position_vector[0]
        result[1][3] += pose.position_vector[1]
        result[2][3] += pose.position_vector[2]

    return np.array(result) if _NUMPY_PACKAGE_AVAILABLE else result


# ______________________________________________________________________________
#
def matrix_to_pose(
    matrix,
    pose: Pose,
    rotation_resolution: float = 5.0,
    position_resolution: float = 0.001,
    lock_rotation_angles: bool = False,
    lock_position_vector: bool = False
) -> None:
    """
    Convert matrix to pose

    This function populates a pose object from a 4x4 transformation matrix.
    It decomposes the matrix into the rotation angles and the position vector.
    The rotation angles follow the rotation axes already defined in the pose
    (default: X-Y-Z). The rotation angles and the position vector are then
    rounded to the nearest multiple of the given rotation/position resolution.
    Finally, the rotation and translation remaining from the rounding is stored
    in the calibration angles and shifts to reflect the entire transformation.

    Please note that there is not only one set of rotation angles leading to
    the same rotation matrix. Thus, passing the results of pose_to_matrix()
    to matrix_to_pose() can result in different rotation angles. However,
    passing the results of matrix_to_pose() to pose_to_matrix() will give
    the same matrix.

    Arguments:
      - matrix: input transformation matrix
      - pose: output component pose (angles and positions are overwritten, the rest is preserved)
      - rotation_resolution: the rotation angles are rounded to a multiple of this value (<= 0: off) [deg]
      - position_resolution: the position vector is rounded to a multiple of this value (<= 0: off) [m]
      - lock_rotation_angles: the rotation angles already defined in the output pose are not changed (since version 1.2.11)
      - lock_position_vector: the position vector already defined in the output pose is not changed (since version 1.2.11)

    See also: pose_to_matrix(), class PoseCalculator

    Since version 1.2.7
    """

    def cleanup(vector):
        if (
            vector and
            math.isclose(vector[0], 0.0, rel_tol=1e-9, abs_tol=1e-9) and
            math.isclose(vector[1], 0.0, rel_tol=1e-9, abs_tol=1e-9) and
            math.isclose(vector[2], 0.0, rel_tol=1e-9, abs_tol=1e-9)
        ):
            return None
        return vector

    # shortcuts for private helper functions
    mul, inv = _private_mul, _private_inv
    matrix_to_angles = _private_matrix_to_angles
    matrix_to_angles_xyz = _private_matrix_to_angles_xyz
    check_matrix = _private_check_matrix

    # clear pose first:
    check_matrix(matrix)
    pose.calibration_angles = None
    pose.calibration_shifts = None

    # derive pose from matrix:
    if not lock_position_vector:
        pose.position_vector = (matrix[0][3], matrix[1][3], matrix[2][3])
    if not lock_rotation_angles:
        pose.rotation_angles = matrix_to_angles(matrix, pose.rotation_axes)

    # round pose values:
    if rotation_resolution > 0.0 and not lock_rotation_angles:
        for i, value in enumerate(pose.rotation_angles):
            pose.rotation_angles[i] = round(value / rotation_resolution) * rotation_resolution
    if position_resolution > 0.0 and not lock_position_vector:
        for i, value in enumerate(pose.position_vector):
            pose.position_vector[i] = round(value / position_resolution) * position_resolution

    # derive calibration from matrix:
    pose.rotation_angles = cleanup(pose.rotation_angles)
    pose.position_vector = cleanup(pose.position_vector)
    result = mul(matrix, inv(pose_to_matrix(pose)))
    pose.calibration_shifts = (result[0][3], result[1][3], result[2][3])
    pose.calibration_angles = matrix_to_angles_xyz(result)
    pose.calibration_shifts = cleanup(pose.calibration_shifts)
    pose.calibration_angles = cleanup(pose.calibration_angles)


# ______________________________________________________________________________
#
def current_date_time_rfc_3339():
    """
    Return current timestamp in RFC 3339 format, e.g. "2019-06-19T17:13:18+00:00"
    """
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat()


# ______________________________________________________________________________
#
def generate_uuid():
    """
    Return new Universally Unique Identifier in RFC 4122 version 4 format, e.g. "550E8400-E29B-11D4-A716-446655440000"
    """
    return str(uuid.uuid4()).upper()


# ______________________________________________________________________________
#
def _private_eye():
    """
    The identity matrix
    Not intended for direct use by clients.
    """
    return [
        [1.0, 0.0, 0.0, 0.0],
        [0.0, 1.0, 0.0, 0.0],
        [0.0, 0.0, 1.0, 0.0],
        [0.0, 0.0, 0.0, 1.0]
    ]


# ______________________________________________________________________________
#
def _private_axis(name):
    """
    Get rotation axis direction by name
    Not intended for direct use by clients.
    """
    if name == "x":
        return [1.0, 0.0, 0.0]
    if name == "y":
        return [0.0, 1.0, 0.0]
    if name == "z":
        return [0.0, 0.0, 1.0]
    raise RuntimeError("axis not supported: " + name)


# ______________________________________________________________________________
#
def _private_rot(deg, axis):
    """
    Get rotation matrix about given axis
    Not intended for direct use by clients.
    """
    nx, ny, nz = axis
    rad = math.radians(deg)
    s, c = math.sin(rad), math.cos(rad)
    f = 1 - c
    return [
        [nx*nx*f+c,    nx*ny*f-nz*s, nx*nz*f+ny*s, 0],
        [ny*nx*f+nz*s, ny*ny*f+c,    ny*nz*f-nx*s, 0],
        [nz*nx*f-ny*s, nz*ny*f+nx*s, nz*nz*f+c,    0],
        [0,            0,            0,            1]
    ]


# ______________________________________________________________________________
#
def _private_mul(m1, m2):
    """
    Get matrix product so that result = m2 * m1
    Not intended for direct use by clients.
    """
    return [
        # row #1
        [
            m1[0][0]*m2[0][0] + m1[1][0]*m2[0][1] + m1[2][0]*m2[0][2] + m1[3][0]*m2[0][3],
            m1[0][1]*m2[0][0] + m1[1][1]*m2[0][1] + m1[2][1]*m2[0][2] + m1[3][1]*m2[0][3],
            m1[0][2]*m2[0][0] + m1[1][2]*m2[0][1] + m1[2][2]*m2[0][2] + m1[3][2]*m2[0][3],
            m1[0][3]*m2[0][0] + m1[1][3]*m2[0][1] + m1[2][3]*m2[0][2] + m1[3][3]*m2[0][3]
        ],
        # row #2
        [
            m1[0][0]*m2[1][0] + m1[1][0]*m2[1][1] + m1[2][0]*m2[1][2] + m1[3][0]*m2[1][3],
            m1[0][1]*m2[1][0] + m1[1][1]*m2[1][1] + m1[2][1]*m2[1][2] + m1[3][1]*m2[1][3],
            m1[0][2]*m2[1][0] + m1[1][2]*m2[1][1] + m1[2][2]*m2[1][2] + m1[3][2]*m2[1][3],
            m1[0][3]*m2[1][0] + m1[1][3]*m2[1][1] + m1[2][3]*m2[1][2] + m1[3][3]*m2[1][3]
        ],
        # row #3
        [
            m1[0][0]*m2[2][0] + m1[1][0]*m2[2][1] + m1[2][0]*m2[2][2] + m1[3][0]*m2[2][3],
            m1[0][1]*m2[2][0] + m1[1][1]*m2[2][1] + m1[2][1]*m2[2][2] + m1[3][1]*m2[2][3],
            m1[0][2]*m2[2][0] + m1[1][2]*m2[2][1] + m1[2][2]*m2[2][2] + m1[3][2]*m2[2][3],
            m1[0][3]*m2[2][0] + m1[1][3]*m2[2][1] + m1[2][3]*m2[2][2] + m1[3][3]*m2[2][3]
        ],
        # row #4
        [
            m1[0][0]*m2[3][0] + m1[1][0]*m2[3][1] + m1[2][0]*m2[3][2] + m1[3][0]*m2[3][3],
            m1[0][1]*m2[3][0] + m1[1][1]*m2[3][1] + m1[2][1]*m2[3][2] + m1[3][1]*m2[3][3],
            m1[0][2]*m2[3][0] + m1[1][2]*m2[3][1] + m1[2][2]*m2[3][2] + m1[3][2]*m2[3][3],
            m1[0][3]*m2[3][0] + m1[1][3]*m2[3][1] + m1[2][3]*m2[3][2] + m1[3][3]*m2[3][3]
        ]
    ]


# ______________________________________________________________________________
#
def _private_inv(m):
    """
    Get inverse of transformation matrix
    Not intended for direct use by clients.
    """
    x = [m[0][0], m[1][0], m[2][0]]  # column #1
    y = [m[0][1], m[1][1], m[2][1]]  # column #2
    z = [m[0][2], m[1][2], m[2][2]]  # column #3
    t = [m[0][3], m[1][3], m[2][3]]  # column #4
    return [
        [x[0], x[1], x[2], -(x[0]*t[0] + x[1]*t[1] + x[2]*t[2])],
        [y[0], y[1], y[2], -(y[0]*t[0] + y[1]*t[1] + y[2]*t[2])],
        [z[0], z[1], z[2], -(z[0]*t[0] + z[1]*t[1] + z[2]*t[2])],
        [0,    0,    0,    1]
    ]


# ______________________________________________________________________________
#
def _private_check_matrix(matrix):
    """
    Check transformation matrix
    """

    if (
        # last row must be 0/0/0/1:
        not math.isclose(matrix[3][0], 0.0) or
        not math.isclose(matrix[3][1], 0.0) or
        not math.isclose(matrix[3][2], 0.0) or
        not math.isclose(matrix[3][3], 1.0)
    ):
        raise RuntimeError("Invalid transformation matrix")


# ______________________________________________________________________________
#
def _private_matrix_to_angles_xyz(matrix):
    """
    Returns the three angles roll, pitch and yaw from the rotation matrix in degrees
    Not intended for direct use by clients.
    """
    _private_check_matrix(matrix)
    if not math.isclose(abs(matrix[2][0]), 1.0):
        pitch = math.atan2(-matrix[2][0], math.sqrt(matrix[0][0]*matrix[0][0] + matrix[1][0]*matrix[1][0]))
        cos_pitch = math.cos(pitch)
        roll = math.atan2(matrix[2][1] / cos_pitch, matrix[2][2] / cos_pitch)
        yaw = math.atan2(matrix[1][0] / cos_pitch, matrix[0][0] / cos_pitch)
    else:
        pitch = -math.asin(matrix[2][0])
        roll = 0.0
        yaw = math.atan2(-matrix[0][1], -matrix[0][2] * matrix[2][0])
    return [math.degrees(roll), math.degrees(pitch), math.degrees(yaw)]


# ______________________________________________________________________________
#
def _private_matrix_to_angles_zxz(matrix):
    """
    Returns the three angles zi, x and ze from the rotation matrix in degrees
    Not intended for direct use by clients.
    """
    _private_check_matrix(matrix)
    if not math.isclose(abs(matrix[2][2]), 1.0):
        ze = math.atan2(matrix[0][2], -matrix[1][2])
        zi = math.atan2(matrix[2][0], matrix[2][1])
        x = math.acos(matrix[2][2])
    else:
        x = 0.0 if matrix[2][2] > 0.0 else math.pi
        ze = math.atan2(matrix[1][0], matrix[0][0])
        zi = 0.0
    return [math.degrees(zi), math.degrees(x), math.degrees(ze)]


# ______________________________________________________________________________
#
def _private_matrix_to_angles(rotation_matrix, rotation_axes):
    """
    Returns the three rotation angles from the rotation matrix in degrees
    Not intended for direct use by clients.
    """
    axes = str().join(rotation_axes) if rotation_axes else "xyz"
    if axes == "xyz":
        return _private_matrix_to_angles_xyz(rotation_matrix)
    if axes == "zxz":
        return _private_matrix_to_angles_zxz(rotation_matrix)
    raise RuntimeError("rotation axes '" + axes + "' not supported")


# ______________________________________________________________________________
#
def _private_resolve_json(base, path):
    """
    Helper function for document_parse_json() and component_parse_json()
    to resolve JSON pointers (https://www.rfc-editor.org/rfc/rfc6901).
    Not intended for direct use by clients.
    """
    if not path:
        return base
    path = path.lstrip("/").split("/")
    item = path.pop(0)
    path = str("/").join(path)
    if isinstance(base, dict):
        item = item.replace("~1", "/")
        item = item.replace("~0", "~")
        if item in base:
            return _private_resolve_json(base[item], path)
    elif isinstance(base, list):
        item = int(item)
        if item >= 0 and item < len(base):
            return _private_resolve_json(base[item], path)
    return None  # not found


# ______________________________________________________________________________
#
def _private_read_json(value, name_or_index, want, path, default_value=None):
    """
    Helper function for document_parse_json() and component_parse_json().
    Not intended for direct use by clients.
    """
    def finalize_parsed_field(value: dict, name: str) -> None:
        if name in value:
            field = value[name]
            if (not isinstance(field, (list, dict))) or (len(field) == 0):
                value.pop(name)

    def finalize_parsed_object(value: dict, result: Object) -> None:
        if result:  # supported class - remove class name:
            value.pop("$class", None)
        if result and len(value) > 0:  # store remainder:
            result.additional_properties = json.dumps(value)
            value.clear()

    if value is not None:
        if isinstance(name_or_index, int):
            value = value[name_or_index]
        elif isinstance(name_or_index, str):
            if name_or_index in value:
                value = value[name_or_index]
            else:
                return default_value

    if value is None:
        return value

    if isinstance(value, int) and (
        want == "UInt8" or
        want == "UInt16" or
        want == "UInt32" or
        want == "UInt64" or
        want == "Int8" or
        want == "Int16" or
        want == "Int32" or
        want == "Int64" or
        want == "Float32" or
        want == "Float64"
    ):
        return value

    if isinstance(value, float) and (
        want == "Float32" or
        want == "Float64"
    ):
        return value

    if isinstance(value, bool) and (
        want == "Boolean"
    ):
        return value

    if isinstance(value, str) and (
        want == "String"
    ):
        return value

    if isinstance(value, list):
        match = re.match(r"List(?:\[(\d+)\])? of (\S+)", want)
        if match:  # either "List of Type" or "List[Size] of Type"
            size, want = match.groups()
            result = list(_private_read_json(value, i, want, path) for i in range(len(value)))

            i = 0
            while i < len(value):
                if (not isinstance(value[i], (list, dict))) or (len(value[i]) == 0):
                    del value[i]
                else:  # keep item:
                    i += 1

            return None if size and len(result) != int(size) else result

    if isinstance(value, dict):
        have = value.get("$class", "unknown")
        if have == "Object":
            result = Object()
            finalize_parsed_object(value, result)
            return result

        if have == "NamedObject":
            result = NamedObject()
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            finalize_parsed_object(value, result)
            return result

        if have == "Group":
            result = Group()
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "TypePlate":
            result = TypePlate()
            result.manufacturer = _private_read_json(value, "manufacturer", "String", path + "/manufacturer", result.manufacturer)
            finalize_parsed_field(value, "manufacturer")
            result.device_type = _private_read_json(value, "device_type", "String", path + "/device_type", result.device_type)
            finalize_parsed_field(value, "device_type")
            result.device_build = _private_read_json(value, "device_build", "String", path + "/device_build", result.device_build)
            finalize_parsed_field(value, "device_build")
            result.channel_number = _private_read_json(value, "channel_number", "Int32", path + "/channel_number", result.channel_number)
            finalize_parsed_field(value, "channel_number")
            result.serial_number = _private_read_json(value, "serial_number", "String", path + "/serial_number", result.serial_number)
            finalize_parsed_field(value, "serial_number")
            result.part_number = _private_read_json(value, "part_number", "String", path + "/part_number", result.part_number)
            finalize_parsed_field(value, "part_number")
            result.model_name = _private_read_json(value, "model_name", "String", path + "/model_name", result.model_name)
            finalize_parsed_field(value, "model_name")
            finalize_parsed_object(value, result)
            return result

        if have == "Pose":
            result = Pose()
            result.rotation_axes = _private_read_json(value, "rotation_axes", "List[3] of String", path + "/rotation_axes", result.rotation_axes)
            finalize_parsed_field(value, "rotation_axes")
            result.rotation_angles = _private_read_json(value, "rotation_angles", "List[3] of Float64", path + "/rotation_angles", result.rotation_angles)
            finalize_parsed_field(value, "rotation_angles")
            result.position_vector = _private_read_json(value, "position_vector", "List[3] of Float64", path + "/position_vector", result.position_vector)
            finalize_parsed_field(value, "position_vector")
            result.calibration_angles = _private_read_json(value, "calibration_angles", "List[3] of Float64", path + "/calibration_angles", result.calibration_angles)
            finalize_parsed_field(value, "calibration_angles")
            result.calibration_shifts = _private_read_json(value, "calibration_shifts", "List[3] of Float64", path + "/calibration_shifts", result.calibration_shifts)
            finalize_parsed_field(value, "calibration_shifts")
            result.adjustment_settings = _private_read_json(value, "adjustment_settings", "PoseAdjustmentSettings", path + "/adjustment_settings", result.adjustment_settings)
            finalize_parsed_field(value, "adjustment_settings")
            result.adjustable = _private_read_json(value, "adjustable", "Boolean", path + "/adjustable", result.adjustable)
            finalize_parsed_field(value, "adjustable")
            finalize_parsed_object(value, result)
            return result

        if have == "NamedPose":
            result = NamedPose()
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.rotation_axes = _private_read_json(value, "rotation_axes", "List[3] of String", path + "/rotation_axes", result.rotation_axes)
            finalize_parsed_field(value, "rotation_axes")
            result.rotation_angles = _private_read_json(value, "rotation_angles", "List[3] of Float64", path + "/rotation_angles", result.rotation_angles)
            finalize_parsed_field(value, "rotation_angles")
            result.position_vector = _private_read_json(value, "position_vector", "List[3] of Float64", path + "/position_vector", result.position_vector)
            finalize_parsed_field(value, "position_vector")
            result.calibration_angles = _private_read_json(value, "calibration_angles", "List[3] of Float64", path + "/calibration_angles", result.calibration_angles)
            finalize_parsed_field(value, "calibration_angles")
            result.calibration_shifts = _private_read_json(value, "calibration_shifts", "List[3] of Float64", path + "/calibration_shifts", result.calibration_shifts)
            finalize_parsed_field(value, "calibration_shifts")
            result.adjustment_settings = _private_read_json(value, "adjustment_settings", "PoseAdjustmentSettings", path + "/adjustment_settings", result.adjustment_settings)
            finalize_parsed_field(value, "adjustment_settings")
            result.adjustable = _private_read_json(value, "adjustable", "Boolean", path + "/adjustable", result.adjustable)
            finalize_parsed_field(value, "adjustable")
            finalize_parsed_object(value, result)
            return result

        if have == "PoseAdjustmentSettings":
            result = PoseAdjustmentSettings()
            result.calibration_angles = _private_read_json(value, "calibration_angles", "List[3] of Float64", path + "/calibration_angles", result.calibration_angles)
            finalize_parsed_field(value, "calibration_angles")
            result.calibration_shifts = _private_read_json(value, "calibration_shifts", "List[3] of Float64", path + "/calibration_shifts", result.calibration_shifts)
            finalize_parsed_field(value, "calibration_shifts")
            finalize_parsed_object(value, result)
            return result

        if have == "TrajectoryOffsets":
            result = TrajectoryOffsets()
            result.time = _private_read_json(value, "time", "Float64", path + "/time", result.time)
            finalize_parsed_field(value, "time")
            result.east = _private_read_json(value, "east", "Float64", path + "/east", result.east)
            finalize_parsed_field(value, "east")
            result.north = _private_read_json(value, "north", "Float64", path + "/north", result.north)
            finalize_parsed_field(value, "north")
            result.height = _private_read_json(value, "height", "Float64", path + "/height", result.height)
            finalize_parsed_field(value, "height")
            result.roll = _private_read_json(value, "roll", "Float64", path + "/roll", result.roll)
            finalize_parsed_field(value, "roll")
            result.pitch = _private_read_json(value, "pitch", "Float64", path + "/pitch", result.pitch)
            finalize_parsed_field(value, "pitch")
            result.yaw = _private_read_json(value, "yaw", "Float64", path + "/yaw", result.yaw)
            finalize_parsed_field(value, "yaw")
            finalize_parsed_object(value, result)
            return result

        if have == "RawDataPacket":
            result = RawDataPacket()
            result.id = _private_read_json(value, "id", "List[2] of UInt32", path + "/id", result.id)
            finalize_parsed_field(value, "id")
            result.content = _private_read_json(value, "content", "List of Float64", path + "/content", result.content)
            finalize_parsed_field(value, "content")
            finalize_parsed_object(value, result)
            return result

        if have == "LidarDeviceGeometryPacket":
            result = LidarDeviceGeometryPacket()
            result.id = _private_read_json(value, "id", "List[2] of UInt32", path + "/id", result.id)
            finalize_parsed_field(value, "id")
            result.content = _private_read_json(value, "content", "List of Float64", path + "/content", result.content)
            finalize_parsed_field(value, "content")
            finalize_parsed_object(value, result)
            return result

        if have == "LidarDeviceGeometryUnits":
            result = LidarDeviceGeometryUnits()
            result.line_circle_count = _private_read_json(value, "line_circle_count", "UInt32", path + "/line_circle_count", result.line_circle_count)
            finalize_parsed_field(value, "line_circle_count")
            result.frame_circle_count = _private_read_json(value, "frame_circle_count", "UInt32", path + "/frame_circle_count", result.frame_circle_count)
            finalize_parsed_field(value, "frame_circle_count")
            finalize_parsed_object(value, result)
            return result

        if have == "LidarDeviceBeamGeometry":
            result = LidarDeviceBeamGeometry()
            result.beam_diameter_exit_aperture = _private_read_json(value, "beam_diameter_exit_aperture", "Float64", path + "/beam_diameter_exit_aperture", result.beam_diameter_exit_aperture)
            finalize_parsed_field(value, "beam_diameter_exit_aperture")
            result.beam_divergence = _private_read_json(value, "beam_divergence", "Float64", path + "/beam_divergence", result.beam_divergence)
            finalize_parsed_field(value, "beam_divergence")
            result.beam_waist_distance = _private_read_json(value, "beam_waist_distance", "Float64", path + "/beam_waist_distance", result.beam_waist_distance)
            finalize_parsed_field(value, "beam_waist_distance")
            finalize_parsed_object(value, result)
            return result

        if have == "LidarDeviceGeometry":
            result = LidarDeviceGeometry()
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.laser_wavelength = _private_read_json(value, "laser_wavelength", "Float64", path + "/laser_wavelength", result.laser_wavelength)
            finalize_parsed_field(value, "laser_wavelength")
            result.beam = _private_read_json(value, "beam", "LidarDeviceBeamGeometry", path + "/beam", result.beam)
            finalize_parsed_field(value, "beam")
            result.primary = _private_read_json(value, "primary", "LidarDeviceGeometryPacket", path + "/primary", result.primary)
            finalize_parsed_field(value, "primary")
            result.secondary = _private_read_json(value, "secondary", "LidarDeviceGeometryPacket", path + "/secondary", result.secondary)
            finalize_parsed_field(value, "secondary")
            result.amu = _private_read_json(value, "amu", "LidarDeviceGeometryUnits", path + "/amu", result.amu)
            finalize_parsed_field(value, "amu")
            finalize_parsed_object(value, result)
            return result

        if have == "CameraDeviceAxes":
            result = CameraDeviceAxes()
            result.x = _private_read_json(value, "x", "String", path + "/x", result.x)
            finalize_parsed_field(value, "x")
            result.y = _private_read_json(value, "y", "String", path + "/y", result.y)
            finalize_parsed_field(value, "y")
            result.z = _private_read_json(value, "z", "String", path + "/z", result.z)
            finalize_parsed_field(value, "z")
            finalize_parsed_object(value, result)
            return result

        if have == "CameraDeviceFieldOfView":
            result = CameraDeviceFieldOfView()
            result.left = _private_read_json(value, "left", "Float64", path + "/left", result.left)
            finalize_parsed_field(value, "left")
            result.right = _private_read_json(value, "right", "Float64", path + "/right", result.right)
            finalize_parsed_field(value, "right")
            result.top = _private_read_json(value, "top", "Float64", path + "/top", result.top)
            finalize_parsed_field(value, "top")
            result.bottom = _private_read_json(value, "bottom", "Float64", path + "/bottom", result.bottom)
            finalize_parsed_field(value, "bottom")
            finalize_parsed_object(value, result)
            return result

        if have == "CameraDeviceGeometryPixelToPixel":
            result = CameraDeviceGeometryPixelToPixel()
            finalize_parsed_object(value, result)
            return result

        if have == "CameraDeviceGeometryPixelToPixelRIEGL":
            result = CameraDeviceGeometryPixelToPixelRIEGL()
            result.u_nodes = _private_read_json(value, "u_nodes", "List of Float64", path + "/u_nodes", result.u_nodes)
            finalize_parsed_field(value, "u_nodes")
            result.v_nodes = _private_read_json(value, "v_nodes", "List of Float64", path + "/v_nodes", result.v_nodes)
            finalize_parsed_field(value, "v_nodes")
            result.u_shift_coef = _private_read_json(value, "u_shift_coef", "List of Float64", path + "/u_shift_coef", result.u_shift_coef)
            finalize_parsed_field(value, "u_shift_coef")
            result.v_shift_coef = _private_read_json(value, "v_shift_coef", "List of Float64", path + "/v_shift_coef", result.v_shift_coef)
            finalize_parsed_field(value, "v_shift_coef")
            result.k = _private_read_json(value, "k", "List[2] of UInt32", path + "/k", result.k)
            finalize_parsed_field(value, "k")
            finalize_parsed_object(value, result)
            return result

        if have == "CameraDeviceGeometryRayToPixel":
            result = CameraDeviceGeometryRayToPixel()
            result.pixel_to_pixel = _private_read_json(value, "pixel_to_pixel", "CameraDeviceGeometryPixelToPixel", path + "/pixel_to_pixel", result.pixel_to_pixel)
            finalize_parsed_field(value, "pixel_to_pixel")
            finalize_parsed_object(value, result)
            return result

        if have == "CameraDeviceGeometryRayToPixelRIEGL":
            result = CameraDeviceGeometryRayToPixelRIEGL()
            result.version = _private_read_json(value, "version", "UInt32", path + "/version", result.version)
            finalize_parsed_field(value, "version")
            result.fx = _private_read_json(value, "fx", "Float64", path + "/fx", result.fx)
            finalize_parsed_field(value, "fx")
            result.fy = _private_read_json(value, "fy", "Float64", path + "/fy", result.fy)
            finalize_parsed_field(value, "fy")
            result.cx = _private_read_json(value, "cx", "Float64", path + "/cx", result.cx)
            finalize_parsed_field(value, "cx")
            result.cy = _private_read_json(value, "cy", "Float64", path + "/cy", result.cy)
            finalize_parsed_field(value, "cy")
            result.k1 = _private_read_json(value, "k1", "Float64", path + "/k1", result.k1)
            finalize_parsed_field(value, "k1")
            result.k2 = _private_read_json(value, "k2", "Float64", path + "/k2", result.k2)
            finalize_parsed_field(value, "k2")
            result.k3 = _private_read_json(value, "k3", "Float64", path + "/k3", result.k3)
            finalize_parsed_field(value, "k3")
            result.k4 = _private_read_json(value, "k4", "Float64", path + "/k4", result.k4)
            finalize_parsed_field(value, "k4")
            result.p1 = _private_read_json(value, "p1", "Float64", path + "/p1", result.p1)
            finalize_parsed_field(value, "p1")
            result.p2 = _private_read_json(value, "p2", "Float64", path + "/p2", result.p2)
            finalize_parsed_field(value, "p2")
            result.pixel_to_pixel = _private_read_json(value, "pixel_to_pixel", "CameraDeviceGeometryPixelToPixel", path + "/pixel_to_pixel", result.pixel_to_pixel)
            finalize_parsed_field(value, "pixel_to_pixel")
            finalize_parsed_object(value, result)
            return result

        if have == "CameraDeviceGeometryPixelToRay":
            result = CameraDeviceGeometryPixelToRay()
            result.pixel_to_pixel = _private_read_json(value, "pixel_to_pixel", "CameraDeviceGeometryPixelToPixel", path + "/pixel_to_pixel", result.pixel_to_pixel)
            finalize_parsed_field(value, "pixel_to_pixel")
            finalize_parsed_object(value, result)
            return result

        if have == "CameraDeviceGeometry":
            result = CameraDeviceGeometry()
            result.nx = _private_read_json(value, "nx", "UInt32", path + "/nx", result.nx)
            finalize_parsed_field(value, "nx")
            result.ny = _private_read_json(value, "ny", "UInt32", path + "/ny", result.ny)
            finalize_parsed_field(value, "ny")
            result.dx = _private_read_json(value, "dx", "Float64", path + "/dx", result.dx)
            finalize_parsed_field(value, "dx")
            result.dy = _private_read_json(value, "dy", "Float64", path + "/dy", result.dy)
            finalize_parsed_field(value, "dy")
            result.ray_to_pixel = _private_read_json(value, "ray_to_pixel", "CameraDeviceGeometryRayToPixel", path + "/ray_to_pixel", result.ray_to_pixel)
            finalize_parsed_field(value, "ray_to_pixel")
            result.pixel_to_ray = _private_read_json(value, "pixel_to_ray", "CameraDeviceGeometryPixelToRay", path + "/pixel_to_ray", result.pixel_to_ray)
            finalize_parsed_field(value, "pixel_to_ray")
            finalize_parsed_object(value, result)
            return result

        if have == "PanoramaCameraDeviceFieldOfView":
            result = PanoramaCameraDeviceFieldOfView()
            result.phi_min = _private_read_json(value, "phi_min", "Float64", path + "/phi_min", result.phi_min)
            finalize_parsed_field(value, "phi_min")
            result.phi_max = _private_read_json(value, "phi_max", "Float64", path + "/phi_max", result.phi_max)
            finalize_parsed_field(value, "phi_max")
            result.theta_min = _private_read_json(value, "theta_min", "Float64", path + "/theta_min", result.theta_min)
            finalize_parsed_field(value, "theta_min")
            result.theta_max = _private_read_json(value, "theta_max", "Float64", path + "/theta_max", result.theta_max)
            finalize_parsed_field(value, "theta_max")
            finalize_parsed_object(value, result)
            return result

        if have == "Component":
            result = Component()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "System":
            result = System()
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.type_plate = _private_read_json(value, "type_plate", "TypePlate", path + "/type_plate", result.type_plate)
            finalize_parsed_field(value, "type_plate")
            result.field_of_application = _private_read_json(value, "field_of_application", "String", path + "/field_of_application", result.field_of_application)
            finalize_parsed_field(value, "field_of_application")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.root_component = _private_read_json(value, "root_component", "Component", path + "/root_component", result.root_component)
            finalize_parsed_field(value, "root_component")
            finalize_parsed_object(value, result)
            return result

        if have == "Document":
            result = Document()
            result.version = _private_read_json(value, "version", "String", path + "/version", result.version)
            finalize_parsed_field(value, "version")
            result.author = _private_read_json(value, "author", "String", path + "/author", result.author)
            finalize_parsed_field(value, "author")
            result.timestamp = _private_read_json(value, "timestamp", "String", path + "/timestamp", result.timestamp)
            finalize_parsed_field(value, "timestamp")
            result.modified_by = _private_read_json(value, "modified_by", "String", path + "/modified_by", result.modified_by)
            finalize_parsed_field(value, "modified_by")
            result.modified_on = _private_read_json(value, "modified_on", "String", path + "/modified_on", result.modified_on)
            finalize_parsed_field(value, "modified_on")
            result.system = _private_read_json(value, "system", "System", path + "/system", result.system)
            finalize_parsed_field(value, "system")
            finalize_parsed_object(value, result)
            return result

        if have == "Housing":
            result = Housing()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "MirrorFacet":
            result = MirrorFacet()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "CarrierPlatform":
            result = CarrierPlatform()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.kind = _private_read_json(value, "kind", "String", path + "/kind", result.kind)
            finalize_parsed_field(value, "kind")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "StaticMount":
            result = StaticMount()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "AdjustableMount":
            result = AdjustableMount()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "LevellingMount":
            result = LevellingMount()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "DiscreteMount":
            result = DiscreteMount()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.poses = _private_read_json(value, "poses", "List of NamedPose", path + "/poses", result.poses)
            finalize_parsed_field(value, "poses")
            result.default_pose = _private_read_json(value, "default_pose", "String", path + "/default_pose", result.default_pose)
            finalize_parsed_field(value, "default_pose")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "ContinuousMount":
            result = ContinuousMount()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.rotation_axis = _private_read_json(value, "rotation_axis", "List[3] of Float64", path + "/rotation_axis", result.rotation_axis)
            finalize_parsed_field(value, "rotation_axis")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "MountingInterface":
            result = MountingInterface()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.mounted_component = _private_read_json(value, "mounted_component", "String", path + "/mounted_component", result.mounted_component)
            finalize_parsed_field(value, "mounted_component")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "IMU":
            result = IMU()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.device_geometry = _private_read_json(value, "device_geometry", "List of RawDataPacket", path + "/device_geometry", result.device_geometry)
            finalize_parsed_field(value, "device_geometry")
            result.statistical_model = _private_read_json(value, "statistical_model", "RawDataPacket", path + "/statistical_model", result.statistical_model)
            finalize_parsed_field(value, "statistical_model")
            result.timing_model = _private_read_json(value, "timing_model", "RawDataPacket", path + "/timing_model", result.timing_model)
            finalize_parsed_field(value, "timing_model")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "GNSSAntenna":
            result = GNSSAntenna()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.antex_code = _private_read_json(value, "antex_code", "String", path + "/antex_code", result.antex_code)
            finalize_parsed_field(value, "antex_code")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "GNSSReceiver":
            result = GNSSReceiver()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.primary_gnss_antenna = _private_read_json(value, "primary_gnss_antenna", "String", path + "/primary_gnss_antenna", result.primary_gnss_antenna)
            finalize_parsed_field(value, "primary_gnss_antenna")
            result.secondary_gnss_antenna = _private_read_json(value, "secondary_gnss_antenna", "String", path + "/secondary_gnss_antenna", result.secondary_gnss_antenna)
            finalize_parsed_field(value, "secondary_gnss_antenna")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "DMI":
            result = DMI()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.pulses_per_revolution = _private_read_json(value, "pulses_per_revolution", "UInt32", path + "/pulses_per_revolution", result.pulses_per_revolution)
            finalize_parsed_field(value, "pulses_per_revolution")
            result.pulses_per_meter = _private_read_json(value, "pulses_per_meter", "UInt32", path + "/pulses_per_meter", result.pulses_per_meter)
            finalize_parsed_field(value, "pulses_per_meter")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "EventDevice":
            result = EventDevice()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "NavigationDevice":
            result = NavigationDevice()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.primary_imu = _private_read_json(value, "primary_imu", "String", path + "/primary_imu", result.primary_imu)
            finalize_parsed_field(value, "primary_imu")
            result.secondary_imu = _private_read_json(value, "secondary_imu", "String", path + "/secondary_imu", result.secondary_imu)
            finalize_parsed_field(value, "secondary_imu")
            result.dmi = _private_read_json(value, "dmi", "String", path + "/dmi", result.dmi)
            finalize_parsed_field(value, "dmi")
            result.gnss_receiver = _private_read_json(value, "gnss_receiver", "String", path + "/gnss_receiver", result.gnss_receiver)
            finalize_parsed_field(value, "gnss_receiver")
            result.ancillary_gnss_receiver = _private_read_json(value, "ancillary_gnss_receiver", "String", path + "/ancillary_gnss_receiver", result.ancillary_gnss_receiver)
            finalize_parsed_field(value, "ancillary_gnss_receiver")
            result.levelling_mount = _private_read_json(value, "levelling_mount", "String", path + "/levelling_mount", result.levelling_mount)
            finalize_parsed_field(value, "levelling_mount")
            result.trajectory_offsets = _private_read_json(value, "trajectory_offsets", "TrajectoryOffsets", path + "/trajectory_offsets", result.trajectory_offsets)
            finalize_parsed_field(value, "trajectory_offsets")
            result.time_system = _private_read_json(value, "time_system", "String", path + "/time_system", result.time_system)
            finalize_parsed_field(value, "time_system")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "LidarDevice":
            result = LidarDevice()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.device_geometry = _private_read_json(value, "device_geometry", "List of LidarDeviceGeometry", path + "/device_geometry", result.device_geometry)
            finalize_parsed_field(value, "device_geometry")
            result.signature = _private_read_json(value, "signature", "String", path + "/signature", result.signature)
            finalize_parsed_field(value, "signature")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "LidarDevice2D":
            result = LidarDevice2D()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.device_geometry = _private_read_json(value, "device_geometry", "List of LidarDeviceGeometry", path + "/device_geometry", result.device_geometry)
            finalize_parsed_field(value, "device_geometry")
            result.signature = _private_read_json(value, "signature", "String", path + "/signature", result.signature)
            finalize_parsed_field(value, "signature")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "LidarDevice3D":
            result = LidarDevice3D()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.device_geometry = _private_read_json(value, "device_geometry", "List of LidarDeviceGeometry", path + "/device_geometry", result.device_geometry)
            finalize_parsed_field(value, "device_geometry")
            result.signature = _private_read_json(value, "signature", "String", path + "/signature", result.signature)
            finalize_parsed_field(value, "signature")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "BaseCameraDevice":
            result = BaseCameraDevice()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "CameraDevice":
            result = CameraDevice()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.lens = _private_read_json(value, "lens", "TypePlate", path + "/lens", result.lens)
            finalize_parsed_field(value, "lens")
            result.shutter = _private_read_json(value, "shutter", "String", path + "/shutter", result.shutter)
            finalize_parsed_field(value, "shutter")
            result.axes = _private_read_json(value, "axes", "CameraDeviceAxes", path + "/axes", result.axes)
            finalize_parsed_field(value, "axes")
            result.field_of_view = _private_read_json(value, "field_of_view", "CameraDeviceFieldOfView", path + "/field_of_view", result.field_of_view)
            finalize_parsed_field(value, "field_of_view")
            result.device_geometry = _private_read_json(value, "device_geometry", "CameraDeviceGeometry", path + "/device_geometry", result.device_geometry)
            finalize_parsed_field(value, "device_geometry")
            result.mirror_facet = _private_read_json(value, "mirror_facet", "String", path + "/mirror_facet", result.mirror_facet)
            finalize_parsed_field(value, "mirror_facet")
            result.calibrated = _private_read_json(value, "calibrated", "Boolean", path + "/calibrated", result.calibrated)
            finalize_parsed_field(value, "calibrated")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "CameraDeviceTruecolor":
            result = CameraDeviceTruecolor()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.lens = _private_read_json(value, "lens", "TypePlate", path + "/lens", result.lens)
            finalize_parsed_field(value, "lens")
            result.shutter = _private_read_json(value, "shutter", "String", path + "/shutter", result.shutter)
            finalize_parsed_field(value, "shutter")
            result.axes = _private_read_json(value, "axes", "CameraDeviceAxes", path + "/axes", result.axes)
            finalize_parsed_field(value, "axes")
            result.field_of_view = _private_read_json(value, "field_of_view", "CameraDeviceFieldOfView", path + "/field_of_view", result.field_of_view)
            finalize_parsed_field(value, "field_of_view")
            result.device_geometry = _private_read_json(value, "device_geometry", "CameraDeviceGeometry", path + "/device_geometry", result.device_geometry)
            finalize_parsed_field(value, "device_geometry")
            result.mirror_facet = _private_read_json(value, "mirror_facet", "String", path + "/mirror_facet", result.mirror_facet)
            finalize_parsed_field(value, "mirror_facet")
            result.calibrated = _private_read_json(value, "calibrated", "Boolean", path + "/calibrated", result.calibrated)
            finalize_parsed_field(value, "calibrated")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "CameraDeviceThermal":
            result = CameraDeviceThermal()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.lens = _private_read_json(value, "lens", "TypePlate", path + "/lens", result.lens)
            finalize_parsed_field(value, "lens")
            result.shutter = _private_read_json(value, "shutter", "String", path + "/shutter", result.shutter)
            finalize_parsed_field(value, "shutter")
            result.axes = _private_read_json(value, "axes", "CameraDeviceAxes", path + "/axes", result.axes)
            finalize_parsed_field(value, "axes")
            result.field_of_view = _private_read_json(value, "field_of_view", "CameraDeviceFieldOfView", path + "/field_of_view", result.field_of_view)
            finalize_parsed_field(value, "field_of_view")
            result.device_geometry = _private_read_json(value, "device_geometry", "CameraDeviceGeometry", path + "/device_geometry", result.device_geometry)
            finalize_parsed_field(value, "device_geometry")
            result.mirror_facet = _private_read_json(value, "mirror_facet", "String", path + "/mirror_facet", result.mirror_facet)
            finalize_parsed_field(value, "mirror_facet")
            result.calibrated = _private_read_json(value, "calibrated", "Boolean", path + "/calibrated", result.calibrated)
            finalize_parsed_field(value, "calibrated")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "HyperspectralCameraDevice":
            result = HyperspectralCameraDevice()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.lens = _private_read_json(value, "lens", "TypePlate", path + "/lens", result.lens)
            finalize_parsed_field(value, "lens")
            result.axes = _private_read_json(value, "axes", "CameraDeviceAxes", path + "/axes", result.axes)
            finalize_parsed_field(value, "axes")
            result.field_of_view = _private_read_json(value, "field_of_view", "CameraDeviceFieldOfView", path + "/field_of_view", result.field_of_view)
            finalize_parsed_field(value, "field_of_view")
            result.device_geometry = _private_read_json(value, "device_geometry", "CameraDeviceGeometry", path + "/device_geometry", result.device_geometry)
            finalize_parsed_field(value, "device_geometry")
            result.minimum_wavelength = _private_read_json(value, "minimum_wavelength", "Float64", path + "/minimum_wavelength", result.minimum_wavelength)
            finalize_parsed_field(value, "minimum_wavelength")
            result.maximum_wavelength = _private_read_json(value, "maximum_wavelength", "Float64", path + "/maximum_wavelength", result.maximum_wavelength)
            finalize_parsed_field(value, "maximum_wavelength")
            result.number_of_channels = _private_read_json(value, "number_of_channels", "UInt32", path + "/number_of_channels", result.number_of_channels)
            finalize_parsed_field(value, "number_of_channels")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "PanoramaCameraDevice":
            result = PanoramaCameraDevice()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.field_of_view = _private_read_json(value, "field_of_view", "PanoramaCameraDeviceFieldOfView", path + "/field_of_view", result.field_of_view)
            finalize_parsed_field(value, "field_of_view")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "PanoramaCameraDeviceSpherical":
            result = PanoramaCameraDeviceSpherical()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.field_of_view = _private_read_json(value, "field_of_view", "PanoramaCameraDeviceFieldOfView", path + "/field_of_view", result.field_of_view)
            finalize_parsed_field(value, "field_of_view")
            result.image_width = _private_read_json(value, "image_width", "UInt32", path + "/image_width", result.image_width)
            finalize_parsed_field(value, "image_width")
            result.image_height = _private_read_json(value, "image_height", "UInt32", path + "/image_height", result.image_height)
            finalize_parsed_field(value, "image_height")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "PanoramaCameraDeviceCylindrical":
            result = PanoramaCameraDeviceCylindrical()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.field_of_view = _private_read_json(value, "field_of_view", "PanoramaCameraDeviceFieldOfView", path + "/field_of_view", result.field_of_view)
            finalize_parsed_field(value, "field_of_view")
            result.image_width = _private_read_json(value, "image_width", "UInt32", path + "/image_width", result.image_width)
            finalize_parsed_field(value, "image_width")
            result.image_height = _private_read_json(value, "image_height", "UInt32", path + "/image_height", result.image_height)
            finalize_parsed_field(value, "image_height")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "PanoramaCameraDeviceCubic":
            result = PanoramaCameraDeviceCubic()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.field_of_view = _private_read_json(value, "field_of_view", "PanoramaCameraDeviceFieldOfView", path + "/field_of_view", result.field_of_view)
            finalize_parsed_field(value, "field_of_view")
            result.image_size = _private_read_json(value, "image_size", "UInt32", path + "/image_size", result.image_size)
            finalize_parsed_field(value, "image_size")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        if have == "CameraSystem":
            result = CameraSystem()
            result.id = _private_read_json(value, "id", "String", path + "/id", result.id) or result.id
            finalize_parsed_field(value, "id")
            result.name = _private_read_json(value, "name", "String", path + "/name", result.name)
            finalize_parsed_field(value, "name")
            result.device = _private_read_json(value, "device", "TypePlate", path + "/device", result.device)
            finalize_parsed_field(value, "device")
            result.comments = _private_read_json(value, "comments", "String", path + "/comments", result.comments)
            finalize_parsed_field(value, "comments")
            result.changelog = _private_read_json(value, "changelog", "String", path + "/changelog", result.changelog)
            finalize_parsed_field(value, "changelog")
            result.thumbnail = _private_read_json(value, "thumbnail", "String", path + "/thumbnail", result.thumbnail)
            finalize_parsed_field(value, "thumbnail")
            result.pose = _private_read_json(value, "pose", "Pose", path + "/pose", result.pose)
            finalize_parsed_field(value, "pose")
            result.weight = _private_read_json(value, "weight", "Float64", path + "/weight", result.weight)
            finalize_parsed_field(value, "weight")
            result.permanent_magnetic_field = _private_read_json(value, "permanent_magnetic_field", "List[3] of Float64", path + "/permanent_magnetic_field", result.permanent_magnetic_field)
            finalize_parsed_field(value, "permanent_magnetic_field")
            result.components = _private_read_json(value, "components", "List of Component", path + "/components", result.components)
            finalize_parsed_field(value, "components")
            finalize_parsed_object(value, result)
            return result

        return default_value  # not supported object

    raise RuntimeError("Not supported value type \"" + type(value).__name__ + "\" at " + path)


# ______________________________________________________________________________
#
def _private_post_json(value):
    """
    Helper function for document_store_json() and component_store_json().
    Not intended for direct use by clients.
    """
    def initialize_stored_object(value: Object) -> dict:
        try:
            return json.loads(value.additional_properties or "{}")
        except:
            return dict()

    def update_stored_object(node: dict, name: str, new_value) -> None:
        if name in node:
            old_value = node[name]
            if isinstance(old_value, dict) and isinstance(new_value, dict):  # merge:
                old_value.update(new_value)
            elif isinstance(old_value, list) and isinstance(new_value, list):  # append:
                old_value.extend(new_value)
            elif new_value:  # plain field type or type mismatch:
                node[name] = new_value
        else:  # field did not exist -> add it:
            node[name] = new_value

    if isinstance(value, list):
        return [_private_post_json(item) for item in value]

    if isinstance(value, Object):
        if value.__class__ is Object:
            result = initialize_stored_object(value)
            result["$class"] = "Object"
            return result

        if value.__class__ is NamedObject:
            result = initialize_stored_object(value)
            result["$class"] = "NamedObject"
            update_stored_object(result, "name", _private_post_json(value.name))
            return result

        if value.__class__ is Group:
            result = initialize_stored_object(value)
            result["$class"] = "Group"
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is TypePlate:
            result = initialize_stored_object(value)
            result["$class"] = "TypePlate"
            update_stored_object(result, "manufacturer", _private_post_json(value.manufacturer))
            update_stored_object(result, "device_type", _private_post_json(value.device_type))
            update_stored_object(result, "device_build", _private_post_json(value.device_build))
            update_stored_object(result, "channel_number", _private_post_json(value.channel_number))
            update_stored_object(result, "serial_number", _private_post_json(value.serial_number))
            update_stored_object(result, "part_number", _private_post_json(value.part_number))
            update_stored_object(result, "model_name", _private_post_json(value.model_name))
            return result

        if value.__class__ is Pose:
            result = initialize_stored_object(value)
            result["$class"] = "Pose"
            update_stored_object(result, "rotation_axes", _private_post_json(value.rotation_axes))
            update_stored_object(result, "rotation_angles", _private_post_json(value.rotation_angles))
            update_stored_object(result, "position_vector", _private_post_json(value.position_vector))
            update_stored_object(result, "calibration_angles", _private_post_json(value.calibration_angles))
            update_stored_object(result, "calibration_shifts", _private_post_json(value.calibration_shifts))
            update_stored_object(result, "adjustment_settings", _private_post_json(value.adjustment_settings))
            update_stored_object(result, "adjustable", _private_post_json(value.adjustable))
            return result

        if value.__class__ is NamedPose:
            result = initialize_stored_object(value)
            result["$class"] = "NamedPose"
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "rotation_axes", _private_post_json(value.rotation_axes))
            update_stored_object(result, "rotation_angles", _private_post_json(value.rotation_angles))
            update_stored_object(result, "position_vector", _private_post_json(value.position_vector))
            update_stored_object(result, "calibration_angles", _private_post_json(value.calibration_angles))
            update_stored_object(result, "calibration_shifts", _private_post_json(value.calibration_shifts))
            update_stored_object(result, "adjustment_settings", _private_post_json(value.adjustment_settings))
            update_stored_object(result, "adjustable", _private_post_json(value.adjustable))
            return result

        if value.__class__ is PoseAdjustmentSettings:
            result = initialize_stored_object(value)
            result["$class"] = "PoseAdjustmentSettings"
            update_stored_object(result, "calibration_angles", _private_post_json(value.calibration_angles))
            update_stored_object(result, "calibration_shifts", _private_post_json(value.calibration_shifts))
            return result

        if value.__class__ is TrajectoryOffsets:
            result = initialize_stored_object(value)
            result["$class"] = "TrajectoryOffsets"
            update_stored_object(result, "time", _private_post_json(value.time))
            update_stored_object(result, "east", _private_post_json(value.east))
            update_stored_object(result, "north", _private_post_json(value.north))
            update_stored_object(result, "height", _private_post_json(value.height))
            update_stored_object(result, "roll", _private_post_json(value.roll))
            update_stored_object(result, "pitch", _private_post_json(value.pitch))
            update_stored_object(result, "yaw", _private_post_json(value.yaw))
            return result

        if value.__class__ is RawDataPacket:
            result = initialize_stored_object(value)
            result["$class"] = "RawDataPacket"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "content", _private_post_json(value.content))
            return result

        if value.__class__ is LidarDeviceGeometryPacket:
            result = initialize_stored_object(value)
            result["$class"] = "LidarDeviceGeometryPacket"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "content", _private_post_json(value.content))
            return result

        if value.__class__ is LidarDeviceGeometryUnits:
            result = initialize_stored_object(value)
            result["$class"] = "LidarDeviceGeometryUnits"
            update_stored_object(result, "line_circle_count", _private_post_json(value.line_circle_count))
            update_stored_object(result, "frame_circle_count", _private_post_json(value.frame_circle_count))
            return result

        if value.__class__ is LidarDeviceBeamGeometry:
            result = initialize_stored_object(value)
            result["$class"] = "LidarDeviceBeamGeometry"
            update_stored_object(result, "beam_diameter_exit_aperture", _private_post_json(value.beam_diameter_exit_aperture))
            update_stored_object(result, "beam_divergence", _private_post_json(value.beam_divergence))
            update_stored_object(result, "beam_waist_distance", _private_post_json(value.beam_waist_distance))
            return result

        if value.__class__ is LidarDeviceGeometry:
            result = initialize_stored_object(value)
            result["$class"] = "LidarDeviceGeometry"
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "laser_wavelength", _private_post_json(value.laser_wavelength))
            update_stored_object(result, "beam", _private_post_json(value.beam))
            update_stored_object(result, "primary", _private_post_json(value.primary))
            update_stored_object(result, "secondary", _private_post_json(value.secondary))
            update_stored_object(result, "amu", _private_post_json(value.amu))
            return result

        if value.__class__ is CameraDeviceAxes:
            result = initialize_stored_object(value)
            result["$class"] = "CameraDeviceAxes"
            update_stored_object(result, "x", _private_post_json(value.x))
            update_stored_object(result, "y", _private_post_json(value.y))
            update_stored_object(result, "z", _private_post_json(value.z))
            return result

        if value.__class__ is CameraDeviceFieldOfView:
            result = initialize_stored_object(value)
            result["$class"] = "CameraDeviceFieldOfView"
            update_stored_object(result, "left", _private_post_json(value.left))
            update_stored_object(result, "right", _private_post_json(value.right))
            update_stored_object(result, "top", _private_post_json(value.top))
            update_stored_object(result, "bottom", _private_post_json(value.bottom))
            return result

        if value.__class__ is CameraDeviceGeometryPixelToPixel:
            result = initialize_stored_object(value)
            result["$class"] = "CameraDeviceGeometryPixelToPixel"
            return result

        if value.__class__ is CameraDeviceGeometryPixelToPixelRIEGL:
            result = initialize_stored_object(value)
            result["$class"] = "CameraDeviceGeometryPixelToPixelRIEGL"
            update_stored_object(result, "u_nodes", _private_post_json(value.u_nodes))
            update_stored_object(result, "v_nodes", _private_post_json(value.v_nodes))
            update_stored_object(result, "u_shift_coef", _private_post_json(value.u_shift_coef))
            update_stored_object(result, "v_shift_coef", _private_post_json(value.v_shift_coef))
            update_stored_object(result, "k", _private_post_json(value.k))
            return result

        if value.__class__ is CameraDeviceGeometryRayToPixel:
            result = initialize_stored_object(value)
            result["$class"] = "CameraDeviceGeometryRayToPixel"
            update_stored_object(result, "pixel_to_pixel", _private_post_json(value.pixel_to_pixel))
            return result

        if value.__class__ is CameraDeviceGeometryRayToPixelRIEGL:
            result = initialize_stored_object(value)
            result["$class"] = "CameraDeviceGeometryRayToPixelRIEGL"
            update_stored_object(result, "version", _private_post_json(value.version))
            update_stored_object(result, "fx", _private_post_json(value.fx))
            update_stored_object(result, "fy", _private_post_json(value.fy))
            update_stored_object(result, "cx", _private_post_json(value.cx))
            update_stored_object(result, "cy", _private_post_json(value.cy))
            update_stored_object(result, "k1", _private_post_json(value.k1))
            update_stored_object(result, "k2", _private_post_json(value.k2))
            update_stored_object(result, "k3", _private_post_json(value.k3))
            update_stored_object(result, "k4", _private_post_json(value.k4))
            update_stored_object(result, "p1", _private_post_json(value.p1))
            update_stored_object(result, "p2", _private_post_json(value.p2))
            update_stored_object(result, "pixel_to_pixel", _private_post_json(value.pixel_to_pixel))
            return result

        if value.__class__ is CameraDeviceGeometryPixelToRay:
            result = initialize_stored_object(value)
            result["$class"] = "CameraDeviceGeometryPixelToRay"
            update_stored_object(result, "pixel_to_pixel", _private_post_json(value.pixel_to_pixel))
            return result

        if value.__class__ is CameraDeviceGeometry:
            result = initialize_stored_object(value)
            result["$class"] = "CameraDeviceGeometry"
            update_stored_object(result, "nx", _private_post_json(value.nx))
            update_stored_object(result, "ny", _private_post_json(value.ny))
            update_stored_object(result, "dx", _private_post_json(value.dx))
            update_stored_object(result, "dy", _private_post_json(value.dy))
            update_stored_object(result, "ray_to_pixel", _private_post_json(value.ray_to_pixel))
            update_stored_object(result, "pixel_to_ray", _private_post_json(value.pixel_to_ray))
            return result

        if value.__class__ is PanoramaCameraDeviceFieldOfView:
            result = initialize_stored_object(value)
            result["$class"] = "PanoramaCameraDeviceFieldOfView"
            update_stored_object(result, "phi_min", _private_post_json(value.phi_min))
            update_stored_object(result, "phi_max", _private_post_json(value.phi_max))
            update_stored_object(result, "theta_min", _private_post_json(value.theta_min))
            update_stored_object(result, "theta_max", _private_post_json(value.theta_max))
            return result

        if value.__class__ is Component:
            result = initialize_stored_object(value)
            result["$class"] = "Component"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is System:
            result = initialize_stored_object(value)
            result["$class"] = "System"
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "type_plate", _private_post_json(value.type_plate))
            update_stored_object(result, "field_of_application", _private_post_json(value.field_of_application))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "root_component", _private_post_json(value.root_component))
            return result

        if value.__class__ is Document:
            result = initialize_stored_object(value)
            result["$class"] = "Document"
            update_stored_object(result, "version", _private_post_json(value.version))
            update_stored_object(result, "author", _private_post_json(value.author))
            update_stored_object(result, "timestamp", _private_post_json(value.timestamp))
            update_stored_object(result, "modified_by", _private_post_json(value.modified_by))
            update_stored_object(result, "modified_on", _private_post_json(value.modified_on))
            update_stored_object(result, "system", _private_post_json(value.system))
            return result

        if value.__class__ is Housing:
            result = initialize_stored_object(value)
            result["$class"] = "Housing"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is MirrorFacet:
            result = initialize_stored_object(value)
            result["$class"] = "MirrorFacet"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is CarrierPlatform:
            result = initialize_stored_object(value)
            result["$class"] = "CarrierPlatform"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "kind", _private_post_json(value.kind))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is StaticMount:
            result = initialize_stored_object(value)
            result["$class"] = "StaticMount"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is AdjustableMount:
            result = initialize_stored_object(value)
            result["$class"] = "AdjustableMount"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is LevellingMount:
            result = initialize_stored_object(value)
            result["$class"] = "LevellingMount"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is DiscreteMount:
            result = initialize_stored_object(value)
            result["$class"] = "DiscreteMount"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "poses", _private_post_json(value.poses))
            update_stored_object(result, "default_pose", _private_post_json(value.default_pose))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is ContinuousMount:
            result = initialize_stored_object(value)
            result["$class"] = "ContinuousMount"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "rotation_axis", _private_post_json(value.rotation_axis))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is MountingInterface:
            result = initialize_stored_object(value)
            result["$class"] = "MountingInterface"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "mounted_component", _private_post_json(value.mounted_component))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is IMU:
            result = initialize_stored_object(value)
            result["$class"] = "IMU"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "device_geometry", _private_post_json(value.device_geometry))
            update_stored_object(result, "statistical_model", _private_post_json(value.statistical_model))
            update_stored_object(result, "timing_model", _private_post_json(value.timing_model))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is GNSSAntenna:
            result = initialize_stored_object(value)
            result["$class"] = "GNSSAntenna"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "antex_code", _private_post_json(value.antex_code))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is GNSSReceiver:
            result = initialize_stored_object(value)
            result["$class"] = "GNSSReceiver"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "primary_gnss_antenna", _private_post_json(value.primary_gnss_antenna))
            update_stored_object(result, "secondary_gnss_antenna", _private_post_json(value.secondary_gnss_antenna))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is DMI:
            result = initialize_stored_object(value)
            result["$class"] = "DMI"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "pulses_per_revolution", _private_post_json(value.pulses_per_revolution))
            update_stored_object(result, "pulses_per_meter", _private_post_json(value.pulses_per_meter))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is EventDevice:
            result = initialize_stored_object(value)
            result["$class"] = "EventDevice"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is NavigationDevice:
            result = initialize_stored_object(value)
            result["$class"] = "NavigationDevice"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "primary_imu", _private_post_json(value.primary_imu))
            update_stored_object(result, "secondary_imu", _private_post_json(value.secondary_imu))
            update_stored_object(result, "dmi", _private_post_json(value.dmi))
            update_stored_object(result, "gnss_receiver", _private_post_json(value.gnss_receiver))
            update_stored_object(result, "ancillary_gnss_receiver", _private_post_json(value.ancillary_gnss_receiver))
            update_stored_object(result, "levelling_mount", _private_post_json(value.levelling_mount))
            update_stored_object(result, "trajectory_offsets", _private_post_json(value.trajectory_offsets))
            update_stored_object(result, "time_system", _private_post_json(value.time_system))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is LidarDevice:
            result = initialize_stored_object(value)
            result["$class"] = "LidarDevice"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "device_geometry", _private_post_json(value.device_geometry))
            update_stored_object(result, "signature", _private_post_json(value.signature))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is LidarDevice2D:
            result = initialize_stored_object(value)
            result["$class"] = "LidarDevice2D"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "device_geometry", _private_post_json(value.device_geometry))
            update_stored_object(result, "signature", _private_post_json(value.signature))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is LidarDevice3D:
            result = initialize_stored_object(value)
            result["$class"] = "LidarDevice3D"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "device_geometry", _private_post_json(value.device_geometry))
            update_stored_object(result, "signature", _private_post_json(value.signature))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is BaseCameraDevice:
            result = initialize_stored_object(value)
            result["$class"] = "BaseCameraDevice"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is CameraDevice:
            result = initialize_stored_object(value)
            result["$class"] = "CameraDevice"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "lens", _private_post_json(value.lens))
            update_stored_object(result, "shutter", _private_post_json(value.shutter))
            update_stored_object(result, "axes", _private_post_json(value.axes))
            update_stored_object(result, "field_of_view", _private_post_json(value.field_of_view))
            update_stored_object(result, "device_geometry", _private_post_json(value.device_geometry))
            update_stored_object(result, "mirror_facet", _private_post_json(value.mirror_facet))
            update_stored_object(result, "calibrated", _private_post_json(value.calibrated))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is CameraDeviceTruecolor:
            result = initialize_stored_object(value)
            result["$class"] = "CameraDeviceTruecolor"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "lens", _private_post_json(value.lens))
            update_stored_object(result, "shutter", _private_post_json(value.shutter))
            update_stored_object(result, "axes", _private_post_json(value.axes))
            update_stored_object(result, "field_of_view", _private_post_json(value.field_of_view))
            update_stored_object(result, "device_geometry", _private_post_json(value.device_geometry))
            update_stored_object(result, "mirror_facet", _private_post_json(value.mirror_facet))
            update_stored_object(result, "calibrated", _private_post_json(value.calibrated))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is CameraDeviceThermal:
            result = initialize_stored_object(value)
            result["$class"] = "CameraDeviceThermal"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "lens", _private_post_json(value.lens))
            update_stored_object(result, "shutter", _private_post_json(value.shutter))
            update_stored_object(result, "axes", _private_post_json(value.axes))
            update_stored_object(result, "field_of_view", _private_post_json(value.field_of_view))
            update_stored_object(result, "device_geometry", _private_post_json(value.device_geometry))
            update_stored_object(result, "mirror_facet", _private_post_json(value.mirror_facet))
            update_stored_object(result, "calibrated", _private_post_json(value.calibrated))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is HyperspectralCameraDevice:
            result = initialize_stored_object(value)
            result["$class"] = "HyperspectralCameraDevice"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "lens", _private_post_json(value.lens))
            update_stored_object(result, "axes", _private_post_json(value.axes))
            update_stored_object(result, "field_of_view", _private_post_json(value.field_of_view))
            update_stored_object(result, "device_geometry", _private_post_json(value.device_geometry))
            update_stored_object(result, "minimum_wavelength", _private_post_json(value.minimum_wavelength))
            update_stored_object(result, "maximum_wavelength", _private_post_json(value.maximum_wavelength))
            update_stored_object(result, "number_of_channels", _private_post_json(value.number_of_channels))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is PanoramaCameraDevice:
            result = initialize_stored_object(value)
            result["$class"] = "PanoramaCameraDevice"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "field_of_view", _private_post_json(value.field_of_view))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is PanoramaCameraDeviceSpherical:
            result = initialize_stored_object(value)
            result["$class"] = "PanoramaCameraDeviceSpherical"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "field_of_view", _private_post_json(value.field_of_view))
            update_stored_object(result, "image_width", _private_post_json(value.image_width))
            update_stored_object(result, "image_height", _private_post_json(value.image_height))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is PanoramaCameraDeviceCylindrical:
            result = initialize_stored_object(value)
            result["$class"] = "PanoramaCameraDeviceCylindrical"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "field_of_view", _private_post_json(value.field_of_view))
            update_stored_object(result, "image_width", _private_post_json(value.image_width))
            update_stored_object(result, "image_height", _private_post_json(value.image_height))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is PanoramaCameraDeviceCubic:
            result = initialize_stored_object(value)
            result["$class"] = "PanoramaCameraDeviceCubic"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "field_of_view", _private_post_json(value.field_of_view))
            update_stored_object(result, "image_size", _private_post_json(value.image_size))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

        if value.__class__ is CameraSystem:
            result = initialize_stored_object(value)
            result["$class"] = "CameraSystem"
            update_stored_object(result, "id", _private_post_json(value.id))
            update_stored_object(result, "name", _private_post_json(value.name))
            update_stored_object(result, "device", _private_post_json(value.device))
            update_stored_object(result, "comments", _private_post_json(value.comments))
            update_stored_object(result, "changelog", _private_post_json(value.changelog))
            update_stored_object(result, "thumbnail", _private_post_json(value.thumbnail))
            update_stored_object(result, "pose", _private_post_json(value.pose))
            update_stored_object(result, "weight", _private_post_json(value.weight))
            update_stored_object(result, "permanent_magnetic_field", _private_post_json(value.permanent_magnetic_field))
            update_stored_object(result, "components", _private_post_json(value.components))
            return result

    return value  # pass through all other types


# ______________________________________________________________________________
#
_private_lookup_context = threading.local()
def _private_get_filters(filter, prefilter):
    """
    Helper function for search() and find().
    Not intended for direct use by clients.
    """
    return (
        filter if filter is not None else all_components(),
        prefilter if prefilter is not None else mounted_components()
    )
