Sebdarmy
Sebdarmy

Reputation: 145

Python if-elif-else runtime optimization

I did a search on the previous asked question, but without finding what i need to optimize my code.

For info, I am running on Python 2.7 but could change to 3 if needed

I am converting every pixel of an image and because of some condition i have to do it pixel by pixel. So i have nested for loop with an if-elif-else statement inside, and it takes an awful long time to run. For an image of 1536 x 2640, the whole code takes ~20 seconds and 90% of time is inside this double for loop

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

  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 for additional detail:

@Jean-FrançoisFabre is right and I am applying three different gain/offset depending on which section I am between 0 to 255. But the section are not always evenly space and can be modified. Maybe to give some additional context, i am simply applying a custom S-curve to an image to shift the pixel value up/down. And each column in the image have their own S-curve

my gain1,2,3/offset1,2,3 values are floating point. the gain will always be positive and the offset can be negative or positive.I also have an individual value for each pixel in the width direction, but they are common in the row direction.

Example, all pixel from column 1 with can use gain/offset 1,2,3 from the 1st row in table below. All pixel from column 2 in the image will use gain/offset form the 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 will look at @Jean-FrançoisFabre solution, but in the meantime i was also looking at using some numpy approaches.

Once i get something that compute faster, i'll post my finding here

Upvotes: 3

Views: 682

Answers (3)

Sebdarmy
Sebdarmy

Reputation: 145

This is the final implementation i am using to remove the 2 for loops. Going down to around 1~2 seconds per images

I am creating 3 array where i will replace the content by 0 when it is not in the range i want. Then do the gain multiplication and offset on each before adding the whole

  height = img_original.shape[0]
  width  = img_original.shape[1]
  print 'height = ', height, 'width = ', width

  # create temp 3 2D-arrays
  img1 = np.array(img_original,dtype=np.int)
  img2 = np.array(img_original,dtype=np.int)
  img3 = np.array(img_original,dtype=np.int)

  #create the 2D array for gain/offset based on 1D array
  # csv array acquire from .csv file, INDEX_xx for column to read
  array_gain1    = np.tile(csv[1:, INDEX_G1],(height,1))
  array_offset1  = np.tile(csv[1:, INDEX_O1],(height,1))

  array_gain2   = np.tile(csv[1:, INDEX_G2],(height,1))
  array_offset2 = np.tile(csv[1:, INDEX_O2],(height,1))

  array_gain3    = np.tile(csv[1:, INDEX_G3],(height,1))
  array_offset3  = np.tile(csv[1:, INDEX_O3],(height,1))

  # replace the content by 0 when not in the desired zone
  np.place(img1,img_original >= G2_TARGET, 0) 
  np.place(img2,img_original < G2_TARGET,0)   
  np.place(img2,img_original > G1_TARGET,0)   
  np.place(img3,img_original <= G1_TARGET, 0) 

  np.place(array_offset1,img_original >= G2_TARGET, 0)
  np.place(array_offset2,(img_original < G2_TARGET), 0)
  np.place(array_offset2,(img_original > G1_TARGET), 0)
  np.place(array_offset3,img_original <= G1_TARGET, 0)

  # apply the gain/offset for each zone
  img1 = np.array(img1 * array_gain1 + array_offset1, dtype=np.uint8)
  img2 = np.array(img2 * array_gain2 + array_offset2, dtype=np.uint8)
  img3 = np.array(img3 * array_gain3 + array_offset3, dtype=np.uint8)

  # recrete the whole image
  img_conv = np.clip(img1 + img2 + img3, 0, 255)

Upvotes: 0

user1196549
user1196549

Reputation:

Have a try with a lookup-table: you precompute all transformed ADC values in the range [0,255], and the loop body will simplify

  for pixel in range(width):
    for row in range(height):
      img_conv[row, pixel]= LUT[img_original[row, pixel]]

Upvotes: 1

Jean-Fran&#231;ois Fabre
Jean-Fran&#231;ois Fabre

Reputation: 140196

Since your values are between 0 and 255 and your bounds are evenly spaced, you could use the trick below:

you seem to want to apply 3 different gains depending if you are on the first third, the second third or the third third of the 0-255 range.

Why not computing 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

in this loop there's only one division and no if. Should be much faster (beside the numpy approaches)

You could use finer level with more accurate lookup tables (also avoid the division by generating 256 tuples):

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

Upvotes: 2

Related Questions