Jun-24-2019, 01:23 PM
Hi,
I have a Python script that generates a 3D plot from a Excel .csv file with three columns (Phi, Theta and Power). I'm pretty new with Python. Now I have to modify the code so that Phi have intervalls up to 360 and Theta up to 180. I have tried couple of times now, and cant see the problem. Basically...the logic og Theta and Phi have to be oposite. otherwies the code is working fine. Pretty sure I have to make a change in "deg read" or "def interpolate_x". I have tried to just switch the names og "Theta" and "Phi", but that does not help.
Any help would be strongly appreciated.
This is the code:
I have a Python script that generates a 3D plot from a Excel .csv file with three columns (Phi, Theta and Power). I'm pretty new with Python. Now I have to modify the code so that Phi have intervalls up to 360 and Theta up to 180. I have tried couple of times now, and cant see the problem. Basically...the logic og Theta and Phi have to be oposite. otherwies the code is working fine. Pretty sure I have to make a change in "deg read" or "def interpolate_x". I have tried to just switch the names og "Theta" and "Phi", but that does not help.
Any help would be strongly appreciated.
This is the code:
import pandas as pd import click import matplotlib #matplotlib.use('TkAgg') # This import registers the 3D projection, but is otherwise unused. from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt import numpy as np from scipy.interpolate import griddata import os import ast class PythonLiteralOption(click.Option): def type_cast_value(self, ctx, value): try: return ast.literal_eval(value) except: raise click.BadParameter(value) def dbm_to_W(db): """ Convert decibell-milliWatts to Watts :param db: :return: """ return 10**(db/10)/1000 def read(filename): df = pd.read_csv(filename).sort_values(by = ['Theta','Phi']) phi = df['Phi'].to_numpy(dtype='float') theta = df['Theta'].to_numpy(dtype='float') r = df['Power'].to_numpy(dtype='float') return [r,theta,phi] def interpolate_x(x, theta, phi, theta_resolution = 5, phi_resolution = 5, close_gap=True): """ Apply bicubic interpolation. Assumptions: theta and phi are in degrees in limits according to ISO conventions Theta and phi values cover their respective domain, if this assumption doesn't hold, extrapolation would occur. :param x: data values :param theta: elevation values, must cover the 0-180 range :param phi: azimuth values, must cover the 0-360 range :param theta_resolution: output elevation resolution :param phi_resolution: output azimuth resolution :param close_gap: boolean, when true sets X[360] to X[0] :return: [X, THETA, PHI] - interpolated values of X and the THETA-PHI interpolation grid """ THETA, PHI = np.mgrid[0.:180.000005:theta_resolution, 0:360.000005:phi_resolution] # X = interpolator(theta_range, phi_range) points=np.array([theta.ravel(), phi.ravel()]).T X = griddata(points, x.ravel(), (THETA, PHI), method='cubic') if close_gap: X[PHI == 360.] = X[PHI == 0.] return [X, THETA, PHI] def arrange_levels(r,theta,phi): """ This generates contour lines to be consumed by matplotlib surface plot. We rely on data being sorted by theta here. :param r: :param theta: :param phi: :return: """ #TODO: maybe support different number of point in each contour # using masked arrays? Sub below # # R = [] # THETA = [] # PHI = [] # i = 0 # begin = 0 # # Not the most efficient implementation, but makes clear what's happening # # for i in range(len(theta)): # n = len(theta) # while True: # t = curtheta = theta[i] # while curtheta==t and i<n: # t = theta[i] # i+=1; # THETA.append(theta[begin:i]) # PHI.append(phi[begin:i]) # R.append(r[begin:i]) # if i==n: # break # # return (np.array(R), # np.array(THETA), # np.array(PHI)) u = len(np.unique(theta)) return [x.reshape((u,-1)) for x in (r, theta, phi)] def sphere2cart(r, theta, phi): """ Convert spherical polar coordinates to Cartesian coordinates according to ISO. See https://en.wikipedia.org/wiki/Spherical_coordinate_system#Conventions :param r: :param theta: :param phi: :return: """ t = np.deg2rad(theta) p = np.deg2rad(phi) x = r*np.sin(t)*np.cos(p) y = r*np.sin(t)*np.sin(p) z = r*np.cos(t) return (x,y,z) def _visuv(r,theta,phi): """ A utility I used to get insight into data :param r: :param theta: :param phi: :return: """ fig = plt.figure() ax = fig.add_subplot(111, projection='3d') # Create the mesh in polar coordinates and compute corresponding Z. # Plot the surface. ax.contour(theta,phi,r) ax.plot_surface(theta,phi,r)#, cmap=plt.cm.YlGnBu_r) ax.set_xlabel('theta') ax.set_ylabel('phi') ax.set_zlabel('r') plt.show() def set_view_deg(ax,azim, elev): ax.view_init(azim=azim, elev = elev) ax.get_proj() ax.stale = True ax.figure.canvas.draw_idle() def axisEqual3D(ax): """ Helper to get correct aspect ratio in 3d plots https://stackoverflow.com/a/19248731/3791466 :param ax: :return: """ extents = np.array([getattr(ax, 'get_{}lim'.format(dim))() for dim in 'xyz']) sz = extents[:,1] - extents[:,0] centers = np.mean(extents, axis=1) maxsize = max(abs(sz)) r = maxsize/2 # ax.auto_scale_xyz(*np.column_stack((centers - r, centers + r))) for ctr, dim in zip(centers, 'xyz'): getattr(ax, 'set_{}lim'.format(dim))(ctr - r, ctr + r) def add_3d_axes(ax): x, y, z = np.zeros((3, 3)) u, v, w = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) ax.quiver(x, y, z, u, v, w, arrow_length_ratio=0.1) for coords, text in zip((u,v,w),"xyz"): ax.text(*coords, text )# , zdir=None, **kwargs) def save_standard_TI_notation(path, fig,ax,prefix=''): standard_rotations = { "90_0":(0,0), "0_0":(-90,90), "90_270":(-90,0), "90_90":(90,0), "90_180":(180,0), "180_0":(90,-90) } for r in standard_rotations: azim = standard_rotations[r][0] elev = standard_rotations[r][1] set_view_deg(ax,azim=azim,elev=elev) pythonFriendlyPath = path.replace('"\"', '"/"') filename = r+".png" filename = os.path.join(pythonFriendlyPath, filename) fig.savefig(filename) #fig.savefig(r+".png") def visualize(path, X,Y,Z,V,cmap_limits = (0,0), prefix="", show=True, show_wireframe =False): if cmap_limits=='auto' or cmap_limits is None or cmap_limits[0]==cmap_limits[1]: (slope,offset) = get_autoscale(V,0,1) bounds=(V.min(),V.max()) else: # cast to float to be safe bounds = np.array(cmap_limits,dtype='float') (slope,offset) = get_autoscale(bounds,0,1) fig = plt.figure() ax = fig.add_subplot(111, projection='3d')#,proj_type = 'ortho') plt.tight_layout() plt.axis('scaled') cmap=plt.get_cmap('jet') patchcolors = cmap(rescale(V,slope,offset), alpha=1) surf = ax.plot_surface(X, Y, Z, facecolors=patchcolors,rstride=1, cstride=1, linewidth=0.5)#, cmap=plt.cm.YlGnBu_r) if show_wireframe: wf = ax.plot_wireframe(X, Y, Z,rstride=1, cstride=1, linewidth=0.5, color='k')#, cmap=plt.cm.YlGnBu_r) add_3d_axes(ax) # set_view_deg(ax,azim=30,elev=90) ax.set_xlim((-1,1)) ax.set_ylim((-1,1)) ax.set_zlim((-1,1)) plt.axis('off') # cbax = fig.add_subplot(122) cbax = plt.axes((0.8,0.5, 0.05, 0.45)) norm = matplotlib.colors.Normalize(vmin=bounds[0], vmax=bounds[1]) cb1 = matplotlib.colorbar.ColorbarBase(cbax, cmap=cmap, norm=norm, orientation="vertical") save_standard_TI_notation(path, fig,ax,prefix=prefix) if show: plt.show() # axisEqual3D(ax) # import vispy.plot as vp # from vispy import color # # fig = vp.Fig() # ax = fig[0,0] # ax.surface(Z) def rescale(x,slope, offset): return x*slope+offset def get_autoscale(x, lower, upper): xmin = x.min() xmax = x.max() slope = (upper-lower)/(xmax-xmin) offset = upper - slope*xmax return (slope, offset) @click.command(context_settings=dict(help_option_names=['-h', '--help'])) @click.option('--dbm_to_watts', is_flag=True, help="if present will convert dbm to watts") @click.option('--disable_interpolation', is_flag=True , help="if present will disable interpolation with 5 degree resulution") @click.option('--show/--no-show', default=True, help="whether to show the interactive window") # @click.option('--cmap_limits', cls=PythonLiteralOption, default='"auto"')#,help="preset colormap limits") @click.option('--cmap_limits', type=(float, float), default=(0,0))#,help="preset colormap limits") @click.argument('filename', nargs=-1)#,help="paths to csv files to process") @click.argument('path', nargs=1)#,help="path to where file should be saved") def main(filename, path, dbm_to_watts = False, disable_interpolation=False, cmap_limits='auto', show=True): """ :param filename: :param path: :param dbm_to_watts: :param disable_interpolation: :param cmap_limits: Limits for the colormap. Use 'auto' or a tuple of lower and upper bounds in data units. :return: """ for f in filename: data = read(f) if dbm_to_watts: db = data[0] #save dbm to use in the legend adn coloring data[0] = dbm_to_W(db) #convert to Watts arranged = arrange_levels(*data) if disable_interpolation: interpolated = arranged else: interpolated= interpolate_x(*arranged) slope, offset= get_autoscale(interpolated[0], lower=0, upper=1) r = rescale(interpolated[0], slope, offset) xyz = sphere2cart(r,interpolated[1],interpolated[2]) prefix = os.path.splitext(f) visualize(path, *xyz,interpolated[0],cmap_limits=cmap_limits,prefix=prefix, show=show) if __name__=='__main__': main()