Save 1 bit deep binary image in Python

I found myself in a situation where I needed to create a lot of binary images, and was frustrated with the available info online. Thanks to the answers and comments here and elsewhere on SO, I was able to find an acceptable solution. The comment from @Jimbo was the best so far. Here is some code to reproduce my exploration of some ways to save binary images in python:

Load libraries and data:

from skimage import data, io, util #'0.16.2'
import matplotlib.pyplot as plt #'3.0.3'
import PIL #'6.2.1'
import cv2 #'4.1.1'
check = util.img_as_bool(data.checkerboard())

The checkerboard image from skimage has dimensions of 200x200. Without compression, as a 1-bit image it should be represented by (200*200/8) 5000 bytes

To save with skimage, note that the package will complain if the data is not uint, hence the conversion. Saving the image takes an average of 2.8ms and has a 408 byte file size

io.imsave('bw_skimage.png',util.img_as_uint(check),plugin='pil',optimize=True,bits=1)

Using matplotlib, 4.2ms and 693 byte file size

plt.imsave('bw_mpl.png',check,cmap='gray')

Using PIL, 0.5ms and 164 byte file size

img = PIL.Image.fromarray(check)
img.save('bw_pil.png',bits=1,optimize=True)

Using cv2, also complains about a bool input. The following command takes 0.4ms and results in a 2566 byte file size, despite the png compression...

_ = cv2.imwrite('bw_cv2.png', check.astype(int), [cv2.IMWRITE_PNG_BILEVEL, 1])

PIL was clearly the best for speed and file size.

I certainly missed some optimizations, comments welcome!


Use:

cv2.imwrite(<image_name>, img, [cv2.IMWRITE_PNG_BILEVEL, 1])

(this will still use compression, so in practice it will most likely have less than 1 bit per pixel)