4

Im trying to find an efficient way to scale 2 byte (-32K -> +32K) numpy int arrays to 8 bit (0 -> 255) using a specific scaling function. The very inefficient method that works is (where minVal and maxVal are the min and Max values in the original 2 byte numpy array, and paddingVal in the original will be set to 0):

...

pixel_array = np.zeros( length, dtype=np.int16)
byte_array = np.zeros( length, dtype=np.uint8)

....

i = 0
for val in np.nditer(pixel_array):
    value = 0.0
    if val == paddingVal:
        byte_array[i] = 0
    else:
        value = 255.0 * ( val - minVal ) / (maxVal - minVal - 1.0)    
        byte_array[i] = (round(value))
    i += 1  

I cant figure out how to avoid the loop and still do the if... and apply the scaling function.

thx

3
  • 4
    Just FYI: There's absolutely no need for the loop. If you're not tightly memory constrained, just do byte_array = (255.0 * (pixel_array - minVal) / (maxVal - minVal - 1.0)).astype(np.uint8). You can set the "padding" values afterwards using byte_array[pixel_array == paddingVal] = 0. It's not memory-efficient, but it will be much faster than what you're currently doing. Commented Jun 18, 2013 at 17:28
  • well that is certainly faster. Lovely how compact python can make it. Commented Jun 18, 2013 at 17:53
  • 1
    Just so you're fore-warned, the version I posted in my comment implicitly takes the floor of the values instead of rounding. If you're fine with that, it's a bit faster than calling numpy.round, but it's not the same as your original code (@jorgeca's answer should give identical results to your original solution, though). Commented Jun 18, 2013 at 17:58

2 Answers 2

3

Try:

byte_array[i] = (((val << 16) >> 8) & 0xFF0000) >> 16

It assumes val is 32 bit integer between 0 and 65535

Sign up to request clarification or add additional context in comments.

3 Comments

val in this case is a negative signed 16 bit int, so the above wont solve the problem. In any case, the performance isn't radically different from the original. Somehow, have to get rid of the explicit loop, I think.
@thefog you can convert a 32 bit integer that is between -32768 and 32767 to a 32 bit integer that will be between 0 and 65535 with (int + 0x10000) & 0xFFFEFFFF. This would mean that after scaling -1 (because it's full on bits in 2's complement) becomes 255 and so on.
@thefog Well it avoids floating point operations, multiplication and a call to round. I guess the code doesn't run too close to hardware if it performs the same :P
2

You can use a mask to benefit from numpy's vectorization (implicit loops), which will be much faster:

mask = pixel_array == paddingVal
byte_array[mask] = 0
byte_array[~mask] = np.round(255.0 * (pixel_array[~mask] - minVal) / (maxVal - minVal - 1.0))

It could also be done like this, which is cleaner because you avoid having to create byte_array beforehand:

byte_array = np.round(255.0 * (pixel_array - minVal) / (maxVal - minVal - 1.0)).astype(np.uint8)
byte_array[pixel_array == paddingVal] = 0

Edit: as Joe Kington points in a comment to the question, this trades of memory for speed.

2 Comments

That fails with an error, I tried something like before and get: byte_array[~mask] = round(255.0 * (pixel_array[~mask] - minVal) / (maxVal - minVal - 1.0)) TypeError: only length-1 arrays can be converted to Python scalars
Sorry, round is a python built in that only works for scalars. I'll fix that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.