#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
A short tutorial on plotting images with Matplotlib.
This tutorial will use Matplotlib's imperative-style plotting
interface, pyplot. This interface maintains global state, and is very
useful for quickly and easily experimenting with various plot
settings. The alternative is the object-oriented interface, which is also
very powerful, and generally more suitable for large application
development. If you'd like to learn about the object-oriented
interface, a great place to start is our :doc:`Usage guide
`. For now, let's get on
with the imperative-style approach:
"""
# TODO fix the bad examples below...
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
###############################################################################
# .. _importing_data:
#
# Importing image data into Numpy arrays
# ======================================
#
# Matplotlib relies on the Pillow_ library to load image data.
#
# .. _Pillow: https://pillow.readthedocs.io/en/latest/
#
# Here's the image we're going to play with.
# It's a 24-bit RGB PNG image (8 bits for each of R, G, B). Depending
# on where you get your data, the other kinds of image that you'll most
# likely encounter are RGBA images, which allow for transparency, or
# single-channel grayscale (luminosity) images. You can right click on
# it and choose "Save image as" to download it to your computer for the
# rest of this tutorial.
#
# And here we go...
img = mpimg.imread("realpython.jpg")
print(img)
print(img.shape)
###############################################################################
# Note the dtype there - float32. Matplotlib has rescaled the 8 bit
# data from each channel to floating point data between 0.0 and 1.0. As
# a side note, the only datatype that Pillow can work with is uint8.
# Matplotlib plotting can handle float32 and uint8, but image
# reading/writing for any format other than PNG is limited to uint8
# data. Why 8 bits? Most displays can only render 8 bits per channel
# worth of color gradation. Why can they only render 8 bits/channel?
# Because that's about all the human eye can see. More here (from a
# photography standpoint): `Luminous Landscape bit depth tutorial
# `_.
#
# Each inner list represents a pixel. Here, with an RGB image, there
# are 3 values. Since it's a black and white image, R, G, and B are all
# similar. An RGBA (where A is alpha, or transparency), has 4 values
# per inner list, and a simple luminance image just has one value (and
# is thus only a 2-D array, not a 3-D array). For RGB and RGBA images,
# Matplotlib supports float32 and uint8 data types. For grayscale,
# Matplotlib supports only float32. If your array data does not meet
# one of these descriptions, you need to rescale it.
#
# Plotting numpy arrays as images
# ===================================
#
# So, you have your data in a numpy array (either by importing it, or by
# generating it). Let's render it. In Matplotlib, this is performed
# using the :func:`~matplotlib.pyplot.imshow` function. Here we'll grab
# the plot object. This object gives you an easy way to manipulate the
# plot from the prompt.
imgplot = plt.imshow(img)
plt.show()
###############################################################################
# You can also plot any numpy array.
#
# Applying pseudocolor schemes to image plots
# -------------------------------------------------
#
# Pseudocolor can be a useful tool for enhancing contrast and
# visualizing your data more easily. This is especially useful when
# making presentations of your data using projectors - their contrast is
# typically quite poor.
#
# Pseudocolor is only relevant to single-channel, grayscale, luminosity
# images. We currently have an RGB image. Since R, G, and B are all
# similar (see for yourself above or in your data), we can just pick one
# channel of our data:
lum_img = img[:, :, 0]
# This is array slicing. You can read more in the `Numpy tutorial
# `_.
plt.imshow(lum_img)
plt.show()
###############################################################################
# Now, with a luminosity (2D, no color) image, the default colormap (aka lookup table,
# LUT), is applied. The default is called viridis. There are plenty of
# others to choose from.
plt.imshow(lum_img, cmap="hot")
plt.show()
###############################################################################
# Note that you can also change colormaps on existing plot objects using the
# :meth:`~matplotlib.cm.ScalarMappable.set_cmap` method:
imgplot = plt.imshow(lum_img)
imgplot.set_cmap("nipy_spectral")
plt.show()
###############################################################################
#
# However, remember that in the Jupyter Notebook with the inline backend,
# you can't make changes to plots that have already been rendered. If you
# create imgplot here in one cell, you cannot call set_cmap() on it in a later
# cell and expect the earlier plot to change. Make sure that you enter these
# commands together in one cell. plt commands will not change plots from earlier
# cells.
#
# There are many other colormap schemes available. See the `list and
# images of the colormaps
# <../colors/colormaps.html>`_.
#
# Color scale reference
# ------------------------
#
# It's helpful to have an idea of what value a color represents. We can
# do that by adding a color bar to your figure:
imgplot = plt.imshow(lum_img)
plt.colorbar()
plt.show()
###############################################################################
# Examining a specific data range
# ---------------------------------
#
# Sometimes you want to enhance the contrast in your image, or expand
# the contrast in a particular region while sacrificing the detail in
# colors that don't vary much, or don't matter. A good tool to find
# interesting regions is the histogram. To create a histogram of our
# image data, we use the :func:`~matplotlib.pyplot.hist` function.
plt.hist(lum_img.ravel(), bins=256, range=(0.0, 1.0), fc="k", ec="k")
plt.show()
###############################################################################
# Most often, the "interesting" part of the image is around the peak,
# and you can get extra contrast by clipping the regions above and/or
# below the peak. In our histogram, it looks like there's not much
# useful information in the high end (not many white things in the
# image). Let's adjust the upper limit, so that we effectively "zoom in
# on" part of the histogram. We do this by passing the clim argument to
# imshow. You could also do this by calling the
# :meth:`~matplotlib.cm.ScalarMappable.set_clim` method of the image plot
# object, but make sure that you do so in the same cell as your plot
# command when working with the Jupyter Notebook - it will not change
# plots from earlier cells.
#
# You can specify the clim in the call to ``plot``.
imgplot = plt.imshow(lum_img, clim=(0.0, 0.7))
plt.show()
###############################################################################
# You can also specify the clim using the returned object
fig = plt.figure()
ax = fig.add_subplot(1, 2, 1)
imgplot = plt.imshow(lum_img)
ax.set_title("Before")
plt.colorbar(ticks=[0.1, 0.3, 0.5, 0.7], orientation="horizontal")
ax = fig.add_subplot(1, 2, 2)
imgplot = plt.imshow(lum_img)
imgplot.set_clim(0.0, 0.7)
ax.set_title("After")
plt.colorbar(ticks=[0.1, 0.3, 0.5, 0.7], orientation="horizontal")
plt.show()