Python if-elif-else runtime optimization

elif-else runtime optimization… here is a solution to the problem.

elif-else runtime optimization

I

searched for the previously asked issue but didn’t find what I needed to optimize the code.

For information, I’m running on Python 2.7, but can change to 3 if needed

I’m converting every pixel of an image and I have to do it pixel by pixel due to some circumstances.
So I nested an if-elif-else statement inside the for loop and it took a long time to run.
For a 1536 x 2640 image, the entire code takes about 20 seconds, and 90% of the time is in this double for loop

I believe there should be a better way to write the code below

  for pixel in range(width):
    for row in range(height):
      ADC = img_original[row, pixel]
      if ADC < 84:
        gain   = gain1
        offset = offset1
      elif ADC > 153: 
        gain   = gain3
        offset = offset3
      else:
        gain   = gain2
        offset = offset2

Conv_ADC  = int(min(max(ADC * gain + offset, 0),255))
      img_conv[row, pixel] = Conv_ADC

Thanks for the help


Edit more details:

@Jean-FrançoisFabre is right, I’m applying three different gains/offsets depending on which part I’m between 0 and 255. However, the section is not always evenly distributed and can be modified.
Maybe to provide some extra context, I just applied a custom S-curve to the image to move the pixel value up/down. And each column in the image has its own S-curve

My gain1,2,3

/offset1,2,3 value is float. The gain will always be positive and the offset can be negative or positive. I also have a separate value for each pixel in the width direction, but they are common in the row direction.

For example, all pixels in column 1 can use gain/offset 1, 2, 3 in row 1 in the table below. All pixels in column 2 in the image will use the gain/offset in row 2 in the table below

Pixel   Gain1     Offset1    Gain2     Offset2   Gain3     Offset3
1       0.417722  24.911392  0.623188  7.652176  1.175676  -76.878357
2       0.43038   25.848103  0.623188  9.652176  1.148649  -70.743225
3       0.443038  23.784809  0.637681  7.434776  1.175676  -74.878357
4       0.443038  22.784809  0.652174  5.217384  1.175676  -74.878357
5       0.455696  23.721519  0.637681  8.434776  1.202703  -78.013519
6       0.455696  21.721519  0.637681  6.434776  1.243243  -86.216217
7       0.455696  22.721519  0.623188  8.652176  1.216216  -82.081085
8       0.443038  22.784809  0.623188  7.652176  1.22973   -85.148651
... until pixel 2640 in width direction

I’ll look at @Jean-FrançoisFabre’s solution, but in the meantime I’m also thinking about using some numpy methods.

Once I get something that calculates faster, I’ll post my findings here

Solution

Since your values are between 0 and 255, and your boundaries are evenly spaced, you can use the following trick:

It seems that you want to apply 3 different gains, depending on whether you are in the first third of the 0-255 range, the second third, or the third third.

Why not calculate the index by dividing by 85 (255/3)?

Simple proof of concept:

gainsoffsets = [(10,1),(20,2),(30,3),(30,3)] # [(gain1,offset1),(gain2,offset2),(gain3,offset3)] + extra corner case for value 255

for value in 84,140,250:
    index = value // 85
    gain,offset = gainsoffsets[index]
    print(gain,offset)

Result:

10 1
20 2
30 3

There is only one division in this loop, no if. Should be much faster (except for the numpy method).

You can use a finer level and a more accurate lookup table (or you can avoid division by generating 256 tuples):

gainsoffsets = [(10,1)]*85+[(20,2)]*85+[(30,3)*86]  # add more intervals for more thresholds

Related Problems and Solutions