How to show negative values in an NDVI image? - python

I am fairly new to python. I am working with some Landsat 8 .tiff images to produce an NDVI image. When I produce the image, the river does not show as a negative value. NDVI goes from -1 to 1, but the lowest it goes to is 0 in this image. This is a portion of the code I used:
import os
from glob import glob
import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import rasterio as rio
from rasterio.plot import plotting_extent
import rioxarray as rxr
import geopandas as gpd
import earthpy as et
import earthpy.spatial as es
import earthpy.plot as ep
#create image stack
arr_st, meta = es.stack(stack_bands_path, nodata = -9999)
#allow division by 0
np.seterr(divide='ignore', invalid='ignore')
#create NDVI
red = arr_st[3].astype(float)
nir = arr_st[4].astype(float)
ndvi = np.divide((nir-red), (nir+red))
ep.plot_bands(ndvi, cmap="RdYlGn", cols=1, vmin=-1, vmax=1)
plt.show()
I am not sure what I am doing wrong. I was originally using earthpy.spatial.normalized_difference but the rivers were coming out green (or 1 on the colorbar).
My theory is that the numbers less than 0 are showing up as zero...but I am not sure. Any help is appreciated. Image of the NDVI is below.
NDVI image

The NDVI will fall in the range -1 to +1 so an integer is not a good representation because there are only 3 possible integer outcomes in that range, namely -1, 0 and 1.
I am not familiar with the specific package you are using but, if you want fractional (decimal) results, I would imagine you'd want to use the float type, i.e.:
red = arr_st[3].astype(float)
nir = arr_st[4].astype(float)

Related

How can I extract the information from a tree using viewer?

from astropy.io import fits
import matplotlib.pyplot as plt
from astrodendro import Dendrogram, pp_catalog
import numpy as np
fname = "/home/citlali/Documentos/Servicio/m/m8093-1901/manga-8093-1901.Pipe3D.cube.fits.gz"
image = fits.open(fname)
DATOS = image["FLUX_ELINES"].data
img = DATOS[45, :, :]
d = Dendrogram.compute(img, min_value=0, min_delta=0, min_npix=0.1)
d.trunk[0]
v = d.viewer()
v.show()
This is my code and when I plotted I got this where I select whatever regions I want (for example, red and green). So, I'm looking for extract the information of those regions but I don't know how to do it. If anyone have an idea o know a function, I will appreciate it. Also it has to be atuomated.
Thank you.

Set scale of pixels in microns in python

I have the following problem: after having segmented some objects across several slices in a stack, I am now trying to analyse them to extract several measurements, such as volume and major and minimum axes lengths. I would like to set the pixel size before extracting any measurements, i.e. to convert the XYZ axis to the corresponding pixel size in microns and then calculate the actual values (e.g. volume, axis length etc.)
For instance:
import os
from glob import glob
import pandas as pd
import numpy as np
from skimage.io import imread, imsave
from skimage.measure import regionprops, regionprops_table
pathname = "path/to/image.tif"
Y = sorted(glob(pathname+'/*.tif'))
Y = list(map(imread,Y))
pixelsize = (0.11, 0.11, 0.25) #xyz
table = pd.DataFrame(regionprops_table(mask.astype(int), properties=['label', 'area', 'major_axis_length'))
At the moment I am doing this, for instance:
table["major_axis_length"] = table["major_axis_length"].apply(lambda x: x*pixelsize[0])
This can work for 2D, but not for 3D measurements. How can I just set the scale before performing any measurement?
Thank you !

How to represent scalar variables over geographic map in Jupyter Notebook

I am representing some geographical data in a Jupyter notebook: temperature, ocean wave height, etc. I have numpy arrays that have the latitude, longitude, and value for those variables. I would like to display these variables over a geographical map, preferably using ipyleaflet (because that is what I am already using). I am trying to get a result similar to a heatmap.
I tried to use the ipyleaflet Heatmap, but it seems to me that it is designed to represent agregation of points and not scalar uniform arrays, because I can't get it to show the results properly. I think ipyleaflet may lack a function to represent this kind of data, but seems odd since it has a very nice Velocity funtion to represent vectorial variables.
The only way I can think of to make this would be to generate an image with matplotlib and then adding it to the map in an image layer, but I feel like that is not the proper way to do it.
For representing a heatmap I would recommend to use Cartopy in combination with Matplotlib.
Here a ready to use script I made for a world projection with a coastline:
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from cartopy.util import add_cyclic_point
# Set x y z variables
x = longitude_data
y = latitude_data
z = heat_map_data
# Set up figure and projection
z, x = add_cyclic_point(z, coord=x)
fig = plt.figure()
ax = fig.add_subplot(1,1,1, projection=ccrs.PlateCarree() )
# Set data range and colourmap
levels = np.arange(min,max,steps)
plt.contourf(x, y, z,levels = levels,transform=ccrs.PlateCarree(),cmap="rainbow")
# Set axes, extent (world) and labels
ax.set_xticks(np.linspace(-180,180,num=7), crs=ccrs.PlateCarree())
ax.set_yticks(np.linspace(-60,60,num=5), crs=ccrs.PlateCarree())
ax.add_feature(cfeature.COASTLINE) #Add coastline
ax.set_global()
ax.set_title('Heatmap')
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
# Add colorbar
plt.colorbar(ax=ax,shrink=0.7,orientation="vertical")
fig.show()
With the Cartopy and Matplotlib documentation you should now be able to create some maps.
As you say, you can generate an image and overlay it onto the map. This is the suggestion I was given when I asked about this on Github.
There's an example notebook here.
Not quite as easy as matplotlib, but you get all the nice interactivity of ipyleaflet!
Here's the current version of the notebook in case the link changes (converted to markdown via jupyter-nbconvert --to markdown Numpy.ipynb)
From NumPy to Leaflet
This notebook shows how to display some raster geographic data in IPyLeaflet. The data is a NumPy array, which means that you have all the power of the Python scientific stack at your disposal to process it.
The following libraries are needed:
* requests
* tqdm
* rasterio
* numpy
* scipy
* pillow
* matplotlib
* ipyleaflet
The recommended way is to try to conda install them first, and if they are not found then pip install.
import requests
import os
from tqdm import tqdm
import zipfile
import rasterio
from affine import Affine
import numpy as np
import scipy.ndimage
from rasterio.warp import reproject, Resampling
import PIL
import matplotlib.pyplot as plt
from base64 import b64encode
try:
from StringIO import StringIO
py3 = False
except ImportError:
from io import StringIO, BytesIO
py3 = True
from ipyleaflet import Map, ImageOverlay, basemap_to_tiles, basemaps
Download a raster file representing the flow accumulation for South America. This gives an idea of the river network.
url = 'https://edcintl.cr.usgs.gov/downloads/sciweb1/shared/hydrosheds/sa_30s_zip_grid/sa_acc_30s_grid.zip'
filename = os.path.basename(url)
name = filename[:filename.find('_grid')]
adffile = name + '/' + name + '/w001001.adf'
if not os.path.exists(adffile):
r = requests.get(url, stream=True)
with open(filename, 'wb') as f:
total_length = int(r.headers.get('content-length'))
for chunk in tqdm(r.iter_content(chunk_size=1024), total=(total_length/1024) + 1):
if chunk:
f.write(chunk)
f.flush()
zip = zipfile.ZipFile(filename)
zip.extractall('.')
We transform the data a bit so that rivers appear thicker.
dataset = rasterio.open(adffile)
acc_orig = dataset.read()[0]
acc = np.where(acc_orig<0, 0, acc_orig)
shrink = 1 # if you are out of RAM try increasing this number (should be a power of 2)
radius = 5 # you can play with this number to change the width of the rivers
circle = np.zeros((2*radius+1, 2*radius+1)).astype('uint8')
y, x = np.ogrid[-radius:radius+1,-radius:radius+1]
index = x**2 + y**2 <= radius**2
circle[index] = 1
acc = np.sqrt(acc)
acc = scipy.ndimage.maximum_filter(acc, footprint=circle)
acc[acc_orig<0] = np.nan
acc = acc[::shrink, ::shrink]
The original data is in the WGS 84 projection, but Leaflet uses Web Mercator, so we need to reproject.
# At this point if GDAL complains about not being able to open EPSG support file gcs.csv, try in the terminal:
# export GDAL_DATA=`gdal-config --datadir`
with rasterio.Env():
rows, cols = acc.shape
src_transform = list(dataset.transform)
src_transform[0] *= shrink
src_transform[4] *= shrink
src_transform = Affine(*src_transform[:6])
src_crs = {'init': 'EPSG:4326'}
source = acc
dst_crs = {'init': 'EPSG:3857'}
dst_transform, width, height = rasterio.warp.calculate_default_transform(src_crs, dst_crs, cols, rows, *dataset.bounds)
dst_shape = height, width
destination = np.zeros(dst_shape)
reproject(
source,
destination,
src_transform=src_transform,
src_crs=src_crs,
dst_transform=dst_transform,
dst_crs=dst_crs,
resampling=Resampling.nearest)
acc_web = destination
Let's convert our NumPy array to an image. For that we must specify a colormap (here plt.cm.jet).
acc_norm = acc_web - np.nanmin(acc_web)
acc_norm = acc_norm / np.nanmax(acc_norm)
acc_norm = np.where(np.isfinite(acc_web), acc_norm, 0)
acc_im = PIL.Image.fromarray(np.uint8(plt.cm.jet(acc_norm)*255))
acc_mask = np.where(np.isfinite(acc_web), 255, 0)
mask = PIL.Image.fromarray(np.uint8(acc_mask), mode='L')
im = PIL.Image.new('RGBA', acc_norm.shape[::-1], color=None)
im.paste(acc_im, mask=mask)
The image is embedded in the URL as a PNG file, so that it can be sent to the browser.
if py3:
f = BytesIO()
else:
f = StringIO()
im.save(f, 'png')
data = b64encode(f.getvalue())
if py3:
data = data.decode('ascii')
imgurl = 'data:image/png;base64,' + data
Not quite as easy as matplotlib, but you get all the nice interactivity of ipyleaflet!
Finally we can overlay our image and if everything went fine it should be exactly over South America.
b = dataset.bounds
bounds = [(b.bottom, b.left), (b.top, b.right)]
io = ImageOverlay(url=imgurl, bounds=bounds)
center = [-10, -60]
zoom = 2
m = Map(center=center, zoom=zoom, interpolation='nearest')
m
tile = basemap_to_tiles(basemaps.Esri.WorldStreetMap)
m.add_layer(tile)
You can play with the opacity slider and check that rivers from our data file match the rivers on OpenStreetMap.
m.add_layer(io)
io.interact(opacity=(0.0,1.0,0.01))

Importing images for manifold Isomap

There are 192 x 144 pixel images. They should be imported to a Python list so that the items in the list are NDArray instances. New dataframe should be created from the list and that dataframe should be given to Isomap. iso.fit(df) fails with the errors
array = array.astype(np.float64)
ValueError: setting an array element with a sequence.
I have spent more than one day trying to figure out how the NDArrays should be processed and the dataframe loaded with them. No luck. Any help would be appreciated.
import pandas as pd
from scipy import misc
import glob
from sklearn import manifold
samples = []
for filename in glob.glob('Datasets/ALOI/32/*.png'):
img = misc.imread(filename, mode='I')
samples.append(img)
df = pd.DataFrame.from_records(samples, coerce_float=True)
iso = manifold.Isomap(n_neighbors=6, n_components=3)
iso.fit(df)
If those are gray scale images from the ALOI, you probably want to treat each pixel's brightness as a feature. Therefore, you should flatten the img array with img.reshape(-1). The revised code follows:
import pandas as pd
from scipy import misc
import glob
from sklearn import manifold
samples = []
for filename in glob.glob('Datasets/ALOI/32/*.png'):
img = misc.imread(filename, mode='I')
# the following line changed
samples.append(img.reshape(-1))
df = pd.DataFrame.from_records(samples, coerce_float=True)
iso = manifold.Isomap(n_neighbors=6, n_components=3)
iso.fit(df)

How can I partially plot a Healpix map using Healpy?

When working with healpy, I am able to plot a Healpix map in Mollview using
import healpy
map = 'filename.fits'
healpy.visufunc.mollview(map)
or as in the tutorial
>>> import numpy as np
>>> import healpy as hp
>>> NSIDE = 32
>>> m = np.arange(hp.nside2npix(NSIDE))
>>> hp.mollview(m, title="Mollview image RING")
which outputs
Is there a way to display only certain regions of the map? For example, only the upper hemisphere, or only the left side?
What I have in mind is viewing only small patches of the sky to see small point sources, or something like the "half-sky" projection from LSST
You can use a mask, which is a boolean map of the same size, where 1 are masked, 0 are not masked:
http://healpy.readthedocs.org/en/latest/tutorial.html#masked-map-partial-maps
Example:
import numpy as np
import healpy as hp
NSIDE = 32
m = hp.ma(np.arange(hp.nside2npix(NSIDE), dtype=np.double))
mask = np.zeros(hp.nside2npix(NSIDE), dtype=np.bool)
pixel_theta, pixel_phi = hp.pix2ang(NSIDE, np.arange(hp.nside2npix(NSIDE)))
mask[pixel_theta > np.pi/2] = 1
m.mask = mask
hp.mollview(m)

Categories

Resources