Note: I will be answering this question myself to help other people who come across this problem in the future. Feel free to submit your own answers if you want, but know that it's already answered!
How can I overlay a masked image with one colormap onto another image with a different colormap in Chaco? Also, how can I add colorbars for each of these?
I'm not taking 100% credit for this, with a quick search online i've found that you can do a simple overlay with the code below:
Source where found:
http://docs.enthought.com/chaco/user_manual/containers.html#overlayplotcontainer
Ref Code:
class OverlayImageExample(HasTraits):
plot = Instance(OverlayImage)
traits_view = View(
Item('plot', editor=ComponentEditor(), show_label=False),
width=800, height=600, resizable=True
)
def _plot_default(self):
# Create data
x = linspace(-5, 15.0, 100)
y = jn(3, x)
pd = ArrayPlotData(index=x, value=y)
zoomable_plot = Plot(pd)
zoomable_plot.plot(('index', 'value'),
name='external', color='red', line_width=3)
# Attach tools to the plot
zoom = ZoomTool(component=zoomable_plot,
tool_mode="box", always_on=False)
zoomable_plot.overlays.append(zoom)
zoomable_plot.tools.append(PanTool(zoomable_plot))
# Create a second inset plot, not resizable, not zoom-able
inset_plot = Plot(pd)
inset_plot.plot(('index', 'value'), color='blue')
inset_plot.set(resizable = '',
bounds = [250, 150],
position = [450, 350],
border_visible = True
)
# Create a container and add our plots
container = OverlayPlotContainer()
container.add(zoomable_plot)
container.add(inset_plot)
return container
Overlaying images in this manner in Chaco is not well documented, but definitely possible. Firstly, how do you plot a masked image with chaco? When plotting with Plot().img_plot(), Chaco uses np.nan values as transparent pixels. For example, plotting:
img = np.eye(100)
img[img==0] = np.nan
would plot a diagonal line with a transparent background.
But how do you actually overlay this image on another image?
There are two main methods to do this.
Make two separate plots and stack them using an OverlayPlotContainer
Make one plot and plot them both in the one plot
The advantage to the second method is that both images will use the same axes. Also if you plot a second image in the same plot as the first, it keeps the same pixel aspect ratio. This means that if you plot a 100x100 image and then overlay a 50x50 image on top of it, the overlaying image will only take up 25% of the whole plot starting at (0,0).
There are some problems with the second method, so I will explain how to correct them.
When you plot multiple images on the same Plot object (using img_plot()), they will both use the same color_mapper by default. This means that both will be scaled to the same range. This may not be the required result, so you must create new color_mappers for both images.
Here's some example code with TraitsUI, which was adapted from the Qt code.
from traits.api import HasTraits, Instance
from traitsui.api import Item, View
from enable.api import ComponentEditor
from chaco.api import ArrayPlotData, Plot, ColorBar, LinearMapper, HPlotContainer, DataRange1D, ImageData
import chaco.default_colormaps
#
import numpy as np
class ImagePlot(HasTraits):
plot = Instance(HPlotContainer)
traits_view = View(
Item('plot', editor=ComponentEditor(), show_label=False), width=500, height=500, resizable=True, title="Chaco Plot")
def _plot_default(self):
bottomImage = np.reshape(np.repeat(np.linspace(0, 5, 100),100), (100,100))
topImage = np.eye(50)
topImage = topImage*np.reshape(np.repeat(np.linspace(-2, 2, 50),50), (50,50))
topImage[topImage==0] = np.nan
#
bottomImageData = ImageData()
bottomImageData.set_data(bottomImage)
#
topImageData = ImageData()
topImageData.set_data(topImage)
#
plotData = ArrayPlotData(imgData=bottomImageData, imgData2=topImageData)
plot = Plot(plotData, name='My Plot')
plot.img_plot("imgData")
plot.img_plot("imgData2")
# Note: DO NOT specify a colormap in the img_plot!
plot.aspect_ratio = 1.0
#
bottomRange = DataRange1D()
bottomRange.sources = [plotData.get_data("imgData")]
topRange = DataRange1D()
topRange.sources = [plotData.get_data("imgData2")]
plot.plots['plot0'][0].color_mapper = chaco.default_colormaps.gray(bottomRange)
plot.plots['plot1'][0].color_mapper = chaco.default_colormaps.jet(topRange)
#
colormapperBottom = plot.plots['plot0'][0].color_mapper
colormapperTop = plot.plots['plot1'][0].color_mapper
#
colorbarBottom = ColorBar(index_mapper=LinearMapper(range=colormapperBottom.range), color_mapper=colormapperBottom, orientation='v', resizable='v', width=30, padding=20)
colorbarBottom.padding_top = plot.padding_top
colorbarBottom.padding_bottom = plot.padding_bottom
#
colorbarTop = ColorBar(index_mapper=LinearMapper(range=colormapperTop.range), color_mapper=colormapperTop, orientation='v', resizable='v', width=30, padding=20)
colorbarTop.padding_top = plot.padding_top
colorbarTop.padding_bottom = plot.padding_bottom
#
container = HPlotContainer(resizable = "hv", bgcolor='transparent', fill_padding=True, padding=0)
container.spacing = 0
container.add(plot)
container.add(colorbarBottom)
container.add(colorbarTop)
#
return container
if __name__ == "__main__":
ImagePlot().configure_traits()
Related
I am trying to generate several maps with different content based on a dataframe.
So far, I have managed to display the information I needed on the interactive maps.
However, as I need to include the generated maps as figures in a report, I need to find a way to show all the markers in the figures. Problem is: some markers only are shown when I manually zoom in the area.
Is there a way to always make the markers visible?
Here is the code:
import plotly.graph_objects as go
token = open("token.mapbox_token").read() # you need your own token
df_select = df_map.loc[df_map['Budget'] == 0.9]
fig= go.Figure(go.Scattermapbox(lat=df_select.Latitude, lon=df_select.Longitude,
mode='markers', marker=go.scattermapbox.Marker(
size=df_select.Warehouse_Size*5, color = df_select.Warehouse_Size,
colorscale = ['white','red','orange','green','blue','purple'],
showscale = False)))
fig = fig.add_trace(go.Choroplethmapbox(geojson=br_geo, locations=df_select.State,
featureidkey="properties.UF_05",
z=df_select.Top10,
colorscale=["white","pink"], showscale=False,
zmin = 0,
zmax=1,
marker_opacity=0.5, marker_line_width=1
))
df_prio = df_select.loc[df_select['Prioritisated'] == 1]
fig= fig.add_trace(go.Scattermapbox(lat=df_prio.Latitude, lon=df_prio.Longitude+1,
mode='markers',
marker=go.scattermapbox.Marker(symbol = "campsite", size = 10)))
fig.update_layout(height=850,width = 870,
mapbox_style = "mapbox://styles/rafaelaveloli/ckollp2dg21dd19pmgm3vyebu",
mapbox_zoom=3.4, mapbox_center = {"lat": -14.5 ,"lon": -52},
mapbox_accesstoken = token, showlegend= False)
fig.show()
This is the result I get:
And this is one of the hidden markers that are only visible when zooming in:
How can I make it visible in the first figure, without changing the figure zoom and dimensions?
Passing allowoverlap=True to go.scattermapbox.Marker() seems to resolve the issue (link to relevant docs).
I have a chart that uses datetime for the x-axis and dollars for the y-axis in Bokeh. I want to place a logo in the upper left corner of the plot area. Bokeh documentations seems especially cryptic on placing images. This code works:
from bokeh.plotting import figure, show
#p = figure(x_range=(0,1200), y_range=(0,600))
p = figure(plot_width=1200, plot_height=600,
sizing_mode = 'scale_width',
toolbar_location='above',
x_axis_label='date',
x_axis_type='datetime',
y_axis_label='value',
)
p.image_url(x=0, y=1, url=["Shrewd_Lines_200.png"], anchor='bottom_left')
show(p)
But when I place this into my main chart, where the data is in datetime, I can not get an image to appear. Here are the key excerpts from the code within the primary chart:
plot = figure(plot_width=1200, plot_height=600,
sizing_mode = 'scale_width',
toolbar_location='above',
tools=tools,
title=plot_dict['chart_title'],
x_axis_label='date',
x_axis_type='datetime',
y_axis_label='value',
)
plot.x_range.end=plot_dict['end_data'] + extend_time
if plot_dict['start_chart'] == 'auto':
plot.x_range.start=plot_dict['start_user_data']
else:
plot.x_range.start = plot_dict['start_chart']
plot.y_range.start=0
plot.y_range.end= extend_y * plot_dict['max_value']
plot.left[0].formatter.use_scientific = False
plot.title.text_font_size = "16pt"
I have tried various approaches to plot the image such as:
plot.image_url(x=0, y=0, url=["Shrewd_Lines_200.png"], anchor='bottom_left')
plot.image_url(x=plot_dict['start_user_data'], y=10000000, url=["Shrewd_Lines_200.png"], anchor='bottom_left')
I have several labels in the chart that work quite nicely. Is there a way to specify image location and size using screen units, in the same manner as you specify locations for labels?
Thought I would post how I got this working in order to move forward. I used the following for my Bokeh plot that places my logo with some generic math to convert data space to screen space. It does this without using numpy arrays or ColumnDataSource (neither of which are bad, but trying to keep simple):
from bokeh.plotting import figure, show
# chart size and ranges need defined for dataspace location
# chart size
chart_width = 900
chart_height = 600
aspect_ratio = chart_width/chart_height
# limits of data ranges
x1 = 300
x2 = 1200
y1 = 0
y2 = 600
plot = figure(
plot_width=chart_width,
plot_height=chart_height,
x_range=(x1, x2),
y_range=(y1, y2),
sizing_mode = 'stretch_both',
x_axis_label='date',
x_axis_type='datetime',
y_axis_label='value')
plot.image_url(url=['my_image.png'], x=(.01*(x2-x1))+x1, y=(.98*(y2-y1))+y1,
w=.35*(x2-x1)/aspect_ratio, h=.1*(y2-y1), anchor="top_left")
show(plot)
Note the x_axis_type can be any type with this schema, datetime was just the issue I was dealing with.
I am learning to make color bars, and thus learning to make good use of plt.Normalize , I succeeded to make it work with scipy.stats.norm, but when tryin to use plt.norm, I found out that I have to do two things to make it work well :
defining vmin and vmax to -1.96 and 1.96 respectively,I guess that it's because they are the z value for 95% confidence interval, but I still don't precisely know why they have we have to set vmin and vmax to those values
dividing the standard deviation by sqrt( number of elements )
I don't understand why are those two points important for using the Norm. Any help is welcome ! thank you in advance
# Use the following data for this assignment:
%matplotlib notebook
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as st
df = pd.DataFrame([np.random.normal(33500,150000,3650),
np.random.normal(41000,90000,3650),
np.random.normal(41000,120000,3650),
np.random.normal(48000,55000,3650)],
index=[1992,1993,1994,1995])
new_df = pd.DataFrame()
new_df['mean'] = df.mean(axis =1)
new_df['std'] = df.std(axis =1)
new_df['se'] = df.sem(axis= 1)
new_df['C_low'] = new_df['mean'] - 1.96 * new_df['se']
new_df['C_high'] = new_df['mean'] + 1.96 * new_df['se']
from scipy.stats import norm
import numpy as np
# First, Define a figure
fig = plt.figure()
# next define its the axis and create a plot
ax = fig.add_subplot(1,1,1)
# change the ticks
xticks = np.array(new_df.index,dtype= 'str')
# remove the top and right borders
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# draw the bars in the axis
bars = ax.bar(xticks,new_df['mean'].values,
yerr = (1.96*new_df['se'],1.96*new_df['se']),
capsize= 10)
# define labels
plt.xlabel('YEARS',size = 14)
plt.ylabel('FREQUENCY',size = 14)
# Define color map
cmap = plt.cm.get_cmap('coolwarm')
# define scalar mappable
sm = plt.cm.ScalarMappable(cmap = cmap)
# draw the color bar
cbar = plt.colorbar(cmap = cmap, mappable =sm)
# define norm (will be used later to turn y to a value from 0 to 1 )
# define the events
class Cursor(object):
def __init__(self,ax):
self.ax = ax
self.lx = ax.axhline(color = 'c')
self.txt = ax.text(1,50000,'')
def mouse_movemnt(self,event):
#behaviour outside of the plot
if not event.inaxes:
return
#behavior inside the plot
y = event.ydata
self.lx.set_ydata(y)
for idx,bar in zip(new_df.index, bars):
norm = plt.Normalize(vmin =-1.96,vmax = 1.96)
mean = new_df.loc[idx,'mean']
err = new_df.loc[idx, 'se']
std = new_df.loc[idx,'std']/ np.sqrt(df.shape[1]) # not sure why we re dividing by np.sqrt(df.shape[1])
self.txt.set_text(f'Y = {round(y,2)} \n')
color_prob = norm( (mean - y)/std)
#color_prob = norm.cdf(y,loc = mean, scale = err) # you can also use this
bar.set_color( cmap(color_prob))
# connect the events to the plot
cursor = Cursor(ax)
plt.connect('motion_notify_event', cursor.mouse_movemnt)
None
After few hours of thinking, an explanation barged into my head and I was able to answer all of my inquiries,
first before answering the first point, I will answer the second one, the standard deviation was divided by the sqrt(nbr of element) because the resulting value is the standard error.
I will now move on to answering the first part:
(I can't embed images for now and I can't use latex either so I have to put links of the image instead). But here is the conclusion in advance, for all values within that confidence interval, the function (y-mean)/se will spit out a value within the range [−1.96,1.96]
answer of first part
Please, if I left something out or you have a better answer, share it with me.
I am trying to save an animation with a completely transparent background. Setting:
fig1 = (...,facecolor=(1,1,1,0))
Does not seem to work. Also, just as a side note, if you do that and view the plot then you get these weird transparency effects and lagging animation. Curious why that happens too, but mostly I just want the background to save as transparent.
If I then try:
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"facecolor": (1,1,1,0)})
Then I get an output which does not have a transparent background and makes the lines thick. Same curiosity as above as why making the figure alpha to 0 would give this effect.
Another attempt:
fig1 = (...,facecolor=(1,1,1,0))
line_ani.save(...,savefig_kwargs={"transparent": None})
Also doesn't produce a transparent background.
If I just include the facecolor in the dictionary, then it gives the undesired line thickening bug.
line_ani.save(...,savefig_kwargs={"transparent": None,"facecolor":(1,1,1,0)})
The code is below.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
from matplotlib.pyplot import figure
def update_line(num, data, line):
line.set_data(data[..., :num])
return line,
def plots():
plt.xlim(-1, 1)
plt.ylim(-1, 1)
plt.xticks([])
plt.yticks([])
plt.box()
# Since I'm calling things twice, it's convenient to define these
fs = (3,3)
inter = 100
frames = 219
lw = 0.25
alph = 0
fig1 = plt.figure(figsize=fs)
l, = plt.plot([], [],'r',linewidth =lw)
# Generate placeholder for data and set initial conditions
DAT =np.zeros((2,300))
DAT[0][0] = 0
DAT[1][0] = 1
theta=2*np.pi*(1/np.e +0.01)
# 2D Rotation Matrix
def R(x):
return [[np.cos(x),-np.sin(x)],[np.sin(x),np.cos(x)]]
# Generate the data
for i in range(len(DAT[0])):
if i < len(DAT[0])-1:
DAT[0][i+1]=DAT[0][i]*R(theta)[0][0] + DAT[1][i]*R(theta)[0][1]
DAT[1][i+1]=DAT[0][i]*R(theta)[1][0] + DAT[1][i]*R(theta)[1][1]
# Animate the data
plots()
line_ani = animation.FuncAnimation(fig1, update_line, frames, fargs=(DAT, l),
interval=inter, blit=True,repeat_delay = 2000)
plt.show()
# Save the animation
matplotlib.use("Agg")
fig1 = plt.figure(figsize=fs)
l, = plt.plot([], [],'r',linewidth = lw)
plots()
line_ani = animation.FuncAnimation(fig1, update_line, frames, fargs=(DAT, l),
interval=inter, blit=True,repeat_delay = 2000)
print("Saving animation...")
now=time.time()
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"transparent": None})
later = time.time()
print("Saved in time: ", int(later-now),"seconds")
If you run the code it should show you the animation and then save it. It also will calculate the runtime.
Setting transparent = True does the trick...
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"transparent": True})
I am working on a Jupyter Notebook and I am using the following ipywidget to set a threshold value:
Thr = widgets.IntSlider(value=-17, min=-30, max=-13, step=1, description='Threshold: ', disabled=False, continuous_update=True, orientation='horizontal', readout=True, readout_format='d')
Thr
Next, I am masking a numpy array using that value with:
import numpy.ma as ma
test= ma.masked_less_equal(S_images[0], Thr.value)
And finally I plot the result with:
plt.figure(figsize = (15,15))
plt.imshow(test[0], cmap='gray')
The ipywidget is in a different Jupyter cell than the other code so when I change the value of Thr I have to manually run again the cell where the masking and ploting takes place.
My question is: I have always seen those interactive plots where you change a parameter (in my case the ipywidget Thr) and automatically the plot gets updated.
I see that widgets.IntSlider has a continuous_update parameter which seems to be close to what I want but still cannot get the behaviour I want.
Any idea if this is doable or possible?
_ EDIT _
Starting from the comment of ac24, I am adapting the example he proposes:
from IPython.display import display, clear_output
import ipywidgets as ipy
import matplotlib.pyplot as plt
import numpy as np
# setup figure
n = 10
out = ipy.Output()
# show random mesh
def update(idx):
with out:
clear_output()
fig, ax = plt.subplots(figsize = (5,5))
h = ax.imshow(S_images[0]) # here I put my image
h.set_data(np.ma.masked_less_equal(S_images[0], slider.value)) # here I set the task to masked accordint to the `slider.value`
fig.canvas.flush_events()
fig.canvas.draw()
plt.show()
slider = ipy.IntSlider(min = 0, max = 10, orientation = 'vertical')
widget = ipy.interactive(update, idx = slider)
layout = ipy.Layout(
# display = 'flex',
# flex_flow = 'row',
# justify_content = 'space-between',
# align_items = 'center',
)
widgets = ipy.HBox(children=(slider, out), layout = layout)
display(widgets)
The example works very nice and is just what I was looking for. However, I have a small question regargind the layout. Originally I am working with 3 Images so I would like to have them displayed as follows, each one with its slider next to it to do the task: (the image below is not real, just made it up to represent what I would like)
EDIT 2
in this occasion, the question is, once I select a value in the slider, I would write to geotiff that raster. For this I am using the following code:
with rasterio.open('/Path/20190331_VV_Crop') as src:
ras_meta = src.profile
with rasterio.open('/path/Threshold.tif', 'w', **ras_meta) as dst:
dst.write(X)
However, I am not sure how to reference the numpy array in dst.write(X)
I've adapted the example I gave into a class, as you want to link a specific output and slider instance, but create multiple groups of them. Setting the layout of the output widget avoids the widget resizing all the time as you slide the slider.
from IPython.display import display, clear_output
import ipywidgets as ipy
import matplotlib.pyplot as plt
import numpy as np
# setup figure
n = 10
class SliderAndImage():
# show random mesh
def update(self, idx):
with self.out:
clear_output()
fig, ax = plt.subplots(figsize = (5,5))
h = ax.imshow(np.random.rand(n, n))
h.set_data(np.random.rand(n, n))
fig.canvas.flush_events()
fig.canvas.draw()
plt.show()
def make_slider_and_image(self):
self.out = ipy.Output(layout=ipy.Layout(width='200px', height='200px'))
slider = ipy.IntSlider(min = 0, max = 10, orientation = 'vertical')
widget = ipy.interactive(self.update, idx = slider)
layout = ipy.Layout(
# display = 'flex',
# flex_flow = 'row',
# justify_content = 'space-between',
# align_items = 'center',
)
widgets = ipy.HBox(children=(slider, self.out), layout = layout)
return widgets
children = []
for _ in range(3):
widgets = SliderAndImage()
children.append(widgets.make_slider_and_image())
display(ipy.HBox(children))