kwimage.im_io

This module provides functions imread and imwrite which are wrappers around concrete readers/writers provided by other libraries. This allows us to support a wider array of formats than any of individual backends.

Module Contents

Functions

imread(fpath, space='auto', backend='auto')

Reads image data in a specified format using some backend implementation.

imwrite(fpath, image, space='auto', backend='auto', **kwargs)

Writes image data to disk.

load_image_shape(fpath)

Determine the height/width/channels of an image without reading the entire

kwimage.im_io.imread(fpath, space='auto', backend='auto')[source]

Reads image data in a specified format using some backend implementation.

Parameters
  • fpath (str) – path to the file to be read

  • space (str, default=’auto’) – The desired colorspace of the image. Can by any colorspace accepted by convert_colorspace, or it can be ‘auto’, in which case the colorspace of the image is unmodified (except in the case where a color image is read by opencv, in which case we convert BGR to RGB by default). If None, then no modification is made to whatever backend is used to read the image.

    New in version 0.7.10: when the backend does not resolve to “cv2” the “auto” space resolves to None, thus the image is read as-is.

  • backend (str, default=’auto’) – which backend reader to use. By default the file extension is used to determine this, but it can be manually overridden. Valid backends are ‘gdal’, ‘skimage’, ‘itk’, and ‘cv2’.

Returns

the image data in the specified color space.

Return type

ndarray

Note

if space is something non-standard like HSV or LAB, then the file must be a normal 8-bit color image, otherwise an error will occur.

Raises
  • IOError - If the image cannot be read

  • ImportError - If trying to read a nitf without gdal

  • NotImplementedError - if trying to read a corner-case image

Example

>>> # xdoctest: +REQUIRES(--network)
>>> from kwimage.im_io import *  # NOQA
>>> import tempfile
>>> from os.path import splitext  # NOQA
>>> # Test a non-standard image, which encodes a depth map
>>> fpath = ub.grabdata(
>>>     'http://www.topcoder.com/contest/problem/UrbanMapper3D/JAX_Tile_043_DTM.tif',
>>>     hasher='sha256', hash_prefix='64522acba6f0fb7060cd4c202ed32c5163c34e63d386afdada4190cce51ff4d4')
>>> img1 = imread(fpath)
>>> # Check that write + read preserves data
>>> tmp = tempfile.NamedTemporaryFile(suffix=splitext(fpath)[1])
>>> imwrite(tmp.name, img1)
>>> img2 = imread(tmp.name)
>>> assert np.all(img2 == img1)
>>> # xdoctest: +REQUIRES(--show)
>>> import kwplot
>>> kwplot.autompl()
>>> kwplot.imshow(img1, pnum=(1, 2, 1), fnum=1, norm=True)
>>> kwplot.imshow(img2, pnum=(1, 2, 2), fnum=1, norm=True)

Example

>>> # xdoctest: +REQUIRES(--network)
>>> import tempfile
>>> img1 = imread(ub.grabdata(
>>>     'http://i.imgur.com/iXNf4Me.png', fname='ada.png', hasher='sha256',
>>>     hash_prefix='898cf2588c40baf64d6e09b6a93b4c8dcc0db26140639a365b57619e17dd1c77'))
>>> tmp_tif = tempfile.NamedTemporaryFile(suffix='.tif')
>>> tmp_png = tempfile.NamedTemporaryFile(suffix='.png')
>>> imwrite(tmp_tif.name, img1)
>>> imwrite(tmp_png.name, img1)
>>> tif_im = imread(tmp_tif.name)
>>> png_im = imread(tmp_png.name)
>>> assert np.all(tif_im == png_im)
>>> # xdoctest: +REQUIRES(--show)
>>> import kwplot
>>> kwplot.autompl()
>>> kwplot.imshow(png_im, pnum=(1, 2, 1), fnum=1)
>>> kwplot.imshow(tif_im, pnum=(1, 2, 2), fnum=1)

Example

>>> # xdoctest: +REQUIRES(--network)
>>> import tempfile
>>> tif_fpath = ub.grabdata(
>>>     'https://ghostscript.com/doc/tiff/test/images/rgb-3c-16b.tiff',
>>>     fname='pepper.tif', hasher='sha256',
>>>     hash_prefix='31ff3a1f416cb7281acfbcbb4b56ee8bb94e9f91489602ff2806e5a49abc03c0')
>>> img1 = imread(tif_fpath)
>>> tmp_tif = tempfile.NamedTemporaryFile(suffix='.tif')
>>> tmp_png = tempfile.NamedTemporaryFile(suffix='.png')
>>> imwrite(tmp_tif.name, img1)
>>> imwrite(tmp_png.name, img1)
>>> tif_im = imread(tmp_tif.name)
>>> png_im = imread(tmp_png.name)
>>> assert np.all(tif_im == png_im)
>>> # xdoctest: +REQUIRES(--show)
>>> import kwplot
>>> kwplot.autompl()
>>> kwplot.imshow(png_im / 2 ** 16, pnum=(1, 2, 1), fnum=1)
>>> kwplot.imshow(tif_im / 2 ** 16, pnum=(1, 2, 2), fnum=1)

Example

>>> # xdoctest: +REQUIRES(module:itk, --network)
>>> import kwimage
>>> import ubelt as ub
>>> # Grab an image that ITK can read
>>> fpath = ub.grabdata(
>>>     url='https://data.kitware.com/api/v1/file/606754e32fa25629b9476f9e/download',
>>>     fname='brainweb1e5a10f17Rot20Tx20.mha',
>>>     hash_prefix='08f0812591691ae24a29788ba8cd1942e91', hasher='sha512')
>>> # Read the image (this is actually a DxHxW stack of images)
>>> img1_stack = kwimage.imread(fpath)
>>> # Check that write + read preserves data
>>> import tempfile
>>> tmp_file = tempfile.NamedTemporaryFile(suffix='.mha')
>>> kwimage.imwrite(tmp_file.name, img1_stack)
>>> recon = kwimage.imread(tmp_file.name)
>>> assert not np.may_share_memory(recon, img1_stack)
>>> assert np.all(recon == img1_stack)
>>> # xdoctest: +REQUIRES(--show)
>>> import kwplot
>>> kwplot.autompl()
>>> kwplot.imshow(kwimage.stack_images_grid(recon[0::20]))
>>> kwplot.show_if_requested()
Benchmark:
>>> from kwimage.im_io import *  # NOQA
>>> import timerit
>>> import kwimage
>>> import tempfile
>>> #
>>> dsize = (1920, 1080)
>>> img1 = kwimage.grab_test_image('amazon', dsize=dsize)
>>> ti = timerit.Timerit(10, bestof=3, verbose=1, unit='us')
>>> formats = {}
>>> dpath = ub.ensure_app_cache_dir('cache')
>>> space = 'auto'
>>> formats['png'] = kwimage.imwrite(join(dpath, '.png'), img1, space=space, backend='cv2')
>>> formats['jpg'] = kwimage.imwrite(join(dpath, '.jpg'), img1, space=space, backend='cv2')
>>> formats['tif_raw'] = kwimage.imwrite(join(dpath, '.raw.tif'), img1, space=space, backend='gdal', compress='RAW')
>>> formats['tif_deflate'] = kwimage.imwrite(join(dpath, '.deflate.tif'), img1, space=space, backend='gdal', compress='DEFLATE')
>>> formats['tif_lzw'] = kwimage.imwrite(join(dpath, '.lzw.tif'), img1, space=space, backend='gdal', compress='LZW')
>>> grid = [
>>>     ('cv2', 'png'),
>>>     ('cv2', 'jpg'),
>>>     ('gdal', 'jpg'),
>>>     ('turbojpeg', 'jpg'),
>>>     ('gdal', 'tif_raw'),
>>>     ('gdal', 'tif_lzw'),
>>>     ('gdal', 'tif_deflate'),
>>>     ('skimage', 'tif_raw'),
>>> ]
>>> backend, filefmt = 'cv2', 'png'
>>> for backend, filefmt in grid:
>>>     for timer in ti.reset(f'imread-{filefmt}-{backend}'):
>>>         with timer:
>>>             kwimage.imread(formats[filefmt], space=space, backend=backend)
>>> # Test all formats in auto mode
>>> for filefmt in formats.keys():
>>>     for timer in ti.reset(f'kwimage.imread-{filefmt}-auto'):
>>>         with timer:
>>>             kwimage.imread(formats[filefmt], space=space, backend='auto')
>>> ti.measures = ub.map_vals(ub.sorted_vals, ti.measures)
>>> import netharn as nh
>>> print('ti.measures = {}'.format(nh.util.align(ub.repr2(ti.measures['min'], nl=2), ':')))
Timed best=42891.504 µs, mean=44008.439 ± 1409.2 µs for imread-png-cv2
Timed best=33146.808 µs, mean=34185.172 ± 656.3 µs for imread-jpg-cv2
Timed best=40120.306 µs, mean=41220.927 ± 1010.9 µs for imread-jpg-gdal
Timed best=30798.162 µs, mean=31573.070 ± 737.0 µs for imread-jpg-turbojpeg
Timed best=6223.170 µs, mean=6370.462 ± 150.7 µs for imread-tif_raw-gdal
Timed best=42459.404 µs, mean=46519.940 ± 5664.9 µs for imread-tif_lzw-gdal
Timed best=36271.175 µs, mean=37301.108 ± 861.1 µs for imread-tif_deflate-gdal
Timed best=5239.503 µs, mean=6566.574 ± 1086.2 µs for imread-tif_raw-skimage
ti.measures = {
    'imread-tif_raw-skimage' : 0.0052395030070329085,
    'imread-tif_raw-gdal'    : 0.006223169999429956,
    'imread-jpg-turbojpeg'   : 0.030798161998973228,
    'imread-jpg-cv2'         : 0.03314680799667258,
    'imread-tif_deflate-gdal': 0.03627117499127053,
    'imread-jpg-gdal'        : 0.040120305988239124,
    'imread-tif_lzw-gdal'    : 0.042459404008695856,
    'imread-png-cv2'         : 0.042891503995633684,
}
>>> print('ti.measures = {}'.format(nh.util.align(ub.repr2(ti.measures['mean'], nl=2), ':')))
kwimage.im_io.imwrite(fpath, image, space='auto', backend='auto', **kwargs)[source]

Writes image data to disk.

Parameters
  • fpath (PathLike) – location to save the image

  • image (ndarray) – image data

  • space (str | None, default=’auto’) – the colorspace of the image to save. Can by any colorspace accepted by convert_colorspace, or it can be ‘auto’, in which case we assume the input image is either RGB, RGBA or grayscale. If None, then absolutely no color modification is made and whatever backend is used writes the image as-is.

    New in version 0.7.10: when the backend does not resolve to “cv2”, the “auto” space resolves to None, thus the image is saved as-is.

  • backend (str, default=’auto’) – which backend writer to use. By default the file extension is used to determine this. Valid backends are ‘gdal’, ‘skimage’, ‘itk’, and ‘cv2’.

  • **kwargs – args passed to the backend writer

Returns

path to the written file

Return type

str

Notes

The image may be modified to preserve its colorspace depending on which backend is used to write the image.

When saving as a jpeg or png, the image must be encoded with the uint8 data type. When saving as a tiff, any data type is allowed.

Raises

Exception – if the image cannot be written

Doctest:
>>> # xdoctest: +REQUIRES(--network)
>>> # This should be moved to a unit test
>>> import tempfile
>>> test_image_paths = [
>>>    ub.grabdata('https://ghostscript.com/doc/tiff/test/images/rgb-3c-16b.tiff', fname='pepper.tif'),
>>>    ub.grabdata('http://i.imgur.com/iXNf4Me.png', fname='ada.png'),
>>>    #ub.grabdata('http://www.topcoder.com/contest/problem/UrbanMapper3D/JAX_Tile_043_DTM.tif'),
>>>    ub.grabdata('https://upload.wikimedia.org/wikipedia/commons/f/fa/Grayscale_8bits_palette_sample_image.png', fname='parrot.png')
>>> ]
>>> for fpath in test_image_paths:
>>>     for space in ['auto', 'rgb', 'bgr', 'gray', 'rgba']:
>>>         img1 = imread(fpath, space=space)
>>>         print('Test im-io consistency of fpath = {!r} in {} space, shape={}'.format(fpath, space, img1.shape))
>>>         # Write the image in TIF and PNG format
>>>         tmp_tif = tempfile.NamedTemporaryFile(suffix='.tif')
>>>         tmp_png = tempfile.NamedTemporaryFile(suffix='.png')
>>>         imwrite(tmp_tif.name, img1, space=space, backend='skimage')
>>>         imwrite(tmp_png.name, img1, space=space)
>>>         tif_im = imread(tmp_tif.name, space=space)
>>>         png_im = imread(tmp_png.name, space=space)
>>>         assert np.all(tif_im == png_im), 'im-read/write inconsistency'
>>>         if _have_gdal:
>>>             tmp_tif2 = tempfile.NamedTemporaryFile(suffix='.tif')
>>>             imwrite(tmp_tif2.name, img1, space=space, backend='gdal')
>>>             tif_im2 = imread(tmp_tif2.name, space=space)
>>>             assert np.all(tif_im == tif_im2), 'im-read/write inconsistency'
>>>         if space == 'gray':
>>>             assert tif_im.ndim == 2
>>>             assert png_im.ndim == 2
>>>         elif space in ['rgb', 'bgr']:
>>>             assert tif_im.shape[2] == 3
>>>             assert png_im.shape[2] == 3
>>>         elif space in ['rgba', 'bgra']:
>>>             assert tif_im.shape[2] == 4
>>>             assert png_im.shape[2] == 4
Benchmark:
>>> import timerit
>>> import os
>>> import kwimage
>>> import tempfile
>>> #
>>> img1 = kwimage.grab_test_image('astro', dsize=(1920, 1080))
>>> space = 'auto'
>>> #
>>> file_sizes = {}
>>> #
>>> ti = timerit.Timerit(10, bestof=3, verbose=2)
>>> #
>>> for timer in ti.reset('imwrite-skimage-tif'):
>>>     with timer:
>>>         tmp = tempfile.NamedTemporaryFile(suffix='.tif')
>>>         kwimage.imwrite(tmp.name, img1, space=space, backend='skimage')
>>>     file_sizes[ti.label] = os.stat(tmp.name).st_size
>>> #
>>> for timer in ti.reset('imwrite-cv2-png'):
>>>     with timer:
>>>         tmp = tempfile.NamedTemporaryFile(suffix='.png')
>>>         kwimage.imwrite(tmp.name, img1, space=space, backend='cv2')
>>>     file_sizes[ti.label] = os.stat(tmp.name).st_size
>>> #
>>> for timer in ti.reset('imwrite-cv2-jpg'):
>>>     with timer:
>>>         tmp = tempfile.NamedTemporaryFile(suffix='.jpg')
>>>         kwimage.imwrite(tmp.name, img1, space=space, backend='cv2')
>>>     file_sizes[ti.label] = os.stat(tmp.name).st_size
>>> #
>>> for timer in ti.reset('imwrite-gdal-raw'):
>>>     with timer:
>>>         tmp = tempfile.NamedTemporaryFile(suffix='.tif')
>>>         kwimage.imwrite(tmp.name, img1, space=space, backend='gdal', compress='RAW')
>>>     file_sizes[ti.label] = os.stat(tmp.name).st_size
>>> #
>>> for timer in ti.reset('imwrite-gdal-lzw'):
>>>     with timer:
>>>         tmp = tempfile.NamedTemporaryFile(suffix='.tif')
>>>         kwimage.imwrite(tmp.name, img1, space=space, backend='gdal', compress='LZW')
>>>     file_sizes[ti.label] = os.stat(tmp.name).st_size
>>> #
>>> for timer in ti.reset('imwrite-gdal-zstd'):
>>>     with timer:
>>>         tmp = tempfile.NamedTemporaryFile(suffix='.tif')
>>>         kwimage.imwrite(tmp.name, img1, space=space, backend='gdal', compress='ZSTD')
>>>     file_sizes[ti.label] = os.stat(tmp.name).st_size
>>> #
>>> for timer in ti.reset('imwrite-gdal-deflate'):
>>>     with timer:
>>>         tmp = tempfile.NamedTemporaryFile(suffix='.tif')
>>>         kwimage.imwrite(tmp.name, img1, space=space, backend='gdal', compress='DEFLATE')
>>>     file_sizes[ti.label] = os.stat(tmp.name).st_size
>>> #
>>> for timer in ti.reset('imwrite-gdal-jpeg'):
>>>     with timer:
>>>         tmp = tempfile.NamedTemporaryFile(suffix='.tif')
>>>         kwimage.imwrite(tmp.name, img1, space=space, backend='gdal', compress='JPEG')
>>>     file_sizes[ti.label] = os.stat(tmp.name).st_size
>>> #
>>> file_sizes = ub.sorted_vals(file_sizes)
>>> import xdev
>>> file_sizes_human = ub.map_vals(lambda x: xdev.byte_str(x, 'MB'), file_sizes)
>>> print('ti.rankings = {}'.format(ub.repr2(ti.rankings, nl=2)))
>>> print('file_sizes = {}'.format(ub.repr2(file_sizes_human, nl=1)))

Example

>>> # Test saving a multi-band file
>>> import kwimage
>>> import tempfile
>>> # In this case the backend will not resolve to cv2, so
>>> # we should not need to specify space.
>>> data = np.random.rand(32, 32, 13).astype(np.float32)
>>> temp = tempfile.NamedTemporaryFile(suffix='.tif')
>>> fpath = temp.name
>>> kwimage.imwrite(fpath, data)
>>> recon = kwimage.imread(fpath)
>>> assert np.all(recon == data)
>>> kwimage.imwrite(fpath, data, backend='skimage')
>>> recon = kwimage.imread(fpath)
>>> assert np.all(recon == data)
>>> import pytest
>>> # In this case the backend will resolve to cv2, and thus we expect
>>> # a failure
>>> temp = tempfile.NamedTemporaryFile(suffix='.png')
>>> fpath = temp.name
>>> with pytest.raises(NotImplementedError):
>>>     kwimage.imwrite(fpath, data)
kwimage.im_io.load_image_shape(fpath)[source]

Determine the height/width/channels of an image without reading the entire file.

Parameters

fpath (str) – path to an image

Returns

Tuple - shape of the dataset.

Recall this library uses the convention that “shape” is refers to height,width,channels and “size” is width,height ordering.

Benchmark:
>>> # For large files, PIL is much faster
>>> import gdal
>>> from PIL import Image
>>> #
>>> import kwimage
>>> fpath = kwimage.grab_test_image_fpath()
>>> #
>>> ti = ub.Timerit(100, bestof=10, verbose=2)
>>> for timer in ti.reset('gdal'):
>>>     with timer:
>>>         gdal_dset = gdal.Open(fpath, gdal.GA_ReadOnly)
>>>         width = gdal_dset.RasterXSize
>>>         height = gdal_dset.RasterYSize
>>>         gdal_dset = None
>>> #
>>> for timer in ti.reset('PIL'):
>>>     with timer:
>>>         pil_img = Image.open(fpath)
>>>         width, height = pil_img.size
>>>         pil_img.close()
Timed gdal for: 100 loops, best of 10
    time per loop: best=62.967 µs, mean=63.991 ± 0.8 µs
Timed PIL for: 100 loops, best of 10
    time per loop: best=46.640 µs, mean=47.314 ± 0.4 µs