I am creating a 3D sphere with 2D grid by selected latitudes and longitudes, which show the cartesian coordinates. This grid represent the key points to draw 3D sphere. Than I am creating X,Y,Z 3D coordinates values from these cartesian coordinates with well known formula. Drawn sphere is shown in the attached picture. I am using a numpy named datatype as
np3d = np.dtype([('X', np.float), ('Y', np.float), ('Z', np.float)])
for 3D coordinates and
np2d = np.dtype([('L', np.float), ('B', np.float)])
for latitude/longitude grid. My python code is
import numpy as np, math
import pandas as pd
from matplotlib import pyplot as plt, ticker, patches, font_manager as fmng
from matplotlib.widgets import Cursor, MultiCursor
from pathlib import Path
from datetime import datetime
fig3d = plt.figure('3D Sphere', figsize=(9.5,9.5))
fig3d.subplots_adjust(left=0.04, bottom=0.07, top=0.97, right=0.97, wspace=0, hspace=0)
prmgraf = dict(axis="both", direction='in',top=True, right=True)
ax3 = fig3d.add_subplot(111, projection='3d')
ax3.grid(False)
ax3.minorticks_on()
ax3.yaxis.set_minor_locator(ticker.AutoMinorLocator(5))
ax3.xaxis.set_minor_locator(ticker.AutoMinorLocator(5))
ax3.zaxis.set_minor_locator(ticker.AutoMinorLocator(5))
ax3.tick_params(which='major', length=4, **prmgraf)
ax3.tick_params(which='minor', length=3, **prmgraf)
ax3.set_xlabel('Axis X')
ax3.set_ylabel('Axis Y')
ax3.set_zlabel('Axis Z')
ax3.set_xlim(-15.0, 15.0)
ax3.set_ylim(-15.0, 15.0)
ax3.set_zlim(-15.0, 15.0)
# azim elev
ax3.view_init(0., 180.)
# ---------------------------- settings ------------------------------
nLat = np.vstack(np.radians(np.arange(-65., 70., 5.)))
nLon = np.radians(np.arange(-180., 185., 5.))
np3d = np.dtype([('X', np.float), ('Y', np.float), ('Z', np.float)])
np2d = np.dtype([('L', np.float), ('B', np.float)])
LatN, LonN = (len(nLat), len(nLon))
SphrRadius = 14.5
#2D cartesian coordinates
pSphr = np.zeros(shape=(LatN, LonN), dtype=np2d)
pSphr['L'] = nLat
pSphr['B'] = nLon
#3D sphere coordinates
Spher = np.zeros(shape=(LatN, LonN), dtype=np3d)
Spher['X'] = SphrRadius*np.cos(pSphr['L'])*np.sin(pSphr['B'])
Spher['Y'] = SphrRadius*np.sin(pSphr['L'])
Spher['Z'] = SphrRadius*np.cos(pSphr['L'])*np.cos(pSphr['B'])
# draw sphere latitudes
for i in range(LatN):
kx = Spher[i,:]['X']
ky = Spher[i,:]['Y']
kz = Spher[i,:]['Z']
ax3.plot3D(kx, ky, kz, c='k',lw=0.5)
plt.show()
Z values of the sphere are changing between -15.0 and 15.0. I want to select ONLY POSITIVE Z VALUES in the "Spher variable". In other words, I want to draw half of the sphere in the Z direction. How can i do that in named datatype? Thanks for now to the friends who will answer.
I found solution as
Spher[(Spher['Z'] < 0.)] = np.nan
But this is not the solution I expected. I want to select all points into a new variable as
ZPositive = Spher[(Spher['Z'] < 0.)]
But in this way, 2D data structure change into 1D data.
Related
I have a Seaborn displot with a hued variable:
For each hued variable, I want to extract the mode of the density estimate and then plot each hue variable versus its mode, like so:
How do I do this?
You can use scipy.stats.gaussian_kde to create the density estimation function. And then call that function on an array of x-values to calculate its maximum.
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
df = pd.DataFrame({'x': np.random.normal(0.001, 1, 1300).cumsum() + 30,
'hue': np.repeat(np.arange(0.08, 0.20001, 0.01), 100).round(2)})
g = sns.displot(df, x='x', hue='hue', palette='turbo', kind='kde', fill=True, height=6, aspect=1.5)
plt.show()
from scipy.stats import gaussian_kde
from matplotlib.cm import ScalarMappable
fig, ax = plt.subplots(figsize=(10, 6))
hues = df['hue'].unique()
num_hues = len(hues)
colors = sns.color_palette('turbo', num_hues)
xmin, xmax = df['x'].min(), df['x'].max()
xs = np.linspace(xmin, xmax, 500)
for hue, color in zip(hues, colors):
data = df[df['hue'] == hue]['x'].values
kde = gaussian_kde(data)
mode_index = np.argmax(kde(xs))
mode_x = xs[mode_index]
sns.scatterplot(x=[hue], y=[mode_x], color=color, s=50, ax=ax)
cmap = sns.color_palette('turbo', as_cmap=True)
norm = plt.Normalize(hues.min(), hues.max())
plt.colorbar(ScalarMappable(cmap=cmap, norm=norm), ax=ax, ticks=hues)
plt.show()
Here is another approach, extracting the kde curves. It uses the legend of the kde plot to get the correspondence between the curves and the hue values. sns.kdeplot is the axes-level function used by sns.displot(kind='kde'). fill=False creates lines instead of filled polygons for the curves, for which the values are easier to extract. (ax1.fill_between can fill the curves during a second pass). The x and y axes of the second plot are switched to align the x-axes of both plots.
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
df = pd.DataFrame({'x': np.random.normal(0.007, 0.1, 1300).cumsum() + 30,
'hue': np.repeat(np.arange(0.08, 0.20001, 0.01), 100).round(2)})
fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(12, 10), sharex=True)
sns.kdeplot(data=df, x='x', hue='hue', palette='turbo', fill=False, ax=ax1)
hues = [float(txt.get_text()) for txt in ax1.legend_.get_texts()]
ax2.set_yticks(hues)
ax2.set_ylabel('hue')
for hue, line in zip(hues, ax1.lines[::-1]):
color = line.get_color()
x = line.get_xdata()
y = line.get_ydata()
ax1.fill_between(x, y, color=color, alpha=0.3)
mode_ind = np.argmax(y)
mode_x = x[mode_ind]
sns.scatterplot(x=[mode_x], y=hue, color=color, s=50, ax=ax2)
sns.despine()
plt.tight_layout()
plt.show()
I have a geodataframe with LINESTRING Z geometries:
TimeUTC
Latitude
Longitude
AGL
geometry
0
2021-06-16 00:34:04+00:00
42.8354
-70.9196
82.2
LINESTRING Z (42.83541343273769 -70.91961015378617 82.2, 42.83541343273769 -70.91961015378617 82.2)
1
2021-06-14 13:32:18+00:00
42.8467
-70.8192
66.3
LINESTRING Z (42.84674080836037 -70.81919357049679 66.3, 42.84674080836037 -70.81919357049679 66.3)
2
2021-06-18 23:56:05+00:00
43.0788
-70.7541
0.9
LINESTRING Z (43.07882882269921 -70.75414567194126 0.9, 43.07884601143309 -70.75416286067514 0, 43.07885174101104 -70.75416286067514 0, 43.07884028185512 -70.75415713109717 0, 43.07884601143309 -70.75414567194126 0, 43.07884601143309 -70.75414567194126 0)
I can plot the component points using pydeck's ScatterplotLayer using the raw
(not geo) dataframe but I need to also plot the full, smooth, track.
I've tried this:
layers = [
pdk.Layer(
type = "PathLayer",
data=tracks,
get_path="geometry",
width_scale=20,
width_min_pixels=5,
get_width=5,
get_color=[180, 0, 200, 140],
pickable=True,
),
]
view_state = pdk.ViewState(
latitude=gdf_polygon.centroid.x,
longitude=gdf_polygon.centroid.y,
zoom=6,
min_zoom=5,
max_zoom=15,
pitch=40.5,
bearing=-27.36)
r = pdk.Deck(layers=[layers], initial_view_state=view_state)
return(r)
Which silently fails. Try as I might, I cannot find a way to convert the
LINESTRING Z's (and I can do without the Z component if need be) to an object
that pydeck will accept.
I found a way to extract the info needed from GeoPandas and make it work in pydeck. You just need to apply a function that extracts the coordinates from the shapely geometries as a list. Here is a fully reproducible example:
import shapely
import numpy as np
import pandas as pd
import pydeck as pdk
import geopandas as gpd
linestring_a = shapely.geometry.LineString([[0,1,2],
[3,4,5],
[6,7,8]])
linestring_b = shapely.geometry.LineString([[7,15,1],
[8,14,2],
[9,13,3]])
multilinestring = shapely.geometry.MultiLineString([[[10,11,2],
[13,14,5],
[16,17,8]],
[[19,10,11],
[12,15,4],
[10,13,0]]])
gdf = gpd.GeoDataFrame({'id':[1,2,3],
'geometry':[linestring_a,
linestring_b,
multilinestring],
'color_hex':['#ed1c24',
'#faa61a',
'#ffe800']})
# Function that transforms a hex string into an RGB tuple.
def hex_to_rgb(h):
h = h.lstrip("#")
return tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))
# Applying the HEX-to-RGB function above
gdf['color_rgb'] = gdf['color_hex'].apply(hex_to_rgb)
# Function that extracts the 2d list of coordinates from an input geometry
def my_geom_coord_extractor(input_geom):
if (input_geom is None) or (input_geom is np.nan):
return []
else:
if input_geom.type[:len('multi')].lower() == 'multi':
full_coord_list = []
for geom_part in input_geom.geoms:
geom_part_2d_coords = [[coord[0],coord[1]] for coord in list(geom_part.coords)]
full_coord_list.append(geom_part_2d_coords)
else:
full_coord_list = [[coord[0],coord[1]] for coord in list(input_geom.coords)]
return full_coord_list
# Applying the coordinate list extractor to the dataframe
gdf['coord_list'] = gdf['geometry'].apply(my_geom_coord_extractor)
gdf_polygon = gdf.unary_union.convex_hull
# Establishing the default view for the pydeck output
view_state = pdk.ViewState(latitude=gdf_polygon.centroid.coords[0][1],
longitude=gdf_polygon.centroid.coords[0][0],
zoom=4)
# Creating the pydeck layer
layer = pdk.Layer(
type="PathLayer",
data=gdf,
pickable=True,
get_color='color_rgb',
width_scale=20,
width_min_pixels=2,
get_path="coord_list",
get_width=5,
)
# Finalizing the pydeck output
r = pdk.Deck(layers=[layer], initial_view_state=view_state, tooltip={"text": "{id}"})
r.to_html("path_layer.html")
Here's the output it yields:
Big caveat
It seems like pydeck isn't able to deal with MultiLineString geometries. Notice how, in the example above, my original dataframe had 3 geometries, but only 2 lines were drawn in the screenshot.
I have a seaborn heatmap plot as shown below:
import pandas as pd
from matplotlib import pyplot as plt
import seaborn as sns
results_changed = [['equal','equal','smaller','smaller or equal','greater or equal'],
['equal','equal','smaller','smaller','greater or equal'],
['greater or equal','equal','smaller or equal','smaller','smaller'],
['equal','smaller or equal','greater or equal','greater or equal','equal'],
['equal','equal','smaller','equal','equal']]
index = ['axc123abc', 'org567def', 'cf5010wer', 'cm1235ert', 'ext447tyu']
columns = ['axc123abc', 'org567def', 'cf5010wer', 'cm1235ert', 'ext447tyu']
# create a dataframe
res_df = pd.DataFrame(results_changed, columns, index)
#construct dictionary from ordered list
category_order = ['greater', 'greater or equal', 'equal', 'smaller or equal', 'smaller']
value_to_int = {j:i for i,j in enumerate(category_order)}
n = len(value_to_int)
# discrete colormap (n samples from a given cmap)
cmap = sns.color_palette("flare", n)
ax = sns.heatmap(res_df.replace(value_to_int), cmap=cmap, vmin=0, vmax=n)
#modify colorbar:
colorbar = ax.collections[0].colorbar
colorbar.set_ticks([0.5 + i for i in range(n)])
colorbar.set_ticklabels(category_order)
ax.tick_params(right=True, top=True, labelright=True, labeltop=True)
plt.tight_layout()
plt.show()
I would like to make it more readable by adding tick descriptions for axes from each side of the figure (not just bottom and left). I've managed to do that by adding one more line of code as shown below:
import pandas as pd
from matplotlib import pyplot as plt
import seaborn as sns
results_changed = [['equal','equal','smaller','smaller or equal','greater or equal'],
['equal','equal','smaller','smaller','greater or equal'],
['greater or equal','equal','smaller or equal','smaller','smaller'],
['equal','smaller or equal','greater or equal','greater or equal','equal'],
['equal','equal','smaller','equal','equal']]
index = ['axc123abc', 'org567def', 'cf5010wer', 'cm1235ert', 'ext447tyu']
columns = ['axc123abc', 'org567def', 'cf5010wer', 'cm1235ert', 'ext447tyu']
# create a dataframe
res_df = pd.DataFrame(results_changed, columns, index)
#construct dictionary from ordered list
category_order = ['greater', 'greater or equal', 'equal', 'smaller or equal', 'smaller']
value_to_int = {j:i for i,j in enumerate(category_order)}
n = len(value_to_int)
# discrete colormap (n samples from a given cmap)
cmap = sns.color_palette("flare", n)
ax = sns.heatmap(res_df.replace(value_to_int), cmap=cmap, vmin=0, vmax=n)
#modify colorbar:
colorbar = ax.collections[0].colorbar
colorbar.set_ticks([0.5 + i for i in range(n)])
colorbar.set_ticklabels(category_order)
# newly added code line
ax.tick_params(right=True, top=True, labelright=True, labeltop=True)
plt.tight_layout()
plt.show()
The problem is, these added tick descriptions are not fully visible (due to their length). I'm not sure how to rotate ticks descriptions on particular axes. I would like to have ticks on the upper part positioned vertically (just as those at the bottom are) and ticks on the right side positioned horizontally (just as those on the left are). How can I achieve this and fit the heatmap, axes' tick descriptions and the color bar nicely in the figure? I would appreciate any suggestions.
You can do it like this:
import pandas as pd
from matplotlib import pyplot as plt
import seaborn as sns
results_changed = [['equal','equal','smaller','smaller or equal','greater or equal'],
['equal','equal','smaller','smaller','greater or equal'],
['greater or equal','equal','smaller or equal','smaller','smaller'],
['equal','smaller or equal','greater or equal','greater or equal','equal'],
['equal','equal','smaller','equal','equal']]
index = ['axc123abc', 'org567def', 'cf5010wer', 'cm1235ert', 'ext447tyu']
columns = ['axc123abc', 'org567def', 'cf5010wer', 'cm1235ert', 'ext447tyu']
# create a dataframe
res_df = pd.DataFrame(results_changed, columns, index)
# construct dictionary from ordered list
category_order = ['greater', 'greater or equal', 'equal', 'smaller or equal', 'smaller']
value_to_int = {j: i for i,j in enumerate(category_order)}
n = len(value_to_int)
# discrete colormap (n samples from a given cmap)
cmap = sns.color_palette("flare", n)
ax = sns.heatmap(res_df.replace(value_to_int), cmap=cmap, vmin=0, vmax=n, cbar_kws=dict(pad=0.2))
# modify colorbar:
colorbar = ax.collections[0].colorbar
colorbar.set_ticks([0.5 + i for i in range(n)])
colorbar.set_ticklabels(category_order)
# newly added code line
ax.tick_params(right=True, top=True, labelright=True, labeltop=True)
plt.xticks(rotation=90)
plt.yticks(rotation=0)
plt.tight_layout()
plt.show()
Output:
I am trying to implement FRST on python to detect centroids of elliptical objects (e.g. cells in microscopy images), but my implementation does not find seed points (more or less center points) of elliptical objects. This effort comes from duplicating FRST from Segmentation of Overlapping Elliptical Objects in Silhouette Images (https://ieeexplore.ieee.org/document/7300433). I don't know why I have these artifacts. An interesting thing is that I see these patterns (crosses) all in the same direction per object. Any point in the right direction to generate the same result as in the paper (just to find the seed points) will be most welcome.
Original Paper: A Fast Radial Symmetry Transform for Detecting Points of Interest by Loy and Zelinsky (ECCV 2002)
I have also tried the pre-existing python package for FRST: https://pypi.org/project/frst/. This somehow results in the same artifacts. Weird.
First image: Original Image
Second image: Sobel-operated Image
Third image: Magnitude Projection Image
Fourth image: Magnitude Projection Image with positively affected pixels only
Fifth image: FRST'd image: end-product with original image overlaid (shadowed)
Sixth image: FRST'd image by the pre-existing python package with original image overlaid (shadowed).
from scipy.ndimage import gaussian_filter
import numpy as np
from scipy.signal import convolve
# Get orientation projection image
def get_proj_img(image, radius):
workingDims = tuple((e + 2*radius) for e in image.shape)
h,w = image.shape
ori_img = np.zeros(workingDims) # Orientation Projection Image
mag_img = np.zeros(workingDims) # Magnitutde Projection Image
# Kenels for the sobel operator
a1 = np.matrix([1, 2, 1])
a2 = np.matrix([-1, 0, 1])
Kx = a1.T * a2
Ky = a2.T * a1
# Apply the Sobel operator
sobel_x = convolve(image, Kx)
sobel_y = convolve(image, Ky)
sobel_norms = np.hypot(sobel_x, sobel_y)
# Distances to afpx, afpy (affected pixels)
dist_afpx = np.multiply(np.divide(sobel_x, sobel_norms, out = np.zeros(sobel_x.shape), where = sobel_norms!=0), radius)
dist_afpx = np.round(dist_afpx).astype(int)
dist_afpy = np.multiply(np.divide(sobel_y, sobel_norms, out = np.zeros(sobel_y.shape), where = sobel_norms!=0), radius)
dist_afpy = np.round(dist_afpy).astype(int)
for cords, sobel_norm in np.ndenumerate(sobel_norms):
i, j = cords
pos_aff_pix = (i+dist_afpx[i,j], j+dist_afpy[i,j])
neg_aff_pix = (i-dist_afpx[i,j], j-dist_afpy[i,j])
ori_img[pos_aff_pix] += 1
ori_img[neg_aff_pix] -= 1
mag_img[pos_aff_pix] += sobel_norm
mag_img[neg_aff_pix] -= sobel_norm
ori_img = ori_img[:h, :w]
mag_img = mag_img[:h, :w]
print ("Did it go back to the original image size? ")
print (ori_img.shape == image.shape)
# try normalizing ori and mag img
return ori_img, mag_img
def get_sn(ori_img, mag_img, radius, kn, alpha):
ori_img_limited = np.minimum(ori_img, kn)
fn = np.multiply(np.divide(mag_img,kn), np.power((np.absolute(ori_img_limited)/kn), alpha))
# convolute fn with gaussian filter.
sn = gaussian_filter(fn, 0.25*radius)
return sn
def do_frst(image, radius, kn, alpha, ksize = 3):
ori_img, mag_img = get_proj_img(image, radius)
sn = get_sn(ori_img, mag_img, radius, kn, alpha)
return sn
Parameters:
radius = 50
kn = 10
alpha = 2
beta = 0
stdfactor = 0.25
I wish to appear a figure (and certain text) as if they are printed on a page of an open book. Is it possible to transform an jpg image programmatically or in matplotlib to have such an effect?
You can use a background axis along with an open source book image to do something like this,
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.8])
ax2 = fig.add_axes([0.2, 0.3, 0.25, 0.3])
#Plot page from a book
im = plt.imread("./book_page.jpg")
implot = ax1.imshow(im, origin='lower')
# Plot a graph and set background to transparent
x = np.linspace(0,4.*np.pi,40)
y = np.sin(x)
ax2.plot(x,y,'-ro',alpha=0.5)
ax2.set_ylim([-1.1,1.1])
ax2.patch.set_alpha(0.0)
from matplotlib import rc
rc('text', usetex=True)
margin = im.shape[0]*0.075
ytext = im.shape[1]/2.+10
ax1.text(margin, ytext, "The following text is an example")
ax1.text(margin, 90, "Figure 1. Showing a sine function")
plt.show()
Which looks like this,
where I used the following book image.
UPDATE: Added non-affine transformation based on scikit-image warp example, but with Maxwell distribution. The solution saves the matplotlib line as an image in order to apply a pointwise transform. Mapping for vector graphics may be possible but I think this will be more complicated...
import numpy as np
import matplotlib.pyplot as plt
def maxwellian_transform_image(image):
from skimage.transform import PiecewiseAffineTransform, warp
rows, cols = image.shape[0], image.shape[1]
src_cols = np.linspace(0, cols, 20)
src_rows = np.linspace(0, rows, 10)
src_rows, src_cols = np.meshgrid(src_rows, src_cols)
src = np.dstack([src_cols.flat, src_rows.flat])[0]
# add maxwellian to row coordinates
x = np.linspace(0, 3., src.shape[0])
dst_rows = src[:, 1] + (np.sqrt(2/np.pi)*x**2 * np.exp(-x**2/2)) * 50
dst_cols = src[:, 0]
dst_rows *= 1.5
dst_rows -= 1.0 * 50
dst = np.vstack([dst_cols, dst_rows]).T
tform = PiecewiseAffineTransform()
tform.estimate(src, dst)
out_rows = image.shape[0] - 1.5 * 50
out_cols = cols
out = warp(image, tform, output_shape=(out_rows, out_cols))
return out
#Create the new figure
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
#Plot page from a book
im = plt.imread("./book_page.jpg")
implot = ax.imshow(im, origin='lower')
# Plot and save graph as image, will need some manipulation of location
temp, at = plt.subplots()
margin = im.shape[0]*0.1
x = np.linspace(margin,im.shape[0]/2.,40)
y = im.shape[1]/3. + 0.1*im.shape[1]*np.sin(12.*np.pi*x/im.shape[0])
at.plot(x,y,'-ro',alpha=0.5)
temp.savefig("lineplot.png",transparent=True)
#Read in plot as an image and apply transform
plot = plt.imread("./lineplot.png")
out = maxwellian_transform_image(plot)
ax.imshow(out, extent=[0,im.shape[1],0,im.shape[0]])
plt.show()
The figure now looks like,