I am working on identifying tumors in CT images, using SciPy. Once I've identified the slice containing the tumor, I retrieve the slice using:
slice_x, slice_y = ndimage.find_objects(label_im==label)[0]
I then plot the full image as follows:
plt.imshow(image, cmap='gray')
I want to overlay the contour of the extracted slice (containing the tumor) on top of the original image in the appropriate location. However, I can't get the contour plot to lie on top of the proper region. Instead, the contour plot always lies in the top-left corner of the image. Here's what I'm using:
plt.contour(mask[slice_x, slice_y], position=(slice_x.start, \
slice_y.start), linewidths=2, colors='r')
Here is the result:
How can I set the position of the contour plot inside the main plot? Thanks!
You need to use the extent keyword argument instead of position. However, you'll need to specify a maximum as well as a start (i.e. it needs to be extent=[xmin, xmax, ymin, ymax])
In your case, it would look something like:
extent=[xslice.start, xslice.stop+1, yslice.start, yslice.stop+1]
plt.contour(mask[xslice, yslice], extent=extent)
Note that you may need to use the origin keyword argument (or flip ymin and ymax) to control the way the input data is interpreted.
Alternately, you could do something like the following:
x, y = np.mgrid[xslice, yslice]
plt.contour(x, y, mask[xslcie, yslice])
It will be a bit inefficient if you're working with a large region, but it's much cleaner.
As a full example of the latter approach:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
data = np.random.random((100,100))
xslice, yslice = slice(20, 30), slice(45, 65)
plt.imshow(data, cmap=cm.gray)
x, y = np.mgrid[xslice, yslice]
plt.contour(x, y, data[xslice, yslice])
plt.show()
Related
I have an image in python. It's a map of california, and I need to place some point on this map.
The coordonate of each point are retrieve from a csv. But the value of each coordinate are in latitude/longitude. So, i need to convert it to the dimension of my picture.
So, here's is the description of my situation:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
# dpi for the saved figure: https://stackoverflow.com/a/34769840/3129414
dpi = 120
img = mpimg.imread("california_map_blank.png")
height, width, bands = img.shape
# Update figure size based on image size
figsize = width / float(dpi), height / float(dpi)
# Create a figure of the right size with one axes that takes up the full figure
figure = plt.figure(figsize=figsize)
axes = figure.add_axes([0, 0, 1, 1])
# Draw the image
axes.imshow(img, interpolation='nearest')
Here's the result:
First i need to modify the y-axis. I need to inverse it so the 0 start at the bottom. Then I need to modify the value of the axis, [31,42] for y-axis and [-123,-114] for x-axis. Because the point I want to place in this map are all in this range. One example of coordinate: 41.76440000093729, -124.1998.
Now here's my question. Is it possible to achieve this ? How ?
PS: I use python 3.6, and I already know how to place point on the image. I don't need to save the image just showing.
PPS: My final goal in fact is to convert lat/lon data into coordinate in a picture so if you know any other way to do it(in Python of course) please tell me.
EDIT: If I apply this: axes.set_xlim(-124.5,-114) it give me this:
I want to have the axis with this range but with the whole image.
In fact, at the end I will not display the axis I will just put the map with the points, but I need to place the point on the map so I think I need to go through this step.
EDIT2: I tried this: axes.imshow(img[::-1], origin='lower', interpolation='nearest') it works fine to reverse the axis but when I draw a point python draw it in the same place when I the axis was normal.
You need to set the limits of the image via the extent= parameter of imshow. These should be quite precise values for the longitudes left and right, and for the latitudes of bottom and top.
Depending on how deformed the map is, the result can be good enough or not. Try to find the exact longitudes and latitudes of the corners of your map, e.g. via Google Maps.
Depending on how you're running your Python program, matplotlib will show an interactive plot. You can zoom to every region, and the axes will adapt. In the bar at the bottom the x and y-positions will be shown. If they are not the desired ones, you can try to change the extents until they match.
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img = mpimg.imread("california_map_blank.png")
dpi = 120
height, width, bands = img.shape
# Update figure size based on image size
figsize = width / float(dpi), height / float(dpi)
# Create a figure of the right size with one axes that takes up the full figure
fig, ax = plt.subplots(figsize=figsize)
# find the extent
longitude_top_left = -124.5
longitude_top_right = -113
latitude_bottom_left = 32
latitude_top_left = 42
extent = [longitude_top_left, longitude_top_right, latitude_bottom_left, latitude_top_left]
# Draw the image
ax.imshow(img, interpolation='nearest', extent=extent)
plt.show()
I am new in Python. The answer to my question might be available in the StackOverflow, but honestly speaking, I tried almost all the codes and suggestions available in the StackOverflow.
My problem: Almost the same as it is described here. I have coordinate points (x and y) and the corresponding value (p) as a .csv file. I am reading that file using pandas.
df = pd.read_csv("example.csv")
The example.csv file can be download from here. Let an image of size 2000 x 2000.
Task:
Based on the x and y coordinate points in the excel sheet, I have to locate the point in that image.
Lets, A is an image and A(x,y) is any point within A. Now I have to generate a heat map in such a way so that 50 pixels from x and 50 pixels fromy i.e., A(x,y), A(x+50, y), A(x, y+50) and A(x+50, y+50) contains p corresponding to that coordinate points.
I found this link which is very helpful and serves my issue, but the problem is some more modifications are necessary for my datasets.
The code which is available in the above link:
#!/usr/bin/python3
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from skimage import io
from skimage.color import rgb2gray
import matplotlib as mpl
# Read original image
img = io.imread('img.jpg')
# Get the dimensions of the original image
x_dim, y_dim, z_dim = np.shape(img)
# Create heatmap
heatmap = np.zeros((x_dim, y_dim), dtype=float)
# Read CSV with a Pandas DataFrame
df = pd.read_csv("data.csv")
# Set probabilities values to specific indexes in the heatmap
for index, row in df.iterrows():
x = np.int(row["x"])
y = np.int(row["y"])
x1 = np.int(row["x1"])
y1 = np.int(row["y1"])
p = row["Probability value"]
heatmap[x:x1,y:y1] = p
# Plot images
fig, axes = plt.subplots(1, 2, figsize=(8, 4))
ax = axes.ravel()
ax[0].imshow(img)
ax[0].set_title("Original")
fig.colorbar(ax[0].imshow(img), ax=ax[0])
ax[1].imshow(img, vmin=0, vmax=1)
ax[1].imshow(heatmap, alpha=.5, cmap='jet')
ax[1].set_title("Original + heatmap")
# Specific colorbar
norm = mpl.colors.Normalize(vmin=0,vmax=2)
N = 11
cmap = plt.get_cmap('jet',N)
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
sm.set_array([])
plt.colorbar(sm, ticks=np.linspace(0,1,N),
boundaries=np.arange(0,1.1,0.1))
fig.tight_layout()
plt.show()
Issues which I am facing when using this code:
This code is generating a heat map of square edges, but I am expecting a smooth edge. I know Gaussian distribution might solve this problem. But I am new in python and I don't know how to implement the Gaussian Distribution in my dataset.
The regions which don't belong to the coordinate points also generating a layer of color. As a result in an overlayed image those layer covering the background of original images. In one sentence I want the background of the heat map will be transparent so that overlays will not create any problem in showing the regions which are not covered by the coordinate points.
Any leads will be highly appreciated.
Your code is perfect. Just change only one line, then your both issues will be solved.
Before changes:
ax[1].imshow(heatmap, alpha=.5, cmap='jet')
After changes:
ax[1].imshow(heatmap, alpha=.5, cmap='coolwarm', interpolation='gaussian')
Though above changes will solve your issue, but if you want then for additional transparency, you can use below function
def transparent_cmap(cmap, N=255):
"Copy colormap and set alpha values"
mycmap = cmap
mycmap._init()
mycmap._lut[:,-1] = np.linspace(0, 0.8, N+4)
return mycmap
mycmap = transparent_cmap(plt.cm.coolwarm)
In that case, your previous code line will change like below:
ax[1].imshow(heatmap, alpha=.5, cmap=mycmap, vmin=0, vmax=1)
The question you linked uses plotly. If you don't want to use that and want to simply smooth the way your data looks, I suggest just using a gaussian filter using scipy.
At the top, import:
import seaborn as sns
from scipy.ndimage.filters import gaussian_filter
Then use it like this:
df_smooth = gaussian_filter(df, sigma=1)
sns.heatmap(df_smooth, vmin=-40, vmax=150, cmap ="coolwarm" , cbar=True , cbar_kws={"ticks":[-40,150,-20,0,25,50,75,100,125]})
You can change the amount of smoothing, using e.g. sigma=3, or any other number that gives you the amount of smoothing you want.
Keep in mind that that will also "smooth out" any maximum data peaks you have, so your minimum and maximum data will not be the same that you specified in your normalization anymore. To still get good looking heatmaps I would suggest not using fixed values for your vmin and vmax, but:
sns.heatmap(df_smooth, vmin=np.min(df_smooth), vmax=np.max(df_smooth), cmap ="coolwarm" , cbar=True , cbar_kws={"ticks":[-40,150,-20,0,25,50,75,100,125]})
In case that you Gaussian filter fulfill your expectations you mentioned you can even implement Gaussian normalization on your data directly.
I'm trying to Plot a high resolution surface_plot, but I would also really like some nice grid lines on top of it. If i use the gridlines in the same argument
ax.plot_surface(x_itp, y_itp, z_itp, rstride=1, cstride=1, facecolors=facecolors, linewidth=0.1)
I get a LOT of grid lines. If I, on the other hand, set "rstride" and "cstride" to higher values, my sphere will become ugly.
I then tried to smash a
ax.plot_wireframe(x_itp, y_itp, z_itp, rstride=3, cstride=3)
in afterwards, but it just lies on top of the colored sphere.. meaning that I can see the backside of the wireframe and then the surface_plot behind it all.
Have anyone tried this?
Another option was to use "Basemap" which can create a nice grid, but then I will have to adapt my colored surface to that.?!
My plot looks like this:
If I add edges to the map with a higher "rstride" and "cstride" then it looks like this:
code :
norm = plt.Normalize()
facecolors = plt.cm.jet(norm(d_itp))
# surface plot
fig, ax = plt.subplots(1, 1, subplot_kw={'projection':'3d', 'aspect':'equal'})
ax.hold(True)
surf = ax.plot_surface(x_itp, y_itp, z_itp, rstride=4, cstride=4, facecolors=facecolors)
surf.set_edgecolors("black")
I want to show the \theta and \phi angles around the sphere.. maybe with 30 degrees apart.
Cheers!
Morten
It looks like you may need to use basemap. With plot_surface() you can either have high resolution plot or low resolution with good grid on top. But not both. I just made a simple basemap with contour plot. I think you can do easily apply pcolor on it. Just do not draw continent and country boundary. Then, you have a nice sphere which gives more control. After making your plot, you can easily add grid on it.
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
map = Basemap(projection='ortho',lat_0=45,lon_0=-150)
map.drawmapboundary(fill_color='aquamarine')
map.drawmeridians(np.arange(0,360,30)) # grid every 30 deg
map.drawparallels(np.arange(-90,90,30))
nlats = 73; nlons = 145; delta = 2.*np.pi/(nlons-1)
lats = (0.5*np.pi-delta*np.indices((nlats,nlons))[0,:,:])
lons = (delta*np.indices((nlats,nlons))[1,:,:])
wave = 0.6*(np.sin(2.*lats)**6*np.cos(4.*lons))
mean = 0.5*np.cos(2.*lats)*((np.sin(2.*lats))**2 + 2.)
x, y = map(lons*180./np.pi, lats*180./np.pi) # projection from lat, lon to sphere
cs = map.contour(x,y,wave+mean,15,linewidths=1.5) # contour data. You can use pcolor() for your project
plt.title('test1')
plt.show()
I am trying to create a color wheel in Python, preferably using Matplotlib. The following works OK:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
xval = np.arange(0, 2*pi, 0.01)
yval = np.ones_like(xval)
colormap = plt.get_cmap('hsv')
norm = mpl.colors.Normalize(0.0, 2*np.pi)
ax = plt.subplot(1, 1, 1, polar=True)
ax.scatter(xval, yval, c=xval, s=300, cmap=colormap, norm=norm, linewidths=0)
ax.set_yticks([])
However, this attempt has two serious drawbacks.
First, when saving the resulting figure as a vector (figure_1.svg), the color wheel consists (as expected) of 621 different shapes, corresponding to the different (x,y) values being plotted. Although the result looks like a circle, it isn't really. I would greatly prefer to use an actual circle, defined by a few path points and Bezier curves between them, as in e.g. matplotlib.patches.Circle. This seems to me the 'proper' way of doing it, and the result would look nicer (no banding, better gradient, better anti-aliasing).
Second (relatedly), the final plotted markers (the last few before 2*pi) overlap the first few. It's very hard to see in the pixel rendering, but if you zoom in on the vector-based rendering you can clearly see the last disc overlap the first few.
I tried using different markers (. or |), but none of them go around the second issue.
Bottom line: can I draw a circle in Python/Matplotlib which is defined in the proper vector/Bezier curve way, and which has an edge color defined according to a colormap (or, failing that, an arbitrary color gradient)?
One way I have found is to produce a colormap and then project it onto a polar axis. Here is a working example - it includes a nasty hack, though (clearly commented). I'm sure there's a way to either adjust limits or (harder) write your own Transform to get around it, but I haven't quite managed that yet. I thought the bounds on the call to Normalize would do that, but apparently not.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
import matplotlib as mpl
fig = plt.figure()
display_axes = fig.add_axes([0.1,0.1,0.8,0.8], projection='polar')
display_axes._direction = 2*np.pi ## This is a nasty hack - using the hidden field to
## multiply the values such that 1 become 2*pi
## this field is supposed to take values 1 or -1 only!!
norm = mpl.colors.Normalize(0.0, 2*np.pi)
# Plot the colorbar onto the polar axis
# note - use orientation horizontal so that the gradient goes around
# the wheel rather than centre out
quant_steps = 2056
cb = mpl.colorbar.ColorbarBase(display_axes, cmap=cm.get_cmap('hsv',quant_steps),
norm=norm,
orientation='horizontal')
# aesthetics - get rid of border and axis labels
cb.outline.set_visible(False)
display_axes.set_axis_off()
plt.show() # Replace with plt.savefig if you want to save a file
This produces
If you want a ring rather than a wheel, use this before plt.show() or plt.savefig
display_axes.set_rlim([-1,1])
This gives
As per #EelkeSpaak in comments - if you save the graphic as an SVG as per the OP, here is a tip for working with the resulting graphic: The little elements of the resulting SVG image are touching and non-overlapping. This leads to faint grey lines in some renderers (Inkscape, Adobe Reader, probably not in print). A simple solution to this is to apply a small (e.g. 120%) scaling to each of the individual gradient elements, using e.g. Inkscape or Illustrator. Note you'll have to apply the transform to each element separately (the mentioned software provides functionality to do this automatically), rather than to the whole drawing, otherwise it has no effect.
I just needed to make a color wheel and decided to update rsnape's solution to be compatible with matplotlib 2.1. Rather than place a colorbar object on an axis, you can instead plot a polar colored mesh on a polar plot.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
import matplotlib as mpl
# If displaying in a Jupyter notebook:
# %matplotlib inline
# Generate a figure with a polar projection
fg = plt.figure(figsize=(8,8))
ax = fg.add_axes([0.1,0.1,0.8,0.8], projection='polar')
# Define colormap normalization for 0 to 2*pi
norm = mpl.colors.Normalize(0, 2*np.pi)
# Plot a color mesh on the polar plot
# with the color set by the angle
n = 200 #the number of secants for the mesh
t = np.linspace(0,2*np.pi,n) #theta values
r = np.linspace(.6,1,2) #radius values change 0.6 to 0 for full circle
rg, tg = np.meshgrid(r,t) #create a r,theta meshgrid
c = tg #define color values as theta value
im = ax.pcolormesh(t, r, c.T,norm=norm) #plot the colormesh on axis with colormap
ax.set_yticklabels([]) #turn of radial tick labels (yticks)
ax.tick_params(pad=15,labelsize=24) #cosmetic changes to tick labels
ax.spines['polar'].set_visible(False) #turn off the axis spine.
It gives this:
I've taken an image and extracted some features from it using OpenCv. I'd like to replot those points and their respective areas (which are real pixel values) into a scatter window and then save it. Unfortunately, when I plot the points, they resize to stay more visible. If I zoom in they resize. I'd like to save the whole figure retaining the actual ratio of pixel (x,y) coordinates to size of points plotted.
For instance:
import matplotlib.pyplot as plt
x=[5000,10000,20000]
y=[20000,10000,5000]
area_in_pixels=[100,200,100]
scatter(x,y,s=area_in_pixels)
I would like this to produce tiny dots on the image. They should span like 10 xy units. However, the dots it produces are large, and appear to span 1000 xy units.
I've tried resizing the image with:
plt.figure(figsize=(10,10))
Which seems to resize the points relative to their position a little. But I'm not sure what scale I would select to make this accurate. DPI settings on plt.figsave seem to make the saved image larger but don't appear to alter relative spot sizes.
Asked another way, is there another way to relate the s which is in points^2 to a real number or to the units of the x-y axis?
You can use patches to create markers sized relative to the data coordinates.
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
xData=[5000,10000,20000, 15000]
yData=[20000,10000,5000, 15000]
radius_in_pixels=[100,200,100, 1000] # Circle takes radius as an argument. You could convert from area.
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
for x, y, r in zip(xData, yData, radius_in_pixels):
ax.add_artist(Circle(xy=(x, y), radius = r))
plt.xlim(0, max(xData) + 200)
plt.ylim(0, max(yData) + 200)
plt.show()