I have a sample script to generate a polar contour plot in matplotlib:
import os
import math
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.axisartist.floating_axes as floating_axes
from matplotlib.projections import PolarAxes
from mpl_toolkits.axisartist.grid_finder import FixedLocator, MaxNLocator, DictFormatter
import random
# ------------------------------------ #
def setup_arc_radial_axes(fig, rect, angle_ticks, radius_ticks, min_rad, max_rad):
tr = PolarAxes.PolarTransform()
pi = np.pi
grid_locator1 = FixedLocator([v for v, s in angle_ticks])
tick_formatter1 = DictFormatter(dict(angle_ticks))
grid_locator2 = FixedLocator([a for a, b in radius_ticks])
tick_formatter2 = DictFormatter(dict(radius_ticks))
grid_helper = floating_axes.GridHelperCurveLinear(tr,
extremes=((370.0*(pi/180.0)), (170.0*(pi/180.0)), max_rad, min_rad),
grid_locator1=grid_locator1,
grid_locator2=grid_locator2,
tick_formatter1=tick_formatter1,
tick_formatter2=tick_formatter2,
)
ax1 = floating_axes.FloatingSubplot(fig, rect, grid_helper=grid_helper)
fig.add_subplot(ax1)
ax1.grid(True)
# create a parasite axes whose transData in RA, cz
aux_ax = ax1.get_aux_axes(tr)
aux_ax.patch = ax1.patch
ax1.patch.zorder=0.9
#ax1.axis["left"].set_ticklabel_direction("+")
return ax1, aux_ax
# ------------------------------------ #
# write angle values to the plotting array
angles = []
for mic_num in range(38):
angle = float(mic_num)*(180.0/36.0)*(math.pi/180.0)+math.pi
angles.append(angle)
# ------------------------------------ #
### these are merely the ticks that appear on the plot axis
### these don't actually get plotted
angle_ticks = range(0,190,10)
angle_ticks_rads = [a*math.pi/180.0 for a in angle_ticks]
angle_ticks_rads_plus_offset = [a+math.pi for a in angle_ticks_rads]
angle_ticks_for_plot = []
for i in range(len(angle_ticks)):
angle_ticks_for_plot.append((angle_ticks_rads_plus_offset[i],r"$"+str(angle_ticks[i])+"$"))
# ------------------------------------ #
scale = 1.0
aspect = 1.50
height = 8.0
fig = plt.figure(1, figsize=(height*aspect*scale, height*scale))
fig.subplots_adjust(wspace=0.3, left=0.05, right=0.95, top=0.84)
fig.subplots_adjust()
plot_real_min = 30.0
plot_real_max = 100.0
plot_fake_min = 0.0
plot_fake_max = 5000.0
rad_tick_increment = 500.0
radius_ticks = []
for i in range(int(plot_fake_min),int(plot_fake_max)+int(rad_tick_increment),int(rad_tick_increment)):
plot_fake_val = ((i-plot_fake_min)/(plot_fake_max-plot_fake_min))*(plot_real_max-plot_real_min)+plot_real_min
radius_ticks.append((plot_fake_val, r"$"+str(i)+"$"))
ax2, aux_ax2 = setup_arc_radial_axes(fig, 111, angle_ticks_for_plot, radius_ticks, plot_real_min, plot_real_max)
azimuths = np.radians(np.linspace(0, 180, 91))
azimuths_adjusted = [ (x + math.pi) for x in azimuths ]
zeniths = np.arange(0, 5050, 50)
zeniths_adjusted = [((x-plot_fake_min)/(plot_fake_max-plot_fake_min))*(plot_real_max-plot_real_min)+plot_real_min for x in zeniths]
r, theta = np.meshgrid(zeniths_adjusted, azimuths_adjusted)
values = 90.0+5.0*np.random.random((len(azimuths), len(zeniths)))
aux_ax2.contourf(theta, r, values)
cbar = plt.colorbar(aux_ax2.contourf(theta, r, values), orientation='vertical')
cbar.ax.set_ylabel('Contour Value [Unit]', fontsize = 16)
plt.suptitle('Plot Title ', fontsize = 24, weight="bold")
plt.legend(loc=3,prop={'size':20})
plt.xlabel('Angle [deg]', fontsize=20, weight="bold")
plt.ylabel('Frequency [Hz]', fontsize=20, weight="bold")
# plt.show()
plt.savefig('test.png', dpi=100)
plt.close()
This script will generate a plot that looks something like:
My question is how can I plot with an alternate color bar scale? Is it possible to define a custom scale?
Something like a blue-white-red scale where deltas around a central value can easily be shown would be the best, something like:
You can create a custom scale, but matplotlib already has what you want. All you have to do is add an argument to contourf:
aux_ax2.contourf(theta, r, values, cmap = 'bwr')
If you don't like bwr, coolwarm and seismic are also blue to red. If you need to reverse the scale, just add _r to the colormap name. You can find more colormaps here: http://matplotlib.org/examples/color/colormaps_reference.html
I can't run your code, but I think you could solve your problem this way:
from matplotlib import pyplot as plt
import matplotlib as mpl
f = plt.figure(figsize=(5,10))
ax = f.add_axes([0.01, 0.01, 0.4, 0.95])
#here we create custom colors
cmap = mpl.colors.LinearSegmentedColormap.from_list(name='Some Data',colors=['b', 'w','w', 'r'])
cb = mpl.colorbar.ColorbarBase(ax, cmap=cmap, orientation='vertical')
cb.set_label('Some Data')
plt.show()
And if linear way is not what you are looking for here is some other types:
http://matplotlib.org/api/colors_api.html#module-matplotlib.colors
Related
I have a figure with 3 subplots, two of which share a colorbar and the third has has it's own colorbar.
I would like the colorbars to align with the vertical limits of their respective plots, and for the top two plots to have the same vertical limits.
Googling, I have found ways to do this with a single plot, but am stuck trying to make it work for my fig. My figure currently looks like this:
The code for which is as follows:
import cartopy.io.shapereader as shpreader
import cartopy.crs as ccrs
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
shpfilename = shpreader.natural_earth(resolution='50m',
category='cultural',
name='admin_0_countries')
reader = shpreader.Reader(shpfilename)
countries = reader.records()
projection = ccrs.PlateCarree()
fig = plt.figure()
axs = [plt.subplot(2, 2, x + 1, projection = projection) for x in range(2)]\
+ [plt.subplot(2, 2, (3, 4), projection = projection)]
def cmap_seg(cmap, value, k):
cmaplist = [cmap(i) for i in range(cmap.N)]
cmap = mpl.colors.LinearSegmentedColormap.from_list(
'Custom cmap', cmaplist, cmap.N)
bounds = np.linspace(0, k, k + 1)
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
color = cmap(norm(value))
return color, cmap
for country in countries:
c_name = country.attributes["SOVEREIGNT"]
country_dat = df.loc[c_name]
cmap = matplotlib.cm.get_cmap("plasma")
cmap_blues = matplotlib.cm.get_cmap("Blues")
ax_extent = [-170, 180, -65, 85]
alpha = 1.0
edgecolor = "k"
linewidth = 0.5
ax = axs[0]
value = country_dat.loc["wgi_bin"]
ax.add_geometries([country.geometry],
projection,
facecolor = cmap_seg(cmap, value, 5)[0],
alpha = alpha,
edgecolor = edgecolor,
linewidth = linewidth)
ax.set_xlabel("WGI group")
ax.set_extent(ax_extent)
ax = axs[1]
value = country_dat.loc["epi_bin"]
ax.add_geometries([country.geometry],
projection,
facecolor = cmap_seg(cmap, value, 5)[0],
alpha = alpha,
edgecolor = edgecolor,
linewidth = linewidth)
ax.set_xlabel("EPI group")
ax.set_extent(ax_extent)
ax = axs[2]
value = country_dat.loc["diff"]
ax.add_geometries([country.geometry],
projection,
facecolor = cmap_seg(cmap_blues, value, 4)[0],
alpha = alpha,
edgecolor = edgecolor,
linewidth = linewidth)
ax.set_xlabel("difference")
ax.set_extent(ax_extent)
subplot_labels = ["WGI group", "EPI group", "Metric difference"]
for i, ax in enumerate(axs):
ax.text(0.5, -0.07, subplot_labels[i], va='bottom', ha='center',
rotation='horizontal', rotation_mode='anchor',
transform=ax.transAxes)
sm = plt.cm.ScalarMappable(cmap=cmap_seg(cmap, 5, 5)[1], norm = plt.Normalize(0, 5))
sm._A = []
cb = plt.colorbar(sm, ax = axs[1], values = [1,2,3,4, 5], ticks = [1,2,3,4,5])
sm2 = plt.cm.ScalarMappable(cmap=cmap_seg(cmap_blues, 5, 4)[1], norm = plt.Normalize(0, 4))
sm2._A = []
cb2 = plt.colorbar(sm2, ax = axs[2], values = [0,1,2,3], ticks = [0,1,2,3])
Try this:
# update your code for this specific line (added shrink option)
cb = plt.colorbar(sm, ax=axs[1], values=[1,2,3,4,5], ticks=[1,2,3,4,5], shrink=0.6)
And add these lines of code towards the end:
p00 = axs[0].get_position()
p01 = axs[1].get_position()
p00_new = [p00.x0, p01.y0, p00.width, p01.height]
axs[0].set_position(p00_new)
The plot should be similar to this:
from mplsoccer.pitch import Pitch
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import kde
np.random.seed(19680801)
plt.style.use('dark_background')
fields = ['id', 'minute', 'result', 'X1', 'Y','xG','h_a','situation','season',
'shotType','X']
df=pd.read_csv('shots.csv', skipinitialspace=True, usecols=fields)
df1 = pd.DataFrame({'A':df.Y,'B':df.X} )
a=(df1.to_numpy())
x, y = a.T
k = kde.gaussian_kde(a.T)
nbins=50
xi, yi = np.mgrid[x.min():x.max():nbins*1j, y.min():y.max():nbins*1j]
zi = k(np.vstack([xi.flatten(), yi.flatten()]))
pitch = Pitch(orientation='vertical',pitch_type='metricasports', view='half',
linewidth=2, line_zorder=1,
line_color= '#94A7AE',pitch_length=105, pitch_width=68,pad_bottom=0)
fig, ax = pitch.draw()
ax.pcolormesh(xi, yi, zi.reshape(xi.shape), shading='gouraud', cmap='Reds',facecolor='black'
)
ax.set_xlim(ax.get_xlim()[::-1])
ax.yaxis.tick_right()
plt.axis('off')
plt.show()
Output Plot here
I want the only red-colored density plot, not the white rectangular background frame. How to make the frame the same as my background?
Here is an approach using a colormap with an "under" color of 'none'. By setting vmin to a cut-off value, the cells with a lower value will get the "under" color ('none' stands for fully transparent). To get an idea of the values, temporarily a colorbar can be added. The values depend strongly on the extension of the x and y values (the integral of the kde is 1, so over a small domain the values need to be high enough).
from mplsoccer.pitch import Pitch
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import kde
from copy import copy
np.random.seed(19680801)
plt.style.use('dark_background')
# first create some random toy data roughly mimicking the given plot
x = np.random.randn(100, 20).cumsum(axis=0).flatten()
y = np.random.randn(100, 20).cumsum(axis=0).flatten()
x = x * 0.04 + 0.5
y = y * 0.01 + 0.9
k = kde.gaussian_kde([x, y])
nbins = 50
xi, yi = np.mgrid[x.min():x.max():nbins * 1j, y.min():y.max():nbins * 1j]
zi = k(np.vstack([xi.flatten(), yi.flatten()]))
pitch = Pitch(orientation='vertical', pitch_type='metricasports', view='half',
linewidth=2, line_zorder=1,
line_color='#94A7AE', pitch_length=105, pitch_width=68, pad_bottom=0)
fig, ax = pitch.draw()
cmap = copy(plt.get_cmap('Reds'))
cmap.set_under('none')
pmesh = ax.pcolormesh(xi, yi, zi.reshape(xi.shape), shading='gouraud', cmap=cmap, vmin=5, facecolor='black')
# fig.colorbar(pmesh, ax=ax) # to temporarily get an idea of the values
ax.invert_xaxis()
ax.yaxis.tick_right()
plt.axis('off')
plt.show()
I'm trying to plot the confidence ellipse for uniformly distributed points. When plotting the ellipse and the scatter plot using Matplotlib, I find that a portion of the ellipse is clipped by the subplot. I tried implementing other solutions suggested here on SO, given here, here, here and here. but am not able to correct the displayed plot.
How do I change the size of this subplot in order to correctly and completely display the ellipse?
Code for generating ellipse: multidimensional confidence intervals
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
def eigsorted(cov):
vals, vecs = np.linalg.eigh(cov)
order = vals.argsort()[::-1]
return vals[order], vecs[:,order]
nstd = 2
fig = plt.figure(1, figsize=(12, 5))
#ax = plt.subplots(111)
ax = fig.add_subplot(111, aspect='equal')
test1 = np.random.uniform(40, 60, 1000)
test2 = np.random.uniform(100, 120, 1000)
cov = np.cov(test1, test2)
vals, vecs = eigsorted(cov)
theta = np.degrees(np.arctan2(*vecs[:,0][::-1]))
w, h = 2 * nstd * np.sqrt(vals)
ell = Ellipse(xy=(np.mean(test1), np.mean(test2)),
width=w, height=h,
angle=theta, color='black')
ell.set_facecolor('none')
ax.add_artist(ell)
plt.scatter(test1, test2)
plt.show()
As #ImportanceOfBeingEarnest rightly commented, a patch needed to be added for displaying the ellipse correctly. Updating the code below for future reference of others.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
def eigsorted(cov):
vals, vecs = np.linalg.eigh(cov)
order = vals.argsort()[::-1]
return vals[order], vecs[:,order]
nstd = 2
fig = plt.figure(1, figsize=(12, 5))
#ax = plt.subplots(111)
ax = fig.add_subplot(111, aspect='equal')
test1 = np.random.uniform(40, 60, 1000)
test2 = np.random.uniform(100, 120, 1000)
cov = np.cov(test1, test2)
vals, vecs = eigsorted(cov)
theta = np.degrees(np.arctan2(*vecs[:,0][::-1]))
w, h = 2 * nstd * np.sqrt(vals)
ell = Ellipse(xy=(np.mean(test1), np.mean(test2)),
width=w, height=h,
angle=theta, color='black')
ell.set_facecolor('none')
#Corrected code for displaying ellipse correctly
ax.add_patch(ell)
plt.scatter(test1, test2)
plt.show()
I have some sample code to make a polar contour plot:
import numpy as np
import matplotlib.pyplot as plt
azimuths = np.radians(np.linspace(0, 180, 90))
zeniths = np.arange(50, 5050, 50)
r, theta = np.meshgrid(zeniths, azimuths)
values = 90.0+5.0*np.random.random((len(azimuths), len(zeniths)))
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.set_theta_zero_location("W")
pp = plt.contourf(theta, r, values, label='tmp')
cbar = plt.colorbar(pp, orientation='vertical')
cbar.ax.set_ylabel('scale label')
plt.show()
which gives me something like:
...but I would like something more like this:
...with space in the middle, and only showing 0 to 180 degrees. Does anyone know of a convenient way to do this?
I'm not sure how convenient this is, but here's a hackable solution (taken from here):
import numpy as np
import mpl_toolkits.axisartist.floating_axes as floating_axes
from matplotlib.projections import PolarAxes
from mpl_toolkits.axisartist.grid_finder import FixedLocator, MaxNLocator, \
DictFormatter
import matplotlib.pyplot as plt
tr = PolarAxes.PolarTransform()
degree_ticks = lambda d: (d*np.pi/180, "%d$^\\circ$"%(360-d))
angle_ticks = map(degree_ticks, np.linspace(180, 360, 5))
grid_locator1 = FixedLocator([v for v, s in angle_ticks])
tick_formatter1 = DictFormatter(dict(angle_ticks))
tick_formatter2 = DictFormatter(dict(zip(np.linspace(1000, 6000, 6),
map(str, np.linspace(0, 5000, 6)))))
grid_locator2 = MaxNLocator(5)
gh = floating_axes.GridHelperCurveLinear(tr,
extremes=(2*np.pi, np.pi, 1000, 6000),
grid_locator1=grid_locator1,
grid_locator2=grid_locator2,
tick_formatter1=tick_formatter1,
tick_formatter2=tick_formatter2)
fig = plt.figure()
ax = floating_axes.FloatingSubplot(fig, 111, grid_helper=gh)
fig.add_subplot(ax)
azimuths = np.radians(np.linspace(180, 360, 90)) # added 180 degrees
zeniths = np.arange(1050, 6050, 50) # added 1000
r, theta = np.meshgrid(zeniths, azimuths)
values = 90.0+5.0*np.random.random((len(azimuths), len(zeniths)))
aux_ax = ax.get_aux_axes(tr)
aux_ax.patch = ax.patch
ax.patch.zorder = 0.9
aux_ax.contourf(theta, r, values) # use aux_ax instead of ax
plt.show()
Note that (in order to get the space near the origin), you'll need to shift all your data points by 1000 in the radius direction and by pi in the theta direction (to get the lower hemisphere).
This yields:
In this example the color is correlative to the radius of each bar. How would one add a colorbar to this plot?
My code mimics a "rose diagram" projection which is essentially a bar chart on a polar projection.
here is a part of it:
angle = radians(10.)
patches = radians(360.)/angle
theta = np.arange(0,radians(360.),angle)
count = [0]*patches
for i, item in enumerate(some_array_of_azimuth_directions):
temp = int((item - item%angle)/angle)
count[temp] += 1
width = angle * np.ones(patches)
# force square figure and square axes looks better for polar, IMO
fig = plt.figure(figsize=(8,8))
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
rmax = max(count) + 1
ax.set_rlim(0,rmax)
ax.set_theta_offset(np.pi/2)
ax.set_thetagrids(np.arange(0,360,10))
ax.set_theta_direction(-1)
# project strike distribution as histogram bars
bars = ax.bar(theta, count, width=width)
r_values = []
colors = []
for r,bar in zip(count, bars):
r_values.append(r/float(max(count)))
colors.append(cm.jet(r_values[-1], alpha=0.5))
bar.set_facecolor(colors[-1])
bar.set_edgecolor('grey')
bar.set_alpha(0.5)
# Add colorbar, make sure to specify tick locations to match desired ticklabels
colorlist = []
r_values.sort()
values = []
for val in r_values:
if val not in values:
values.append(val*float(max(count)))
color = cm.jet(val, alpha=0.5)
if color not in colorlist:
colorlist.append(color)
cpt = mpl.colors.ListedColormap(colorlist)
bounds = range(max(count)+1)
norm = mpl.colors.BoundaryNorm(values, cpt.N-1)
cax = fig.add_axes([0.97, 0.3, 0.03, 0.4])
cb = mpl.colorbar.ColorbarBase(cax, cmap=cpt,
norm=norm,
boundaries=bounds,
# Make the length of each extension
# the same as the length of the
# interior colors:
extendfrac='auto',
ticks=[bounds[i] for i in range(0, len(bounds), 2)],
#ticks=bounds,
spacing='uniform')
and here is the resulting plot:
As you can see, the colorbar is not quite right. If you look closely, between 16 and 17, there is a color missing (darker orange) and according to the colorbar the yellows reach a value of 15, which is not true in the rose diagram (or the data).
I have played around with the code so much and I just can't figure out how to normalize the colorbar correctly.
The easiest way is to use a PatchCollection and pass in your "z" (i.e. the values you want to color by) as the array kwarg.
As a simple example:
import itertools
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
import numpy as np
def main():
fig = plt.figure()
ax = fig.add_subplot(111, projection='polar')
x = np.radians(np.arange(0, 360, 10))
y = np.random.random(x.size)
z = np.random.random(y.size)
cmap = plt.get_cmap('cool')
coll = colored_bar(x, y, z, ax=ax, width=np.radians(10), cmap=cmap)
fig.colorbar(coll)
ax.set_yticks([0.5, 1.0])
plt.show()
def colored_bar(left, height, z=None, width=0.8, bottom=0, ax=None, **kwargs):
if ax is None:
ax = plt.gca()
width = itertools.cycle(np.atleast_1d(width))
bottom = itertools.cycle(np.atleast_1d(bottom))
rects = []
for x, y, w, h in zip(left, bottom, width, height):
rects.append(Rectangle((x,y), w, h))
coll = PatchCollection(rects, array=z, **kwargs)
ax.add_collection(coll)
ax.autoscale()
return coll
if __name__ == '__main__':
main()
If you want a discrete color map, it's easiest to just specify the number of intervals you'd like when you call plt.get_cmap. For example, in the code above, if you replace the line cmap = plt.get_cmap('cool') with:
cmap = plt.get_cmap('cool', 5)
Then you'll get a discrete colormap with 5 intervals. (Alternately, you could pass in the ListedColormap that you created in your example.)
If you want a "full-featured" rose diagram function, you might do something like this:
import itertools
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
import numpy as np
def main():
azi = np.random.normal(20, 30, 100)
z = np.cos(np.radians(azi + 45))
plt.figure(figsize=(5,6))
plt.subplot(111, projection='polar')
coll = rose(azi, z=z, bidirectional=True)
plt.xticks(np.radians(range(0, 360, 45)),
['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'])
plt.colorbar(coll, orientation='horizontal')
plt.xlabel('A rose diagram colored by a second variable')
plt.rgrids(range(5, 20, 5), angle=290)
plt.show()
def rose(azimuths, z=None, ax=None, bins=30, bidirectional=False,
color_by=np.mean, **kwargs):
"""Create a "rose" diagram (a.k.a. circular histogram).
Parameters:
-----------
azimuths: sequence of numbers
The observed azimuths in degrees.
z: sequence of numbers (optional)
A second, co-located variable to color the plotted rectangles by.
ax: a matplotlib Axes (optional)
The axes to plot on. Defaults to the current axes.
bins: int or sequence of numbers (optional)
The number of bins or a sequence of bin edges to use.
bidirectional: boolean (optional)
Whether or not to treat the observed azimuths as bi-directional
measurements (i.e. if True, 0 and 180 are identical).
color_by: function or string (optional)
A function to reduce the binned z values with. Alternately, if the
string "count" is passed in, the displayed bars will be colored by
their y-value (the number of azimuths measurements in that bin).
Additional keyword arguments are passed on to PatchCollection.
Returns:
--------
A matplotlib PatchCollection
"""
azimuths = np.asanyarray(azimuths)
if color_by == 'count':
z = np.ones_like(azimuths)
color_by = np.sum
if ax is None:
ax = plt.gca()
ax.set_theta_direction(-1)
ax.set_theta_offset(np.radians(90))
if bidirectional:
other = azimuths + 180
azimuths = np.concatenate([azimuths, other])
if z is not None:
z = np.concatenate([z, z])
# Convert to 0-360, in case negative or >360 azimuths are passed in.
azimuths[azimuths > 360] -= 360
azimuths[azimuths < 0] += 360
counts, edges = np.histogram(azimuths, range=[0, 360], bins=bins)
if z is not None:
idx = np.digitize(azimuths, edges)
z = np.array([color_by(z[idx == i]) for i in range(1, idx.max() + 1)])
z = np.ma.masked_invalid(z)
edges = np.radians(edges)
coll = colored_bar(edges[:-1], counts, z=z, width=np.diff(edges),
ax=ax, **kwargs)
return coll
def colored_bar(left, height, z=None, width=0.8, bottom=0, ax=None, **kwargs):
"""A bar plot colored by a scalar sequence."""
if ax is None:
ax = plt.gca()
width = itertools.cycle(np.atleast_1d(width))
bottom = itertools.cycle(np.atleast_1d(bottom))
rects = []
for x, y, h, w in zip(left, bottom, height, width):
rects.append(Rectangle((x,y), w, h))
coll = PatchCollection(rects, array=z, **kwargs)
ax.add_collection(coll)
ax.autoscale()
return coll
if __name__ == '__main__':
main()