Use utility.lsample instead of numpy.uint in calibration._convert().
[pycomedi.git] / pycomedi / calibration.pyx
1 # Copyright (C) 2011-2012 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of pycomedi.
4 #
5 # pycomedi is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 2 of the License, or (at your option) any later
8 # version.
9 #
10 # pycomedi is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # pycomedi.  If not, see <http://www.gnu.org/licenses/>.
16
17 """Pythonic wrappers for converting between Comedilib and physical units
18
19 For one-off conversions, use the functions `comedi_to_physical` and
20 `comedi_from_physical`.  For repeated conversions, use an instance of
21 `CalibratedConverter`.
22 """
23
24 cimport numpy as _numpy
25 import numpy as _numpy
26
27 cimport _comedi_h
28 cimport _comedilib_h
29 import constant as _constant
30 import utility as _utility
31
32 cdef void _setup_comedi_polynomial_t(
33     _comedilib_h.comedi_polynomial_t *p, coefficients, expansion_origin):
34     """Setup the `comedi_polynomial_t` at `p`
35
36     * `coefficients` is an iterable containing polynomial coefficients
37     * `expansion_origin` is the center of the polynomial expansion
38     """
39     for i,x in enumerate(coefficients):
40         p.coefficients[i] = x
41     p.order = len(coefficients)-1
42     p.expansion_origin = expansion_origin
43
44 cdef object _convert(
45     _comedilib_h.comedi_polynomial_t *p, object data, object direction):
46     """Apply the polynomial conversion `p` to `data`.
47
48     `direction` should be a value from `constant.CONVERSION_DIRECTION`.
49     """
50     to_physical = (_constant.bitwise_value(direction)
51                    == _constant.CONVERSION_DIRECTION.to_physical.value)
52     if _numpy.isscalar(data):
53         if to_physical:
54             return _comedilib_h.comedi_to_physical(data, p)
55         else:
56             return _comedilib_h.comedi_from_physical(data, p)
57     if to_physical:
58         dtype = _numpy.double
59     else:
60         dtype = _utility.lsampl
61     array = _numpy.array(data, dtype=dtype)
62     for i,d in enumerate(data):
63         if to_physical:
64             array[i] = _comedilib_h.comedi_to_physical(d, p)
65         else:
66             array[i] = _comedilib_h.comedi_from_physical(d, p)
67     return array
68
69 cpdef comedi_to_physical(data, coefficients, expansion_origin):
70     """Convert Comedi bit values (`lsampl_t`) to physical units (`double`)
71
72     * `data` is the value to be converted (scalar or array-like)
73     * `coefficients` and `expansion_origin` should be appropriate
74       for `_setup_comedi_polynomial_t`.  TODO: expose it's docstring?
75
76     The conversion algorithm is::
77
78         x = sum_i c_i * (d-d_o)^i
79
80     where `x` is the returned physical value, `d` is the supplied data,
81     `c_i` is the `i`\th coefficient, and `d_o` is the expansion origin.
82
83     >>> print comedi_to_physical.__doc__  # doctest: +ELLIPSIS
84     Convert Comedi bit values (`lsampl_t`) to physical units (`double`)
85     ...
86     >>> comedi_to_physical(1, [1, 2, 3], 2)
87     2.0
88     >>> comedi_to_physical([1, 2, 3], [1, 2, 3], 2)
89     array([ 2.,  1.,  6.])
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.to_physical)
94
95 cpdef comedi_from_physical(data, coefficients, expansion_origin):
96     """Convert physical units to Comedi bit values
97
98     Like `comedi_to_physical` but converts `double` -> `lsampl_t`.
99
100     >>> comedi_from_physical(1, [1,2,3], 2)
101     2L
102     >>> comedi_from_physical([1, 2, 3], [1, 2, 3], 2)
103     array([2, 1, 6], dtype=uint32)
104     """
105     cdef _comedilib_h.comedi_polynomial_t p
106     _setup_comedi_polynomial_t(&p, coefficients, expansion_origin)
107     return _convert(&p, data, _constant.CONVERSION_DIRECTION.from_physical)
108
109
110 cdef class CalibratedConverter (object):
111     """Apply a converion polynomial
112
113     Usually you would get the this converter from
114     `DataChannel.get_converter()` or similar. but for testing, we'll
115     just create one out of thin air.
116
117     >>> c = CalibratedConverter(
118     ...     to_physical_coefficients=[1, 2, 3],
119     ...     to_physical_expansion_origin=1)
120     >>> c  # doctest: +NORMALIZE_WHITESPACE
121     <CalibratedConverter
122      to_physical:{coefficients:[1.0, 2.0, 3.0] origin:1.0}
123      from_physical:{coefficients:[0.0] origin:0.0}>
124
125     >>> c.to_physical(1)
126     1.0
127     >>> c.to_physical([0, 1, 2])
128     array([ 2.,  1.,  6.])
129     >>> c.to_physical(_numpy.array([0, 1, 2, 3], dtype=_numpy.uint))
130     array([  2.,   1.,   6.,  17.])
131
132     >>> c.get_to_physical_expansion_origin()
133     1.0
134     >>> c.get_to_physical_coefficients()
135     array([ 1.,  2.,  3.])
136     """
137     def __init__(self, to_physical_coefficients=None,
138                  to_physical_expansion_origin=0,
139                  from_physical_coefficients=None,
140                  from_physical_expansion_origin=0):
141         if to_physical_coefficients:
142             _setup_comedi_polynomial_t(
143                 &self._to_physical, to_physical_coefficients,
144                  to_physical_expansion_origin)
145         if from_physical_coefficients:
146             _setup_comedi_polynomial_t(
147                 &self._from_physical, from_physical_coefficients,
148                  from_physical_expansion_origin)
149
150     cdef _str_poly(self, _comedilib_h.comedi_polynomial_t polynomial):
151         return '{coefficients:%s origin:%s}' % (
152             [float(polynomial.coefficients[i])
153              for i in range(polynomial.order+1)],
154             float(polynomial.expansion_origin))
155
156     def __str__(self):
157         return '<%s to_physical:%s from_physical:%s>' % (
158             self.__class__.__name__, self._str_poly(self._to_physical),
159             self._str_poly(self._from_physical))
160
161     def __repr__(self):
162         return self.__str__()
163
164     cpdef to_physical(self, data):
165         return _convert(&self._to_physical, data,
166                         _constant.CONVERSION_DIRECTION.to_physical)
167
168     cpdef from_physical(self, data):
169         return _convert(&self._from_physical, data,
170                         _constant.CONVERSION_DIRECTION.from_physical)
171
172     cpdef get_to_physical_expansion_origin(self):
173         return self._to_physical.expansion_origin
174
175     cpdef get_to_physical_coefficients(self):
176         ret = _numpy.ndarray((self._to_physical.order+1,), _numpy.double)
177         for i in xrange(len(ret)):
178             ret[i] = self._to_physical.coefficients[i]
179         return ret
180
181     cpdef get_from_physical_expansion_origin(self):
182         return self._from_physical.expansion_origin
183
184     cpdef get_from_physical_coefficients(self):
185         ret = _numpy.ndarray((self._from_physical.order+1,), _numpy.double)
186         for i in xrange(len(ret)):
187             ret[i] = self._from_physical.coefficients[i]
188         return ret
189
190
191 # TODO: see comedi_caldac_t and related at end of comedilib.h