annofab_3dpc.annotation のソースコード

import json
import math
from collections.abc import Sequence
from dataclasses import dataclass
from typing import Any, Optional, Union

import numpy
from dataclasses_json import DataClassJsonMixin

ANNOTATION_TYPE_UNKNOWN = "Unknown"


[ドキュメント] class CuboidAnnotationDecodeError(ValueError): pass
[ドキュメント] @dataclass class Location(DataClassJsonMixin): x: float y: float z: float def __add__(self, other: "Location") -> "Location": return self.__class__( x=self.x + other.x, y=self.y + other.y, z=self.z + other.z, ) def __sub__(self, other: "Location") -> "Location": return self.__class__( x=self.x - other.x, y=self.y - other.y, z=self.z - other.z, )
[ドキュメント] @dataclass class Vector3(DataClassJsonMixin): x: float y: float z: float
class _Quaternion: """ クォータニオンを表すクラス。 回転行列を算出するために、クラスを作った。 Notes: 以下のコードを流用した。 https://github.com/KieranWynn/pyquaternion/blob/99025c17bab1c55265d61add13375433b35251af/pyquaternion/quaternion.py#L138 """ def __init__(self, array: Union[Sequence[float], numpy.ndarray]) -> None: self.q = numpy.array(array) def _q_matrix(self) -> numpy.ndarray: """Matrix representation of quaternion for multiplication purposes.""" return numpy.array( [ [self.q[0], -self.q[1], -self.q[2], -self.q[3]], [self.q[1], self.q[0], -self.q[3], self.q[2]], [self.q[2], self.q[3], self.q[0], -self.q[1]], [self.q[3], -self.q[2], self.q[1], self.q[0]], ] ) def _q_bar_matrix(self) -> numpy.ndarray: """Matrix representation of quaternion for multiplication purposes.""" return numpy.array( [ [self.q[0], -self.q[1], -self.q[2], -self.q[3]], [self.q[1], self.q[0], self.q[3], -self.q[2]], [self.q[2], -self.q[3], self.q[0], self.q[1]], [self.q[3], self.q[2], -self.q[1], self.q[0]], ] ) @property def rotation_matrix(self) -> numpy.ndarray: """Get the 3x3 rotation matrix equivalent of the quaternion rotation. Returns: A 3x3 orthogonal rotation matrix as a 3x3 Numpy array Note: This feature only makes sense when referring to a unit quaternion. Calling this method will implicitly normalise the Quaternion object to a unit quaternion if it is not already one. """ # noqa: E501 product_matrix = numpy.dot(self._q_matrix(), self._q_bar_matrix().conj().transpose()) return product_matrix[1:][:, 1:]
[ドキュメント] @dataclass class EulerAnglesZXY(DataClassJsonMixin): """ z-x-y系のオイラー角(右手系)。 単位はラジアン。 """ x: float """X軸周りの回転角度[ラジアン]""" y: float """Y軸周りの回転角度[ラジアン]""" z: float """Z軸周りの回転角度[ラジアン]"""
[ドキュメント] def to_quaternion(self) -> list[float]: """ クォータニオンを生成する。 以下のサイトから移植 https://github.com/BabylonJS/Babylon.js/blob/40ded9ccf1e1bd8ac9cdf3a26909d3e12bc60ab8/src/Maths/math.vector.ts#L3259-L3275 Returns: クォータニオン[w,x,y,z] """ yaw = self.y pitch = self.x roll = self.z halfRoll = roll * 0.5 halfPitch = pitch * 0.5 halfYaw = yaw * 0.5 sinRoll = math.sin(halfRoll) cosRoll = math.cos(halfRoll) sinPitch = math.sin(halfPitch) cosPitch = math.cos(halfPitch) sinYaw = math.sin(halfYaw) cosYaw = math.cos(halfYaw) qx = (cosYaw * sinPitch * cosRoll) + (sinYaw * cosPitch * sinRoll) qy = (sinYaw * cosPitch * cosRoll) - (cosYaw * sinPitch * sinRoll) qz = (cosYaw * cosPitch * sinRoll) - (sinYaw * sinPitch * cosRoll) qw = (cosYaw * cosPitch * cosRoll) + (sinYaw * sinPitch * sinRoll) return [qw, qx, qy, qz]
[ドキュメント] @classmethod def from_quaternion(cls, quaternion: Union[Sequence[float], numpy.ndarray]) -> "EulerAnglesZXY": """ quaternion から生成する。以下のコードを移植した。 https://github.com/BabylonJS/Babylon.js/blob/40ded9ccf1e1bd8ac9cdf3a26909d3e12bc60ab8/src/Maths/math.vector.ts#L2970-L3001 Args: quaternion: wxyzの1次元配列 """ qw = quaternion[0] qx = quaternion[1] qy = quaternion[2] qz = quaternion[3] sqx = qx * qx sqy = qy * qy sqz = qz * qz sqw = qw * qw zAxisY = qy * qz - qx * qw limit = 0.4999999 if zAxisY < -limit: euler_y = 2 * math.atan2(qy, qw) euler_x = math.pi / 2 euler_z = 0.0 elif zAxisY > limit: euler_y = 2 * math.atan2(qy, qw) euler_x = -math.pi / 2 euler_z = 0.0 else: euler_z = math.atan2(2.0 * (qx * qy + qz * qw), (-sqz - sqx + sqy + sqw)) euler_x = math.asin(-2.0 * (qz * qy - qx * qw)) euler_y = math.atan2(2.0 * (qz * qx + qy * qw), (sqz - sqx - sqy + sqw)) return cls(euler_x, euler_y, euler_z)
[ドキュメント] @dataclass class Size(DataClassJsonMixin): width: float """cuboid座標系のY軸方向の長さ""" height: float """cuboid座標系のZ軸方向の長さ""" depth: float """cuboid座標系のX軸方向の長さ"""
[ドキュメント] @dataclass class CuboidDirection(DataClassJsonMixin): """ cuboidの向き(長さ1のベクトル) """ front: Vector3 """cuboid座標系X軸の正の方向""" up: Vector3 """cuboid座標系Z軸の正の方向"""
[ドキュメント] @classmethod def from_euler_angles(cls, euler_angles: EulerAnglesZXY) -> "CuboidDirection": """ オイラー角から生成する。 Args: euler_angles: z-x-y系のオイラー角 """ # 直接計算するのでなく、クォータニオンを介して計算するようにした return cls.from_quaternion(euler_angles.to_quaternion())
[ドキュメント] @classmethod def from_quaternion(cls, quaternion: Union[Sequence[float], numpy.ndarray]) -> "CuboidDirection": """ quaternion から生成する。 Notes: 以下のコードを移植した。 https://github.com/BabylonJS/Babylon.js/blob/40ded9ccf1e1bd8ac9cdf3a26909d3e12bc60ab8/src/Maths/math.vector.ts#L5356-L5389 Args: quaternion: wxyzの1次元配列 """ matrix = _Quaternion(quaternion).rotation_matrix front = matrix @ numpy.array([1, 0, 0]) up = matrix @ numpy.array([0, 0, 1]) return cls(front=Vector3(front[0], front[1], front[2]), up=Vector3(up[0], up[1], up[2]))
[ドキュメント] @dataclass class CuboidShapeV2(DataClassJsonMixin): dimensions: Size """cuboidのサイズ""" location: Location """cuboidの中心位置""" rotation: EulerAnglesZXY """cuboidの回転""" direction: CuboidDirection """cuboidの向き(cuboidの回転から一意に決まる)"""
[ドキュメント] @dataclass class CuboidAnnotationDetailDataV2(DataClassJsonMixin): shape: CuboidShapeV2 kind: str = "CUBOID" version: str = "2"
[ドキュメント] def dump(self) -> dict[str, Any]: """SimpleAnnotationDetailクラスのdataプロパティに対応するdictを生成する。""" str_data = json.dumps(self.to_dict(), separators=(",", ":")) return {"data": str_data, "_type": ANNOTATION_TYPE_UNKNOWN}
[ドキュメント] @dataclass class SegmentData(DataClassJsonMixin): points: list[int] """セグメントに含まれる点の集合。数値はアノテーション対象の点群の、0-originでのindex""" kind: str = "SEGMENT" version: str = "1"
[ドキュメント] @dataclass class SegmentAnnotationDetailData(DataClassJsonMixin): data_uri: str """セグメント情報が格納されたファイルのパス"""
[ドキュメント] def dump(self) -> dict[str, Any]: """SimpleAnnotationDetailクラスのdataプロパティに対応するdictを生成する。""" return {"data": self.data_uri, "_type": ANNOTATION_TYPE_UNKNOWN}
[ドキュメント] @dataclass class SizeV1(DataClassJsonMixin): width: float """cuboid座標系のX軸方向の長さ""" height: float """cuboid座標系のY軸方向の長さ""" depth: float """cuboid座標系のZ軸方向の長さ"""
[ドキュメント] @dataclass class CuboidShapeV1(DataClassJsonMixin): dimensions: SizeV1 """cuboidのサイズ""" location: Location """cuboidの中心位置""" rotation: EulerAnglesZXY """cuboidの回転""" direction: Optional[Vector3] = None """[1, 0, 0]のベクトルを、rotationによって回転させた結果のベクトル"""
[ドキュメント] @dataclass class CuboidAnnotationDetailDataV1(DataClassJsonMixin): shape: CuboidShapeV1 kind: str = "CUBOID" version: str = "1"
[ドキュメント] def dump(self) -> dict[str, Any]: """SimpleAnnotationDetailクラスのdataプロパティに対応するdictを生成する。""" str_data = json.dumps(self.to_dict(), separators=(",", ":")) return {"data": str_data, "_type": ANNOTATION_TYPE_UNKNOWN}
[ドキュメント] def convert_annotation_detail_data(dict_data: dict[str, Any]) -> Any: # noqa: ANN401 """ SimpleAnnotationDetailクラスのdict型であるdataプロパティを、3DPC Editor用のDataclassに変換します。 3DPC Editor用のDataclassに変換できない場合は、引数をそのまま返します。 Args: dict_data: SimpleAnnotationDetailクラスのdict型のdataプロパティ Returns: 3DPC Editor用のDataclass """ if dict_data["_type"] != ANNOTATION_TYPE_UNKNOWN: return dict_data try: tmp = json.loads(dict_data["data"]) if isinstance(tmp, dict) and tmp.get("kind") == "CUBOID": version = tmp.get("version") if version == "2": return CuboidAnnotationDetailDataV2.from_dict(tmp) elif version is None or version == "1": return CuboidAnnotationDetailDataV1.from_dict(tmp) else: # versionが"3"のときなど raise CuboidAnnotationDecodeError(f"version='{version}'のCuboidAnnotationはサポート対象外です。") else: return dict_data except json.JSONDecodeError: return SegmentAnnotationDetailData(dict_data["data"])