How to crop a raster image by coordinates in python? - python

I have a GeoTiff in UTM32 and coordinates of a rectangle also in UTM32.
(This projection may not always be the case, but the projections will always be the same)
I simply need to crop the image using the rectangle.
The rectangle is given by: (xmin, xmax, ymin, ymax)
699934.584491, 700160.946739, 6168703.00544, 6169364.0093
I know how to make a polygon from the points, how to make a shapefile from the polygon, and I know how to create a masked numpy array using the points. However, I don't know how to use the polygon, the shapefile or the mask to actually crop the image.
I already looked at the description at:
https://pcjericks.github.io/py-gdalogr-cookbook/raster_layers.html#clip-a-geotiff-with-shapefile
However, I don't really understand it and it seems overly complicated. (like I don't know what histogram stretching is supposed to be doing there except confusing)

Akin's answer is mostly right, but doesn't provide a complete explanation.
You can crop a gdal file using gdal_translate, which can be used in python via gdal.Translate.
Best option: projwin
The easiest way is with the projwin flag, which takes 4 values:
window = (upper_left_x, upper_left_y, lower_right_x, lower_right_y)
These values are in map coordinates. The bounds of the input file can be obtained via gdalinfo input_raster.tif from the command line.
NOTE: for many coordinate systems, ymax is actually less than ymin, so it's important to use "upper_left" and "lower_right" to identify the coordinates instead of "max" and "min." Akin's answer didn't work for me because of this difference.
The complete solution, then is:
from osgeo import gdal
upper_left_x = 699934.584491
upper_left_y = 6169364.0093
lower_right_x = 700160.946739
lower_right_y = 6168703.00544
window = (upper_left_x,upper_left_y,lower_right_x,lower_right_y)
gdal.Translate('output_crop_raster.tif', 'input_raster.tif', projWin = window)
Additional option: srcwin
srcwin is another gdal_translate flag similar to projwin, but takes in the pixel and line window via an offset and size, instead of using the map coordinate bounds. You would use it like this:
window = (offset_x, offset_y, size_x, size_y)
gdal.Translate('output_crop_raster.tif', 'input_raster.tif', srcWin = window)

Try to use bbox = (xmin,ymin,xmax,ymax)
from osgeo import gdal
bbox = (xmin,ymin,xmax,ymax)
gdal.Translate('output_crop_raster.tif', 'input_raster.tif', projWin = bbox)

Related

Why does imshow display non-integer x and y values for the pixel position?

I am trying to read the x and y positions of the pixels in images. This is an example of what is shown when I run:
plt.figure(1)
plt.imshow(img)
plt.title('image')
plt.show()
Why are they non-integer values? My best guess is that some scaling is occurring? I am running python on spyder as an IDE.
Edit: Here is the image:
Edit 2: Upon closer inspection, inspecting pixel by pixel, they appear to be at the .5 marks rather than 0 to 1 as well. And here is a screenshot of my axis settings... something is definitely funky here. Anybody have an idea why?
My guess is, that the float values you worry about while hovering over the shown image with your mouse is just the mouse pointer position, which does not have to be integer. Yet still lays within a pixel (squared integer area) and thus gives you information about the channels at that pixel's position.
Another way to get information about your pixels in a more controlled way is given here:
Here is my working code snippet printing the pixel colours from an image:
import os, sys
import Image
im = Image.open("image.jpg")
x = 3
y = 4
pix = im.load()
print pix[x,y]
Answer edit 2: It just makes sense like that. The pixel centers fall on the integer values .0 you expect the pixels to have. If the edges would fall on the .0 a direct mapping between pixel coordinates and pixel values would not be possible within the visualization. Also the pixel having a height and width of 1 is exactly what we would expect.

Overplot SunPy HEK polygon mask on custom numpy array instead of Sunpy Map

I'm working on sunspot detection and I'm trying to build ground truth masks using sunpy.net.hek client to download solar events from the knowledge base.
I followed this tutorial.
My problem is that I'm not able to get the polygon pixel coordinates after the rotation. That is:
ch_boundary = SkyCoord( [(float(v[0]), float(v[1])) * u.arcsec for v in p3],
obstime=ch_date,
frame=frames.Helioprojective)
rotated_ch_boundary = solar_rotate_coordinate(ch_boundary, aia_map.date)
Where p3 holds the original coordinates of the event (they have to be rotated because your picture could not have the same timing as the event on hek). rotated_ch_boundary is an Astropy SkyCoord but cannot figure out how to get the coordinates in pixel relative to the image from that.
Then in the tutorial it just plots the coordinates using Sunpy Map and matplotlib:
aia_map.plot(axes=ax)
ax.plot_coord(rotated_ch_boundary, color='c')
I cannot do that because I want to print the polygon (filled) on a numpy array and save it.
I also tried to build a custom Sunpy map and use the same function to plot:
from sunpy.net.helioviewer import HelioviewerClient
hv = HelioviewerClient()
filepath = hv.download_jp2('2017/07/10 10:00:00', observatory='SDO',
instrument='HMI', detector='HMI', measurement='continuum')
hmi = sunpy.map.Map(filepath)
# QUERY AND ROTATION CODE HERE...
hmi.plot(axes=ax)
ax.plot_coord(rotated_ch_boundary, color='c')
but it doesn't even show the polygon on the plot, I don't know if it's for the different resolution or whatever.
Do you have any idea on how I can plot the polygon on a custom image and save it in order to use it later?
My purpose is to create a black image with a white polygon highlighted. The polygon should be in the exact same position as the sunspot in the corresponding image, let's say an SDO HMI intensitygram of the same day I downloaded from helioviewer.
Solution:
aia_map.world_to_pixel(rotated_ch_boundary)
or
rotated_ch_boundary.to_pixel(aia_map.wcs)
Thanks to fraserwatson for this post

Python Basemap Coordinates

I am using python 3.6 to open a shapefile of the Amazon River on to basemap. However I am confused with how coordinates work in python. I looked up coordinates of the the Amazon River and found it to be lon,lat=-55.126648,-2.163106. But to open my map I need the lat/lon values of corners, which I am not sure how to get.
Here is my code so far:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
map= Basemap(projection='tmerc',
lon_0=180,
lat_0=0,
resolution='l')
map.drawmapboundary(fill_color='aqua')
map.fillcontinents(color='#ddaa66',lake_color='aqua')
map.drawcoastlines()
map.readshapefile('filename','Amazon')
plt.show()
Here is the error message I get when I try to run it:
ValueError: must either specify lat/lon values of corners
(llcrnrlon,llcrnrlat,ucrnrlon,urcrnrlat) in degrees or width and height in meters
When creating your map (map = Basemap(...)) , you need to specify those values. They are lower left corner longitude, lower left corner latitude, upper right corner longitude, and upper right corner latitude. These define the extents of the map. You could just plot the whole earth, then look at the region you want and pick the points off of it for your new corners.
The best method for this type of point plotting is to create your own corners by 'zooming out' from the point. this means you'll need to specify llcrnrlat (lower left corner latitude), etc. as such:
my_coords = [38.9719980,-76.9219820]
# How much to zoom from coordinates (in degrees)
zoom_scale = 1
# Setup the bounding box for the zoom and bounds of the map
bbox = [my_coords[0]-zoom_scale,my_coords[0]+zoom_scale,\
my_coords[1]-zoom_scale,my_coords[1]+zoom_scale]
plt.figure(figsize=(12,6))
# Define the projection, scale, the corners of the map, and the resolution.
m = Basemap(projection='merc',llcrnrlat=bbox[0],urcrnrlat=bbox[1],\
llcrnrlon=bbox[2],urcrnrlon=bbox[3],lat_ts=10,resolution='i')
If you want to see a full tutorial on plotting lat/lon points from a .csv file, check out my tutorial where I go through the whole process and include the full code:
Geographic Mapping from a CSV File Using Python and Basemap
You end up with a result that looks like the following:

qwt/pyqt custom scale for image plot (pixel to mm conversion)

I've decided to use guiqwt as my main plot library in Python and it works quite well. However, I'm missing a contour plot feature, so I had to work out my own contours in my image plots. That was quite easy by using scikit. Now I have my plot showing the image and the contours on top. Scale unit in x and y-direction is pixel as the image raw data is given per pixel and the calculated contours as well.
My problem is to convert the pixel-scale into e. g. mm-scale without scaling the image. I want to replace the original scale with a scale that represents the measured distances. The distances are available in an array.
In my first attempt I tried to change the AxisScaleDivision by creating a new one and using QwtPlot::setAxisScaleDiv. But that seems to work like a zoom-function as the image is reduced to the new interval.
Here is my code for a small example:
from guiqwt.plot import ImageDialog
from guiqwt.builder import make
from skimage import measure
import numpy as np
data = np.random.rand(80,30)
contours = measure.find_contours(data, 0.1)
win = ImageDialog(edit=False, toolbar=True, wintitle="Contrast test",
options=dict(show_contrast=True))
img = make.image(data)
plot = win.get_plot()
plot.add_item(img)
for n, contour in enumerate(contours):
curve = make.curve(contour[:, 1], contour[:, 0], 'k-')
plot.add_item(curve)
win.show()
scaleEng = plot.axisScaleEngine(2)
scaleDiv = scaleEng.divideScale(20, 30, 5, 5, 0)
plot.setAxisScaleDiv(2, scaleDiv)
plot.replot()
The syntax is very close to qwt, so I think anybody who is familiar with qwt might be able to help me :)
The image zoom should stay unaltered. Only the axis should be recalculated to a mm-scale and afterwards, of course, adapted when the zoom function is used.
I solved the problem by using a completely different approach. I used the xyimage-function of guiqwt. However, I had to scale my contours too. I missed that the last time, that's why I posted the question.

Matplotlib text bounding box dimensions

What I want to do:
I want to get the position and dimensions of a text instance in matplotlib world units (not screen pixels), with the intention of calculating and preventing text overlaps.
I'm developing on Mac OSX 10.9.3, Python 2.7.5, matplotlib 1.3.1.
What I've tried:
Let t be a text instance.
t.get_window_extent(renderer):
This gets bounding box dimensions in pixels, and I need world coordinates (normalized between -1.0 and 1.0 in my case).
t._get_bbox_patch():
t = ax.text(x, y, text_string, prop_dict, bbox=dict(facecolor='red', alpha=0.5, boxstyle='square'))
print t._get_bbox_patch()
When I execute the above sequence, the output is FancyBboxPatchFancyBboxPatch(0,0;1x1). In the image I produce, the text instance is rendered properly with a red bounding box, so that output leads me to think that the FancyBbox is instantiated but not actually populated with real dimensions until render time.
So, how can I get the position and dimensions of the text instance's bounding box in the same coord system units that I used for the x and y parameters I passed to ax.text(...)?
This may help a bit.
import matplotlib.pyplot as plt
f = plt.figure()
ax = f.add_subplot(111)
ax.plot([0,10], [4,0])
t = ax.text(3.2, 2.1, "testing...")
# get the inverse of the transformation from data coordinates to pixels
transf = ax.transData.inverted()
bb = t.get_window_extent(renderer = f.canvas.renderer)
bb_datacoords = bb.transformed(transf)
# Bbox('array([[ 3.2 , 2.1 ],\n [ 4.21607125, 2.23034396]])')
This should give what you want. If you want to have the coordinates in terms of figure coordinates (0..1,0..1), then use the inverse of ax.transAxes.
However, there is a small catch in this solution. An excerpt from the matplotlib documentation:
Any Text instance can report its extent in window coordinates (a negative x coordinate is outside the window), but there is a rub.
The RendererBase instance, which is used to calculate the text size, is not known until the figure is drawn (draw()). After the window is drawn and the text instance knows its renderer, you can call get_window_extent().
So, before the figure is really drawn, there seems to be no way to find out the text size.
BTW, you may have noticed that the Bbox instances have method overlaps which may be used to find out whether the Bbox overlaps with another one (bb1.overlaps(bb2)). This may be useful in some cases, but it does not answer the question "how much".
If you have rotated texts, you will have hard time seeing if they overlap, but that you probably already know.
Little late, but here is other example, which shows how to get bounding box of a text object in data coordinates/units. It also draws the bounding box obtained around the text for its visual representation.
import matplotlib.pyplot as plt
# some example plot
plt.plot([1,2,3], [2,3,4])
t = plt.text(1.1, 3.1, "my text", fontsize=18)
# to get the text bounding box
# we need to draw the plot
plt.gcf().canvas.draw()
# get bounding box of the text
# in the units of the data
bbox = t.get_window_extent()\
.inverse_transformed(plt.gca().transData)
print(bbox)
# prints: Bbox(x0=1.1, y0=3.0702380952380954, x1=1.5296875, y1=3.2130952380952382)
# plot the bounding box around the text
plt.plot([bbox.x0, bbox.x0, bbox.x1, bbox.x1, bbox.x0],
[bbox.y0, bbox.y1, bbox.y1, bbox.y0, bbox.y0])
plt.show()

Categories