Mostly-complete Cython implementation.
[pycomedi.git] / pycomedi / calibration.pyx
1 # Copyright
2
3 """Pythonic wrappers for converting between Comedilib and physical units
4
5 For one-off conversions, use the functions `comedi_to_physical` and
6 `comedi_from_physical`.  For repeated conversions, use an instance of
7 `CalibratedConverter`.
8 """
9
10 cimport numpy as _numpy
11 import numpy as _numpy
12
13 cimport _comedi_h
14 cimport _comedilib_h
15 import  constant as _constant
16
17
18 cdef void _setup_comedi_polynomial_t(
19     _comedilib_h.comedi_polynomial_t *p, coefficients, expansion_origin):
20     """Setup the `comedi_polynomial_t` at `p`
21
22     * `coefficients` is an iterable containing polynomial coefficients
23     * `expansion_origin` is the center of the polynomial expansion
24     """
25     for i,x in enumerate(coefficients):
26         p.coefficients[i] = x
27     p.order = len(coefficients)-1
28     p.expansion_origin = expansion_origin
29
30 cdef object _convert(
31     _comedilib_h.comedi_polynomial_t *p, object data, object direction):
32     """Apply the polynomial conversion `p` to `data`.
33
34     `direction` should be a value from `constant.CONVERSION_DIRECTION`.
35     """
36     to_physical = (_constant.bitwise_value(direction)
37                    == _constant.CONVERSION_DIRECTION.to_physical.value)
38     if _numpy.isscalar(data):
39         if to_physical:
40             return _comedilib_h.comedi_to_physical(data, p)
41         else:
42             return _comedilib_h.comedi_from_physical(data, p)
43     if to_physical:
44         dtype = _numpy.double
45     else:
46         dtype = _numpy.uint
47     array = _numpy.array(data, dtype=dtype)
48     for i,d in enumerate(data):
49         if to_physical:
50             array[i] = _comedilib_h.comedi_to_physical(d, p)
51         else:
52             array[i] = _comedilib_h.comedi_from_physical(d, p)
53     return array
54
55 cpdef comedi_to_physical(data, coefficients, expansion_origin):
56     """Convert Comedi bit values (`lsampl_t`) to physical units (`double`)
57
58     * `data` is the value to be converted (scalar or array-like)
59     * `coefficients` and `expansion_origin` should be appropriate
60       for `_setup_comedi_polynomial_t`.  TODO: expose it's docstring?
61
62     The conversion algorithm is::
63
64         x = sum_i c_i * (d-d_o)^i
65
66     where `x` is the returned physical value, `d` is the supplied data,
67     `c_i` is the `i`\th coefficient, and `d_o` is the expansion origin.
68
69     >>> print comedi_to_physical.__doc__  # doctest: +ELLIPSIS
70     Convert Comedi bit values (`lsampl_t`) to physical units (`double`)
71     ...
72     >>> comedi_to_physical(1, [1, 2, 3], 2)
73     2.0
74     >>> comedi_to_physical([1, 2, 3], [1, 2, 3], 2)
75     array([ 2.,  1.,  6.])
76     """
77     cdef _comedilib_h.comedi_polynomial_t p
78     _setup_comedi_polynomial_t(&p, coefficients, expansion_origin)
79     return _convert(&p, data, _constant.CONVERSION_DIRECTION.to_physical)
80
81 cpdef comedi_from_physical(data, coefficients, expansion_origin):
82     """Convert physical units to Comedi bit values
83
84     Like `comedi_to_physical` but converts `double` -> `lsampl_t`.
85
86     >>> comedi_from_physical(1, [1,2,3], 2)
87     2L
88     >>> comedi_from_physical([1, 2, 3], [1, 2, 3], 2)
89     array([2, 1, 6], dtype=uint32)
90     """
91     cdef _comedilib_h.comedi_polynomial_t p
92     _setup_comedi_polynomial_t(&p, coefficients, expansion_origin)
93     return _convert(&p, data, _constant.CONVERSION_DIRECTION.from_physical)
94
95
96 cdef class CalibratedConverter (object):
97     """Apply a converion polynomial
98
99     Usually you would get the this converter from
100     `DataChannel.get_converter()` or similar. but for testing, we'll
101     just create one out of thin air.
102
103     >>> c = CalibratedConverter(
104     ...     to_physical_coefficients=[1, 2, 3],
105     ...     to_physical_expansion_origin=1)
106     >>> c  # doctest: +NORMALIZE_WHITESPACE
107     <CalibratedConverter
108      to_physical:{coefficients:[1.0, 2.0, 3.0] origin:1.0}
109      from_physical:{coefficients:[0.0] origin:0.0}>
110
111     >>> c.to_physical(1)
112     1.0
113     >>> c.to_physical([0, 1, 2])
114     array([ 2.,  1.,  6.])
115     >>> c.to_physical(_numpy.array([0, 1, 2, 3], dtype=_numpy.uint))
116     array([  2.,   1.,   6.,  17.])
117
118     >>> c.get_to_physical_expansion_origin()
119     1.0
120     >>> c.get_to_physical_coefficients()
121     array([ 1.,  2.,  3.])
122     """
123     def __init__(self, to_physical_coefficients=None,
124                  to_physical_expansion_origin=0,
125                  from_physical_coefficients=None,
126                  from_physical_expansion_origin=0):
127         if to_physical_coefficients:
128             _setup_comedi_polynomial_t(
129                 &self._to_physical, to_physical_coefficients,
130                  to_physical_expansion_origin)
131         if from_physical_coefficients:
132             _setup_comedi_polynomial_t(
133                 &self._from_physical, from_physical_coefficients,
134                  from_physical_expansion_origin)
135
136     cdef _str_poly(self, _comedilib_h.comedi_polynomial_t polynomial):
137         return '{coefficients:%s origin:%s}' % (
138             [float(polynomial.coefficients[i])
139              for i in range(polynomial.order+1)],
140             float(polynomial.expansion_origin))
141
142     def __str__(self):
143         return '<%s to_physical:%s from_physical:%s>' % (
144             self.__class__.__name__, self._str_poly(self._to_physical),
145             self._str_poly(self._from_physical))
146
147     def __repr__(self):
148         return self.__str__()
149
150     cpdef to_physical(self, data):
151         return _convert(&self._to_physical, data,
152                         _constant.CONVERSION_DIRECTION.to_physical)
153
154     cpdef from_physical(self, data):
155         return _convert(&self._from_physical, data,
156                         _constant.CONVERSION_DIRECTION.from_physical)
157
158     cpdef get_to_physical_expansion_origin(self):
159         return self._to_physical.expansion_origin
160
161     cpdef get_to_physical_coefficients(self):
162         ret = _numpy.ndarray((self._to_physical.order+1,), _numpy.double)
163         for i in xrange(len(ret)):
164             ret[i] = self._to_physical.coefficients[i]
165         return ret
166
167     cpdef get_from_physical_expansion_origin(self):
168         return self._from_physical.expansion_origin
169
170     cpdef get_from_physical_coefficients(self):
171         ret = _numpy.ndarray((self._from_physical.order+1,), _numpy.double)
172         for i in xrange(len(ret)):
173             ret[i] = self._from_physical.coefficients[i]
174         return ret
175
176
177 # TODO: see comedi_caldac_t and related at end of comedilib.h