Reputation: 26118
I am looking for ideas on how to translate one range values to another in Python. I am working on hardware project and am reading data from a sensor that can return a range of values, I am then using that data to drive an actuator that requires a different range of values.
For example lets say that the sensor returns values in the range 1 to 512, and the actuator is driven by values in the range 5 to 10. I would like a function that I can pass a value and the two ranges and get back the value mapped to the second range. If such a function was named translate
it could be used like this:
sensor_value = 256
actuator_value = translate(sensor_value, 1, 512, 5, 10)
In this example I would expect the output actuator_value
to be 7.5
since the sensor_value
is in the middle of the possible input range.
Upvotes: 102
Views: 167324
Reputation: 107
v
is value. Maps a-b
range to c-d
range
def map_range(v, a, b, c, d):
return (v-a) / (b-a) * (d-c) + c
usage
a = map_range(4, 0, 10, 0, 1)
print(a) # -> 0.4
Upvotes: 1
Reputation: 670
You could use a lambda function
translate = lambda a, b, c, d, e: (a - b) * (e - d) / (c - b) + d
sensor_value = 256
translate(sensor_value, 1, 512, 5, 10)
>> 7.495107632093934
Upvotes: 0
Reputation: 11
All of the existing answers are under the CC BY-SA license. Here's one that I wrote; to the extent possible under law, I waive all copyright or related and neighboring rights to this code. (Creative Commons CC0 Public Domain Dedication).
def remap(number, from_min, from_max, to_min, to_max):
number_s = number - from_min
from_max_s = from_max - from_min
to_max_s = to_max - to_min
return ((number_s / from_max_s) * to_max_s) + to_min
Upvotes: 1
Reputation: 183
Simple map range function:
def mapRange(value, inMin, inMax, outMin, outMax):
return outMin + (((value - inMin) / (inMax - inMin)) * (outMax - outMin))
Upvotes: 7
Reputation: 7121
def maprange(a, b, s):
(a1, a2), (b1, b2) = a, b
return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))
a = [from_lower, from_upper]
b = [to_lower, to_upper]
found at https://rosettacode.org/wiki/Map_range#Python_
a
or b
(it extrapolates)from_lower > from_upper
or to_lower > to_upper
Upvotes: 4
Reputation: 161
I was looking for the same thing in python to map angles 0-300deg to raw dynamixel values 0-1023, or 1023-0 depending on the actuator orientations.
I ended up going very simple.
x:input value;
a,b:input range
c,d:output range
y:return value
def mapFromTo(x,a,b,c,d):
y=(x-a)/(b-a)*(d-c)+c
return y
dyn111.goal_position=mapFromTo(pos111,0,300,0,1024)
Upvotes: 15
Reputation: 63719
This would actually be a good case for creating a closure, that is write a function that returns a function. Since you probably have many of these values, there is little value in calculating and recalculating these value spans and factors for every value, nor for that matter, in passing those min/max limits around all the time.
Instead, try this:
def make_interpolater(left_min, left_max, right_min, right_max):
# Figure out how 'wide' each range is
leftSpan = left_max - left_min
rightSpan = right_max - right_min
# Compute the scale factor between left and right values
scaleFactor = float(rightSpan) / float(leftSpan)
# create interpolation function using pre-calculated scaleFactor
def interp_fn(value):
return right_min + (value-left_min)*scaleFactor
return interp_fn
Now you can write your processor as:
# create function for doing interpolation of the desired
# ranges
scaler = make_interpolater(1, 512, 5, 10)
# receive list of raw values from sensor, assign to data_list
# now convert to scaled values using map
scaled_data = map(scaler, data_list)
# or a list comprehension, if you prefer
scaled_data = [scaler(x) for x in data_list]
Upvotes: 30
Reputation: 41511
You can also use scipy.interpolate
package to do such conversions (if you don't mind dependency on SciPy):
>>> from scipy.interpolate import interp1d
>>> m = interp1d([1,512],[5,10])
>>> m(256)
array(7.4951076320939336)
or to convert it back to normal float from 0-rank scipy array:
>>> float(m(256))
7.4951076320939336
You can do also multiple conversions in one command easily:
>>> m([100,200,300])
array([ 5.96868885, 6.94716243, 7.92563601])
As a bonus, you can do non-uniform mappings from one range to another, for intance if you want to map [1,128] to [1,10], [128,256] to [10,90] and [256,512] to [90,100] you can do it like this:
>>> m = interp1d([1,128,256,512],[1,10,90,100])
>>> float(m(400))
95.625
interp1d
creates piecewise linear interpolation objects (which are callable just like functions).
As noted by ~unutbu, numpy.interp
is also an option (with less dependencies):
>>> from numpy import interp
>>> interp(256,[1,512],[5,10])
7.4951076320939336
Upvotes: 123
Reputation: 5019
One solution would be:
def translate(value, leftMin, leftMax, rightMin, rightMax):
# Figure out how 'wide' each range is
leftSpan = leftMax - leftMin
rightSpan = rightMax - rightMin
# Convert the left range into a 0-1 range (float)
valueScaled = float(value - leftMin) / float(leftSpan)
# Convert the 0-1 range into a value in the right range.
return rightMin + (valueScaled * rightSpan)
You could possibly use algebra to make it more efficient, at the expense of readability.
Upvotes: 122
Reputation: 113955
def translate(sensor_val, in_from, in_to, out_from, out_to):
out_range = out_to - out_from
in_range = in_to - in_from
in_val = sensor_val - in_from
val=(float(in_val)/in_range)*out_range
out_val = out_from+val
return out_val
Upvotes: 7