Skip to main content
added 276 characters in body
Source Link
Graipher
  • 41.7k
  • 7
  • 70
  • 134

While the answer by @AJNeufeld correctly explains the shortcomings regarding normal Python in your code, there is indeed a faster way using numpy.

First, you can build a mask of all pixels that have the value 1:

mask = (img == [1, 1, 1]).all(axis=-1)

This mask is already the same as the output of your generateArray.

Executing your function takes about 720 ms ± 17.9 ms on my machine with some image file I had lying around (PNG, 471 x 698 pixels), whereas the generation of this mask is a hundred times faster at 7.37 ms ± 96.6 µs.

Now we construct two images, one completely black and one completely white:

black = np.zeros_like(img)
white = np.ones_like(img)

And then you can use numpy.where to conditionally on the mask use those two images:

img_bw = np.where(mask[:,:,None], white, black)

The weird looking slicing with None just makes sure that the mask has the right dimensionality (i.e. three values per pixel, instead of just one).

Putting it all together:

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np

def to_bw(img):
    """Return a black and white copy of `img`.

    Pixels which are white in the original image stay white,
    all other pixels are turned black.
    """
    mask = (img == [1, 1, 1]).all(axis=-1)
    black = np.zeros_like(img)
    white = np.ones_like(img)
    return np.where(mask[:,:,None], white, black)

if __name__ == "__main__":
    img = mpimg.imread('./Training/Image1.png')
    img_bw = to_bw(img)
    plt.imshow(img_bw)
    plt.show()

The whole to_bw function only takes 14 ms ± 324 µs.

While the answer by @AJNeufeld correctly explains the shortcomings regarding normal Python in your code, there is indeed a faster way using numpy.

First, you can build a mask of all pixels that have the value 1:

mask = (img == [1, 1, 1]).all(axis=-1)

This mask is already the same as the output of your generateArray.

Now we construct two images, one completely black and one completely white:

black = np.zeros_like(img)
white = np.ones_like(img)

And then you can use numpy.where to conditionally on the mask use those two images:

img_bw = np.where(mask[:,:,None], white, black)

The weird looking slicing with None just makes sure that the mask has the right dimensionality (i.e. three values per pixel, instead of just one).

Putting it all together:

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np

def to_bw(img):
    mask = (img == [1, 1, 1]).all(axis=-1)
    black = np.zeros_like(img)
    white = np.ones_like(img)
    return np.where(mask[:,:,None], white, black)

if __name__ == "__main__":
    img = mpimg.imread('./Training/Image1.png')
    img_bw = to_bw(img)
    plt.imshow(img_bw)
    plt.show()

While the answer by @AJNeufeld correctly explains the shortcomings regarding normal Python in your code, there is indeed a faster way using numpy.

First, you can build a mask of all pixels that have the value 1:

mask = (img == [1, 1, 1]).all(axis=-1)

This mask is already the same as the output of your generateArray.

Executing your function takes about 720 ms ± 17.9 ms on my machine with some image file I had lying around (PNG, 471 x 698 pixels), whereas the generation of this mask is a hundred times faster at 7.37 ms ± 96.6 µs.

Now we construct two images, one completely black and one completely white:

black = np.zeros_like(img)
white = np.ones_like(img)

And then you can use numpy.where to conditionally on the mask use those two images:

img_bw = np.where(mask[:,:,None], white, black)

The weird looking slicing with None just makes sure that the mask has the right dimensionality (i.e. three values per pixel, instead of just one).

Putting it all together:

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np

def to_bw(img):
    """Return a black and white copy of `img`.

    Pixels which are white in the original image stay white,
    all other pixels are turned black.
    """
    mask = (img == [1, 1, 1]).all(axis=-1)
    black = np.zeros_like(img)
    white = np.ones_like(img)
    return np.where(mask[:,:,None], white, black)

if __name__ == "__main__":
    img = mpimg.imread('./Training/Image1.png')
    img_bw = to_bw(img)
    plt.imshow(img_bw)
    plt.show()

The whole to_bw function only takes 14 ms ± 324 µs.

edited body
Source Link
Graipher
  • 41.7k
  • 7
  • 70
  • 134

While the answer by @AJNeufeld correctly explains the shortcomings regarding normal Python in your code, there is indeed a faster way using numpy.

First, you can build a mask of all pixels that have the value 1:

mask = (img == [1, 1, 1]).all(axis=-1)

This mask is already the same as the output of your generateArray.

Now we construct two images, one completely 1black and one completely 0white:

black = np.zeros_like(img)
white = np.ones_like(img)

And then you can use numpy.where to conditionally on the mask use those two images:

img_bw = np.where(mask[:,:,None], blackwhite, whiteblack)

The weird looking slicing with None just makes sure that the mask has the right dimensionality (i.e. three values per pixel, instead of just one).

Putting it all together:

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np

def to_bw(img):
    mask = (img == [1, 1, 1]).all(axis=-1)
    black = np.zeros_like(img)
    white = np.ones_like(img)
    return np.where(mask[:,:,None], blackwhite, whiteblack)

if __name__ == "__main__":
    img = mpimg.imread('./Training/Image1.png')
    img_bw = to_bw(img)
    plt.imshow(img_bw)
    plt.show()

While the answer by @AJNeufeld correctly explains the shortcomings regarding normal Python in your code, there is indeed a faster way using numpy.

First, you can build a mask of all pixels that have the value 1:

mask = (img == [1, 1, 1]).all(axis=-1)

Now we construct two images, one completely 1 and one completely 0:

black = np.zeros_like(img)
white = np.ones_like(img)

And then you can use numpy.where to conditionally on the mask use those two images:

img_bw = np.where(mask[:,:,None], black, white)

The weird looking slicing with None just makes sure that the mask has the right dimensionality (i.e. three values per pixel, instead of just one).

Putting it all together:

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np

def to_bw(img):
    mask = (img == [1, 1, 1]).all(axis=-1)
    black = np.zeros_like(img)
    white = np.ones_like(img)
    return np.where(mask[:,:,None], black, white)

if __name__ == "__main__":
    img = mpimg.imread('./Training/Image1.png')
    img_bw = to_bw(img)
    plt.imshow(img_bw)
    plt.show()

While the answer by @AJNeufeld correctly explains the shortcomings regarding normal Python in your code, there is indeed a faster way using numpy.

First, you can build a mask of all pixels that have the value 1:

mask = (img == [1, 1, 1]).all(axis=-1)

This mask is already the same as the output of your generateArray.

Now we construct two images, one completely black and one completely white:

black = np.zeros_like(img)
white = np.ones_like(img)

And then you can use numpy.where to conditionally on the mask use those two images:

img_bw = np.where(mask[:,:,None], white, black)

The weird looking slicing with None just makes sure that the mask has the right dimensionality (i.e. three values per pixel, instead of just one).

Putting it all together:

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np

def to_bw(img):
    mask = (img == [1, 1, 1]).all(axis=-1)
    black = np.zeros_like(img)
    white = np.ones_like(img)
    return np.where(mask[:,:,None], white, black)

if __name__ == "__main__":
    img = mpimg.imread('./Training/Image1.png')
    img_bw = to_bw(img)
    plt.imshow(img_bw)
    plt.show()
Source Link
Graipher
  • 41.7k
  • 7
  • 70
  • 134

While the answer by @AJNeufeld correctly explains the shortcomings regarding normal Python in your code, there is indeed a faster way using numpy.

First, you can build a mask of all pixels that have the value 1:

mask = (img == [1, 1, 1]).all(axis=-1)

Now we construct two images, one completely 1 and one completely 0:

black = np.zeros_like(img)
white = np.ones_like(img)

And then you can use numpy.where to conditionally on the mask use those two images:

img_bw = np.where(mask[:,:,None], black, white)

The weird looking slicing with None just makes sure that the mask has the right dimensionality (i.e. three values per pixel, instead of just one).

Putting it all together:

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np

def to_bw(img):
    mask = (img == [1, 1, 1]).all(axis=-1)
    black = np.zeros_like(img)
    white = np.ones_like(img)
    return np.where(mask[:,:,None], black, white)

if __name__ == "__main__":
    img = mpimg.imread('./Training/Image1.png')
    img_bw = to_bw(img)
    plt.imshow(img_bw)
    plt.show()