Source code for pycanape.calibration_object

# SPDX-FileCopyrightText: 2022-present Artur Drogunow <artur.drogunow@zf.com>
#
# SPDX-License-Identifier: MIT

import ctypes
import typing
from functools import cached_property

import numpy as np

from .cnp_api.cnp_class import (
    DBObjectInfo,
    TAsap3Hdl,
    TCalibrationObjectValue,
    TModulHdl,
    enum_type,
)
from .cnp_api.cnp_constants import ObjectType, TAsap3DataType, TFormat, ValueType
from .cnp_api.cnp_prototype import CANapeDll
from .config import RC
from .utils import CANapeError

if typing.TYPE_CHECKING:
    import numpy.typing as npt


_MULTIPLE_DIMENSION_THR: typing.Final = 2


class BaseCalibrationObject:
    def __init__(
        self,
        dll: CANapeDll,
        asap3_handle: TAsap3Hdl,  # type: ignore[valid-type]
        module_handle: typing.Union[TModulHdl, int],
        name: str,
        object_info: DBObjectInfo,
    ) -> None:
        self._dll = dll
        self._asap3_handle = asap3_handle
        self._module_handle = module_handle
        self._name = name
        self._object_info = object_info
        self._force_upload = True
        try:
            self._datatype: typing.Optional[TAsap3DataType] = self._read_datatype()
        except CANapeError:
            self._datatype = None

    def _read_datatype(self) -> TAsap3DataType:
        _dtype = enum_type(0)
        _address = ctypes.c_ulong(0)
        _min = ctypes.c_double(0)
        _max = ctypes.c_double(0)
        _increment = ctypes.c_double(0)
        self._dll.Asap3ReadObjectParameter(
            self._asap3_handle,
            self._module_handle,
            self._name.encode(RC["ENCODING"]),
            TFormat.PHYSICAL_REPRESENTATION,
            ctypes.byref(_dtype),
            ctypes.byref(_address),
            ctypes.byref(_min),
            ctypes.byref(_max),
            ctypes.byref(_increment),
        )
        return TAsap3DataType(_dtype.value)

    def _read_calibration_object_value(self) -> TCalibrationObjectValue:
        cov = TCalibrationObjectValue()
        self._dll.Asap3ReadCalibrationObject2(
            self._asap3_handle,
            self._module_handle,
            self._name.encode(RC["ENCODING"]),
            TFormat.PHYSICAL_REPRESENTATION,
            self._force_upload,
            ctypes.byref(cov),
        )
        return cov

    def _write_calibration_object_value(self, cov: TCalibrationObjectValue) -> None:
        if self.object_type != ObjectType.OTT_CALIBRATE:
            err_msg = "Cannot set value to a Measurement Object."
            raise TypeError(err_msg)
        self._dll.Asap3WriteCalibrationObject(
            self._asap3_handle,
            self._module_handle,
            self._name.encode(RC["ENCODING"]),
            TFormat.PHYSICAL_REPRESENTATION,
            ctypes.byref(cov),
        )

    @property
    def object_type(self) -> ObjectType:
        return ObjectType(self._object_info.DBObjecttype)

    @property
    def name(self) -> str:
        return self._name

    @property
    def max(self) -> float:
        return self._object_info.max

    @property
    def min(self) -> float:
        return self._object_info.min

    @property
    def max_ex(self) -> float:
        return self._object_info.maxEx

    @property
    def min_ex(self) -> float:
        return self._object_info.minEx

    @property
    def precision(self) -> int:
        return self._object_info.precision

    @property
    def value_type(self) -> ValueType:
        return ValueType(self._object_info.type)

    @property
    def unit(self) -> str:
        return self._object_info.unit.decode(RC["ENCODING"])

    @property
    def force_upload(self) -> bool:
        return self._force_upload

    @force_upload.setter
    def force_upload(self, value: bool) -> None:
        if not isinstance(value, bool):
            err_msg = f"value must be {bool}, but is {type(value)}"
            raise TypeError(err_msg)
        self._force_upload = value

    def __str__(self) -> str:
        return f"{self.__class__.__name__}({self._name})"

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}("
            f"asap3_handle={self._asap3_handle!r}, "
            f"module_handle={self._module_handle!r}, "
            f"name={self._name!r}, "
            f"object_info)"
        )


[docs] class ScalarCalibrationObject(BaseCalibrationObject): """0D calibration object""" @property def value(self) -> float: cov = self._read_calibration_object_value() return float(cov.value.value) @value.setter def value(self, new_value: float) -> None: cov = self._read_calibration_object_value() cov.value.value = new_value self._write_calibration_object_value(cov)
[docs] class AxisCalibrationObject(BaseCalibrationObject): """1D calibration object""" @cached_property def dimension(self) -> int: cov = self._read_calibration_object_value() return cov.axis.dimension @property def axis(self) -> "npt.NDArray[np.float64]": cov = self._read_calibration_object_value() np_array = np.ctypeslib.as_array(cov.axis.axis, shape=(cov.axis.dimension,)) return np_array.astype(dtype=float, copy=True) @axis.setter def axis(self, new_axis: typing.Sequence[float]) -> None: cov = self._read_calibration_object_value() axis = (ctypes.c_double * len(new_axis))(*new_axis) cov.axis.axis = axis self._write_calibration_object_value(cov)
[docs] class CurveCalibrationObject(BaseCalibrationObject): """2D Calibration Object""" @cached_property def dimension(self) -> int: cov = self._read_calibration_object_value() return cov.curve.dimension @property def axis(self) -> "npt.NDArray[np.float64]": cov = self._read_calibration_object_value() np_array = np.ctypeslib.as_array(cov.curve.axis, shape=(cov.curve.dimension,)) return np_array.astype(dtype=float, copy=True) @axis.setter def axis(self, new_axis: typing.Sequence[float]) -> None: cov = self._read_calibration_object_value() axis = (ctypes.c_double * cov.curve.dimension)(*new_axis) cov.curve.axis = axis self._write_calibration_object_value(cov) @property def values(self) -> "npt.NDArray[np.float64]": cov = self._read_calibration_object_value() np_array = np.ctypeslib.as_array(cov.curve.values, shape=(cov.curve.dimension,)) return np_array.astype(dtype=float, copy=True) @values.setter def values(self, values: typing.Sequence[float]) -> None: cov = self._read_calibration_object_value() c_array = (ctypes.c_double * cov.curve.dimension)(*values) cov.curve.values = c_array self._write_calibration_object_value(cov)
[docs] class MapCalibrationObject(BaseCalibrationObject): """3D calibration object""" @cached_property def x_dimension(self) -> int: cov = self._read_calibration_object_value() return cov.map.xDimension @cached_property def y_dimension(self) -> int: cov = self._read_calibration_object_value() return cov.map.yDimension @property def x_axis(self) -> "npt.NDArray[np.float64]": cov = self._read_calibration_object_value() np_array = np.ctypeslib.as_array(cov.map.xAxis, shape=(cov.map.xDimension,)) return np_array.astype(dtype=float, copy=True) @x_axis.setter def x_axis(self, new_x_axis: typing.Sequence[float]) -> None: cov = self._read_calibration_object_value() c_array = (ctypes.c_double * cov.map.xDimension)(*new_x_axis) cov.map.xAxis = c_array self._write_calibration_object_value(cov) @property def y_axis(self) -> "npt.NDArray[np.float64]": cov = self._read_calibration_object_value() np_array = np.ctypeslib.as_array(cov.map.yAxis, shape=(cov.map.yDimension,)) return np_array.astype(dtype=float, copy=True) @y_axis.setter def y_axis(self, new_y_axis: typing.Sequence[float]) -> None: cov = self._read_calibration_object_value() c_array = (ctypes.c_double * cov.map.yDimension)(*new_y_axis) cov.map.yAxis = c_array self._write_calibration_object_value(cov) @property def values(self) -> "npt.NDArray[np.float64]": cov = self._read_calibration_object_value() if cov.type == ValueType.MAP: np_array = np.ctypeslib.as_array( cov.map.values, shape=(cov.map.xDimension, cov.map.yDimension) ) elif cov.type == ValueType.VAL_BLK: np_array = np.ctypeslib.as_array( cov.valblk.values, shape=(cov.valblk.xDimension, cov.valblk.yDimension) ) else: raise ValueError return np_array.astype(dtype=float, copy=True) @values.setter def values(self, new_values: "npt.NDArray[np.float64]") -> None: cov = self._read_calibration_object_value() if cov.type == ValueType.MAP: c_array = (ctypes.c_double * (cov.map.xDimension * cov.map.yDimension))( *new_values.flatten() ) cov.map.values = c_array elif cov.type == ValueType.VAL_BLK: c_array = ( ctypes.c_double * (cov.valblk.xDimension * cov.valblk.yDimension) )(*new_values.flatten()) cov.valblk.values = c_array else: raise ValueError self._write_calibration_object_value(cov)
[docs] class AsciiCalibrationObject(BaseCalibrationObject): @cached_property def len(self) -> int: cov = self._read_calibration_object_value() return cov.ascii.len @property def ascii(self) -> str: cov = self._read_calibration_object_value() return cov.ascii.ascii[: self.len].decode(RC["ENCODING"]) @ascii.setter def ascii(self, new_ascii: str) -> None: cov = self._read_calibration_object_value() cov.ascii.ascii = new_ascii.encode(RC["ENCODING"]) self._write_calibration_object_value(cov)
[docs] class ValueBlockCalibrationObject(BaseCalibrationObject): @cached_property def x_dimension(self) -> int: cov = self._read_calibration_object_value() return cov.valblk.xDimension @cached_property def y_dimension(self) -> int: cov = self._read_calibration_object_value() return cov.valblk.yDimension @property def values(self) -> "npt.NDArray[np.float64]": cov = self._read_calibration_object_value() np_array = np.ctypeslib.as_array( cov.valblk.values, shape=(cov.valblk.xDimension, cov.valblk.yDimension) ) if cov.valblk.yDimension < _MULTIPLE_DIMENSION_THR: np_array = np_array.flatten() return np_array.astype(dtype=float, copy=True) @values.setter def values(self, new_values: "npt.NDArray[np.float64]") -> None: cov = self._read_calibration_object_value() c_array = (ctypes.c_double * (cov.valblk.xDimension * cov.valblk.yDimension))( *new_values.flatten() ) cov.valblk.values = c_array self._write_calibration_object_value(cov)
CalibrationObject = typing.Union[ ScalarCalibrationObject, AxisCalibrationObject, CurveCalibrationObject, MapCalibrationObject, AsciiCalibrationObject, ValueBlockCalibrationObject, ] def get_calibration_object( dll: CANapeDll, asap3_handle: TAsap3Hdl, # type: ignore[valid-type] module_handle: typing.Union[TModulHdl, int], name: str, ) -> CalibrationObject: object_info = DBObjectInfo() found = dll.Asap3GetDBObjectInfo( asap3_handle, module_handle, name.encode(RC["ENCODING"]), ctypes.byref(object_info), ) if not found: err_msg = f"{name} not found." raise KeyError(err_msg) cal_obj_map: typing.Dict[ValueType, typing.Type[CalibrationObject]] = { ValueType.VALUE: ScalarCalibrationObject, ValueType.CURVE: CurveCalibrationObject, ValueType.MAP: MapCalibrationObject, ValueType.AXIS: AxisCalibrationObject, ValueType.ASCII: AsciiCalibrationObject, ValueType.VAL_BLK: ValueBlockCalibrationObject, } try: cal_obj_type = cal_obj_map[object_info.type] except KeyError: err_msg = f"Calibration object {name} has unknown value type." raise TypeError(err_msg) from None return cal_obj_type( dll=dll, asap3_handle=asap3_handle, module_handle=module_handle, name=name, object_info=object_info, )