From cfc156b15b926d38e95ef830d65e83b40e583e00 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 16 Oct 2012 15:11:37 -0400 Subject: [PATCH] calibration: add Caldac, CalibrationSetting, and Calibration. Add Python wrappers around comedi_calibration_t and its sub-structures. This allows you to load calibration files (soft and hard) and manipulate their contents in Python. --- pycomedi/calibration.pxd | 20 ++ pycomedi/calibration.pyx | 444 +++++++++++++++++++++++++++++++++++++++ pycomedi/device.pyx | 10 +- 3 files changed, 468 insertions(+), 6 deletions(-) diff --git a/pycomedi/calibration.pxd b/pycomedi/calibration.pxd index 8f7c6af..047bf0b 100644 --- a/pycomedi/calibration.pxd +++ b/pycomedi/calibration.pxd @@ -17,6 +17,8 @@ "Expose `CalibratedConverter` internals at the C level for other Cython modules" cimport _comedilib_h +from device cimport Device as _Device +from subdevice cimport Subdevice as _Subdevice cdef class CalibratedConverter (object): @@ -29,3 +31,21 @@ cdef class CalibratedConverter (object): cpdef get_to_physical_coefficients(self) cpdef get_from_physical_expansion_origin(self) cpdef get_from_physical_coefficients(self) + + +cdef class Caldac (object): + cdef _comedilib_h.comedi_caldac_t *caldac + + +cdef class CalibrationSetting (object): + cdef _comedilib_h.comedi_calibration_setting_t *setting + cdef public _Subdevice subdevice + + cpdef _soft_calibration_set(self, CalibratedConverter value) + + +cdef class Calibration (object): + cdef _comedilib_h.comedi_calibration_t *calibration + cdef public _Device device + + cpdef from_file(self, path) diff --git a/pycomedi/calibration.pyx b/pycomedi/calibration.pyx index 7d5dd80..6e688b3 100644 --- a/pycomedi/calibration.pyx +++ b/pycomedi/calibration.pyx @@ -21,14 +21,42 @@ For one-off conversions, use the functions `comedi_to_physical` and `CalibratedConverter`. """ +from libc cimport stdlib as _stdlib +from libc cimport string as _string cimport numpy as _numpy import numpy as _numpy cimport _comedi_h cimport _comedilib_h +import _error import constant as _constant import utility as _utility + +cdef void _python_to_charp(char **charp, object obj, object encoding): + """Convert a Python string into a `char *`. + + Cython automatically converts string or byte array to a `char *` + for use with external C libraries. However, the resulting + pointers are only valid until the Python object is garbage + collected. For the `Calibration` class, we need persistent + pointers that will be manually freed later. This function creates + these manual copies. + """ + cdef char *ret + cdef char *src + if charp[0] is not NULL: # charp[0] is the same as *charp + _stdlib.free(charp[0]) + charp[0] = NULL + if hasattr(obj, 'encode'): + obj = obj.encode(encoding, 'strict') + src = obj + ret = _stdlib.malloc(len(obj) + 1) + if ret is NULL: + raise MemoryError() + _string.strncpy(ret, src, len(obj) + 1) + charp[0] = ret + cdef void _setup_comedi_polynomial_t( _comedilib_h.comedi_polynomial_t *p, coefficients, expansion_origin): """Setup the `comedi_polynomial_t` at `p` @@ -188,4 +216,420 @@ cdef class CalibratedConverter (object): return ret +cdef class Caldac (object): + """Class wrapping comedi_caldac_t + + >>> from .device import Device + >>> from . import constant + + >>> d = Device('/dev/comedi0') + >>> d.open() + + >>> c = d.parse_calibration() + >>> s = c.settings[0] + >>> print(s) + + >>> caldac = s.caldacs[0] + >>> print(caldac) + + + >>> d.close() + """ + def __cinit__(self): + self.caldac = NULL + + def __str__(self): + fields = ['{}:{}'.format(f, getattr(self, f)) + for f in ['subdevice', 'channel', 'value']] + return '<{} {}>'.format(self.__class__.__name__, ' '.join(fields)) + + def __repr__(self): + return self.__str__() + + def _subdevice_get(self): + if self.caldac is not NULL: + return self.caldac.subdevice + def _subdevice_set(self, value): + assert self.caldac is not NULL, 'load caldac first' + self.caldac.subdevice = value + subdevice = property(fget=_subdevice_get, fset=_subdevice_set) + + def _channel_get(self): + if self.caldac is not NULL: + return self.caldac.channel + def _channel_set(self, value): + assert self.caldac is not NULL, 'load caldac first' + self.caldac.channel = value + channel = property(fget=_channel_get, fset=_channel_set) + + def _value_get(self): + if self.caldac is not NULL: + return self.caldac.value + def _value_set(self, value): + assert self.caldac is not NULL, 'load caldac first' + self.caldac.value = value + value = property(fget=_value_get, fset=_value_set) + + +cdef class CalibrationSetting (object): + """Class wrapping comedi_calibration_setting_t + + >>> from .device import Device + >>> from . import constant + + >>> d = Device('/dev/comedi0') + >>> d.open() + + >>> c = d.parse_calibration() + >>> s = c.settings[0] + + >>> print(s) + + >>> print(s.subdevice) # doctest: +ELLIPSIS + + >>> for s in c.settings: + ... print('{} {}'.format(s.subdevice.index, s.subdevice.get_type())) + ... print(' channels: {}'.format(s.channels)) + ... print(' ranges: {}'.format(s.ranges)) + ... print(' arefs: {}'.format(s.arefs)) + ... print(' caldacs:') + ... for caldac in s.caldacs: + ... print(' {}'.format(caldac)) + ... print(' soft_calibration:') + ... sc = s.soft_calibration + ... print(' to physical coefficients: {}'.format( + ... sc.get_to_physical_coefficients())) + ... print(' to physical origin: {}'.format( + ... sc.get_to_physical_expansion_origin())) + ... print(' from physical coefficients: {}'.format( + ... sc.get_from_physical_coefficients())) + ... print(' from physical origin: {}'.format( + ... sc.get_from_physical_expansion_origin())) + ... # doctest: +REPORT_UDIFF + 0 ai + channels: [] + ranges: [] + arefs: [] + caldacs: + + + + + + + soft_calibration: + to physical coefficients: [ 0.] + to physical origin: 0.0 + from physical coefficients: [ 0.] + from physical origin: 0.0 + 0 ai + channels: [] + ranges: [ 8 9 10 11 12 13 14 15] + arefs: [] + caldacs: + + + soft_calibration: + to physical coefficients: [ 0.] + to physical origin: 0.0 + from physical coefficients: [ 0.] + from physical origin: 0.0 + 1 ao + channels: [0] + ranges: [0 2] + arefs: [] + caldacs: + + + + + soft_calibration: + to physical coefficients: [ 0.] + to physical origin: 0.0 + from physical coefficients: [ 0.] + from physical origin: 0.0 + 1 ao + channels: [0] + ranges: [1 3] + arefs: [] + caldacs: + + + + + soft_calibration: + to physical coefficients: [ 0.] + to physical origin: 0.0 + from physical coefficients: [ 0.] + from physical origin: 0.0 + 1 ao + channels: [1] + ranges: [0 2] + arefs: [] + caldacs: + + + + + soft_calibration: + to physical coefficients: [ 0.] + to physical origin: 0.0 + from physical coefficients: [ 0.] + from physical origin: 0.0 + 1 ao + channels: [1] + ranges: [1 3] + arefs: [] + caldacs: + + + + + soft_calibration: + to physical coefficients: [ 0.] + to physical origin: 0.0 + from physical coefficients: [ 0.] + from physical origin: 0.0 + + >>> d.close() + """ + def __cinit__(self): + self.setting = NULL + + def __init__(self, subdevice): + super(CalibrationSetting, self).__init__() + self.subdevice = subdevice + + def __str__(self): + fields = [ + 'device:{}'.format(self.subdevice.device.filename), + 'subdevice:{}'.format(self.subdevice.index), + ] + return '<{} {}>'.format(self.__class__.__name__, ' '.join(fields)) + + def _channels_get(self): + if self.setting is NULL: + return None + ret = _numpy.ndarray(shape=(self.setting.num_channels,), dtype=int) + # TODO: point into existing data array? + for i in range(self.setting.num_channels): + ret[i] = self.setting.channels[i] + return ret + def _channels_set(self, value): + assert self.setting is not NULL, 'load setting first' + if self.setting.channels is not NULL: + _stdlib.free(self.setting.channels) + length = len(value) + self.setting.channels = _stdlib.malloc( + length * sizeof(unsigned int)) + if self.setting.channels is NULL: + self.setting.num_channels = 0 + raise MemoryError() + self.setting.num_channels = length + for i,x in enumerate(value): + if i >= length: + raise ValueError((i, length)) + self.setting.channels[i] = x + channels = property(fget=_channels_get, fset=_channels_set) + + def _ranges_get(self): + if self.setting is NULL: + return None + ret = _numpy.ndarray(shape=(self.setting.num_ranges,), dtype=int) + # TODO: point into existing data array? + for i in range(self.setting.num_ranges): + ret[i] = self.setting.ranges[i] + return ret + def _ranges_set(self, value): + assert self.setting is not NULL, 'load setting first' + if self.setting.ranges is not NULL: + _stdlib.free(self.setting.ranges) + length = len(value) + self.setting.ranges = _stdlib.malloc( + length * sizeof(unsigned int)) + if self.setting.ranges is NULL: + self.setting.num_ranges = 0 + raise MemoryError() + self.setting.num_ranges = length + for i,x in enumerate(value): + if i >= length: + raise ValueError((i, length)) + self.setting.ranges[i] = x + ranges = property(fget=_ranges_get, fset=_ranges_set) + + def _arefs_get(self): + if self.setting is NULL: + return None + ret = _numpy.ndarray(shape=(self.setting.num_arefs,), dtype=int) + # TODO: point into existing data array? + for i in range(self.setting.num_arefs): + ret[i] = self.setting.arefs[i] + return ret + def _arefs_set(self, value): + assert self.setting is not NULL, 'load setting first' + length = len(value) + for i,x in enumerate(value): + if i >= _comedilib_h.CS_MAX_AREFS_LENGTH: + raise ValueError((i, _comedilib_h.CS_MAX_AREFS_LENGTH)) + self.setting.arefs[i] = x + for i in range(length, _comedilib_h.CS_MAX_AREFS_LENGTH): + self.setting.arefs[i] = 0 + arefs = property(fget=_arefs_get, fset=_arefs_set) + + def _caldacs_get(self): + if self.setting is NULL: + return None + if not self.setting.num_caldacs: + return [] + # TODO: point into existing data array? + ret = [] + for i in range(self.setting.num_caldacs): + c = Caldac() + c.caldac = &self.setting.caldacs[i] + ret.append(c) + return ret + def _caldacs_set(self, value): + assert self.setting is not NULL, 'load setting first' + if self.setting.caldacs is not NULL: + _stdlib.free(self.setting.caldacs) + length = len(value) + self.setting.caldacs = <_comedilib_h.comedi_caldac_t *> _stdlib.malloc( + length * sizeof(_comedilib_h.comedi_caldac_t)) + if self.setting.caldacs is NULL: + self.setting.num_caldacs = 0 + raise MemoryError() + self.setting.num_caldacs = length + for i,x in enumerate(value): + if i >= length: + raise ValueError((i, length)) + self.setting.caldacs[i] = x + caldacs = property(fget=_caldacs_get, fset=_caldacs_set) + + def _soft_calibration_get(self): + cdef CalibratedConverter ret + if self.setting is NULL: + return None + ret = CalibratedConverter() + if self.setting.soft_calibration.to_phys is not NULL: + ret._to_physical = self.setting.soft_calibration.to_phys[0] + if self.setting.soft_calibration.from_phys is not NULL: + ret._from_physical = self.setting.soft_calibration.from_phys[0] + return ret + cpdef _soft_calibration_set(self, CalibratedConverter value): + assert self.setting is not NULL, 'load setting first' + if (self.setting.soft_calibration.to_phys is NULL and + (value._to_physical.expansion_origin or + value._to_physical.order >= 0)): + self.setting.soft_calibration.to_phys = ( + <_comedilib_h.comedi_polynomial_t *> _stdlib.malloc( + sizeof(_comedilib_h.comedi_polynomial_t))) + self.setting.soft_calibration.to_phys[0] = value._to_physical + if (self.setting.soft_calibration.from_phys is NULL and + (value._from_physical.expansion_origin or + value._from_physical.order >= 0)): + self.setting.soft_calibration.from_phys = ( + <_comedilib_h.comedi_polynomial_t *> _stdlib.malloc( + sizeof(_comedilib_h.comedi_polynomial_t))) + self.setting.soft_calibration.from_phys[0] = value._from_physical + soft_calibration = property( + fget=_soft_calibration_get, fset=_soft_calibration_set) + + +cdef class Calibration (object): + """A board calibration configuration. + + Wraps comedi_calibration_t. + + Warning: You probably want to use the `.from_file()` method or + `device.parse_calibration()` rather than initializing this + stucture by hand. + + >>> from .device import Device + >>> from . import constant + + >>> d = Device('/dev/comedi0') + >>> d.open() + + >>> c = d.parse_calibration() + + >>> print(c) + + >>> c.driver_name + 'ni_pcimio' + >>> c.board_name + 'pci-6052e' + + >>> c.settings # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + [, + ... + ] + >>> print(c.settings[0]) + + + >>> name = c.driver_name + >>> c.driver_name = "Override with your own value" + >>> c.driver_name = name + + >>> d.close() + """ + def __cinit__(self): + self.calibration = NULL + + def __init__(self, device): + super(Calibration, self).__init__() + self.device = device + + def __dealloc__(self): + if self.calibration is not NULL: + _comedilib_h.comedi_cleanup_calibration(self.calibration) + self.calibration = NULL + + def __str__(self): + fields = ['device:{}'.format(self.device.filename)] + return '<{} {}>'.format(self.__class__.__name__, ' '.join(fields)) + + def __repr__(self): + return '<{} {}>'.format(self.__class__.__name__, id(self)) + + def _driver_name_get(self): + if self.calibration is NULL: + return None + return self.calibration.driver_name + def _driver_name_set(self, value): + assert self.calibration is not NULL, 'load calibration first' + _python_to_charp(&self.calibration.driver_name, value, 'ascii') + driver_name = property(fget=_driver_name_get, fset=_driver_name_set) + + def _board_name_get(self): + if self.calibration is NULL: + return None + return self.calibration.board_name + def _board_name_set(self, value): + assert self.calibration is not NULL, 'load calibration first' + _python_to_charp(&self.calibration.board_name, value, 'ascii') + board_name = property(fget=_board_name_get, fset=_board_name_set) + + def _settings_get(self): + if self.calibration is NULL: + return None + ret = [] + for i in range(self.calibration.num_settings): + s = CalibrationSetting( + subdevice=self.device.subdevice( + index=self.calibration.settings[i].subdevice)) + s.setting = &self.calibration.settings[i] + ret.append(s) + return ret + def _settings_set(self, value): + assert self.calibration is not NULL, 'load calibration first' + return None + settings = property(fget=_settings_get, fset=_settings_set) + + cpdef from_file(self, path): + self.calibration = _comedilib_h.comedi_parse_calibration_file(path) + if self.calibration == NULL: + _error.raise_error( + function_name='comedi_parse_calibration_file') + + # TODO: see comedi_caldac_t and related at end of comedilib.h diff --git a/pycomedi/device.pyx b/pycomedi/device.pyx index a7d6d16..e8329d1 100644 --- a/pycomedi/device.pyx +++ b/pycomedi/device.pyx @@ -24,6 +24,7 @@ from pycomedi import PyComediError as _PyComediError cimport _comedi_h cimport _comedilib_h import _error +from calibration import Calibration as _Calibration from instruction cimport Insn as _Insn from instruction import Insn as _Insn from subdevice import Subdevice as _Subdevice @@ -280,12 +281,9 @@ cdef class Device (object): """ if path is None: path = self.get_default_calibration_path() - - ret = _comedilib_h.comedi_parse_calibration_file(path) - if ret == NULL: - _error.raise_error( - function_name='comedi_parse_calibration_file') - return ret + c = _Calibration(device=self) + c.from_file(path) + return c # extensions to make a more idomatic Python interface -- 2.26.2