Quick note before we begin:
In order to run this notebook, several additional python libraries are needed. Some of these libraries create dependency issues with other libraries you might have installed. It is strongly recomended that you use Anaconda and the conda-forge installation channel to install the libriaries within a NEW conda environment. Included in the GitHub is an environment.yml file, which can automatically create this environment either online in your browser via a one-time session on MyBinder, or within your Anaconda environment. To create the environment on your machine, use the terminal command:
conda env create -f environment.yml
If you need help setting up the environment, contact info@donike.net.
In this notebook, we will have a deeper look at Moran's I spatial autocorrelation. This measure looks at correlations of neighbouring pixels (or shapes) and returns a value between -1 and 1. A value of -1 indicates perfect separation of the pixels within the dataset (concentration), a vaule of 0 indicates a perfectly random distribution while a value of 1 indicates a perfectly dispersed/segregated dataset. Firstly, we will import and visualize the images of the 3 extremes the values can have. Then, we calculate Moran's I for each one of these images. Finally, we a Moran's I is calculated for an actual satellite image excerpt.
"""All libraries used in the notebook are imported in the beginning
If you get an error message here, be sure to closely follow the instructions at the beginning"""
import pysal
from skimage.io import imread
from libpysal.weights import lat2W
import numpy as np
import pandas as pd
from esda.moran import Moran
from skimage.color import rgb2gray
from splot.esda import moran_scatterplot
import matplotlib.pyplot as plt
"""Loading the example images as numpy arays"""
raster_image_random = imread(r'data/images/random.tif')
raster_image_chess = imread(r'data/images/chess_100_imperfect.tif')
raster_image_half = imread(r'data/images/half+half_100.tif')
"""Plotting images"""
fig1 = plt.imshow(raster_image_random)
plt.title("Random Distribution\nexpected Moran's I: 0")
plt.figure()
fig2 = plt.imshow(raster_image_chess)
plt.title("Chessboard Pattern\nexpected Moran's I: -1")
plt.figure()
fig3 = plt.imshow(raster_image_half)
plt.title("Half&Half\nexpected Moran's I: 1")
plt.figure()
/Users/simondonike/opt/anaconda3/envs/geo_env/lib/python3.7/site-packages/pysal/explore/segregation/network/network.py:16: UserWarning: You need pandana and urbanaccess to work with segregation's network module You can install them with `pip install urbanaccess pandana` or `conda install -c udst pandana urbanaccess` "You need pandana and urbanaccess to work with segregation's network module\n" /Users/simondonike/opt/anaconda3/envs/geo_env/lib/python3.7/site-packages/pysal/model/spvcm/abstracts.py:10: UserWarning: The `dill` module is required to use the sqlite backend fully. from .sqlite import head_to_sql, start_sql
<Figure size 432x288 with 0 Axes>
<Figure size 432x288 with 0 Axes>
Looking at the images plotted above, the meaning behind spatial corelation becomes apparent, like statistically show spatial segregation within a given dataset. We continue by implementing a function that calculates Moran's I. The function also shows the standard Moran scatterplot with all pixel values plotted and the correlation line drawn.
def Morans_I(data):
"""transforming RGB data to grayscale"""
data_gray = np.dot(data[...,:3], [0.2989, 0.5870, 0.1140])
col,row = data_gray.shape[:2]
WeightMatrix= lat2W(row,col)
WeightMatrix = lat2W(data_gray.shape[0],data_gray.shape[1])
MoranM= Moran(data_gray,WeightMatrix)
fig, ax = moran_scatterplot(MoranM, aspect_equal=True)
print("Raster Dimensions:\t" + str(data_gray.shape))
print("Moran's I Value:\t" +str(round(MoranM.I,4)))
plt.show()
"""
Lets start with calculating and plotting the results for the random image
"""
Morans_I(raster_image_random)
Raster Dimensions: (100, 100) Moran's I Value: 0.0078
As seen in the scatterplot of the random image, the pixel values are randomly distributed. Consequently, the correlation line and Moran's I value are close to 0. This indicates neither a perfect separation nor an even distribution, but almost perfect random distribution.
"""
Now, the chesboard-style image
"""
Morans_I(raster_image_chess)
Raster Dimensions: (100, 101) Moran's I Value: -0.9974
The plot of the chessboard-style image shows, as expected, a value close to -1. This indicates that the values are almost perfectly and evenly distributed.
Please note: A small random noise has been addedto the image. This was done so that the graph can be displayed, as a perfect negative spatial autocorrelation can not be plotted by matplotlib.
"""
Lastly, the half&half image
"""
Morans_I(raster_image_half)
Raster Dimensions: (100, 100) Moran's I Value: 0.9899
Lastly, this plot shows the scatterplot of the half & half image. As expected, the Moran's I is close to 1, idnicating a near-perfect separation of the values.
After looking at the extremes the Moran's I value can take in order to understand what the corrleation value shows, we perform the same for a real-life satellite image of the Salzburg area. The picture used is a real color RGB image (which gets translated into grayscale within the calculation).
"""Loading SBG iamge and transforming to Grayscale"""
raster_image_sbg = plt.imread(r'data/images/sbg.tif')
raster_image_sbg_gray = np.dot(raster_image_sbg[...,:3], [0.2989, 0.5870, 0.1140])
"""Plotting both Images"""
fig1 = plt.imshow(raster_image_sbg)
plt.title("Satellite Image Salzburg RBG")
plt.figure()
fig2 = plt.imshow(raster_image_sbg_gray, cmap="gray")
plt.title("Satellite Image Salzburg Grayscale")
plt.figure()
<Figure size 432x288 with 0 Axes>
<Figure size 432x288 with 0 Axes>
Ask yourself this: which Moran's I value can we expect from this image? Of course the grayscale "muddies" distictions to the human eye, but are similar pixels still more or less next to each other?
"""
Pasting the RBG picture to the function, because the RGB to Grayscale is hardcoded in the function.
"""
Morans_I(raster_image_sbg)
Raster Dimensions: (200, 300) Moran's I Value: 0.8772
The value close to 1 indicates that there is a strong separation/segregation of pixel values. That makes sense, since in the image we can clearly distinguish between built-up urban areas, fields and water surface.