Search code examples
pythonscipy3dinterpolation

Python 3D Surface Interpolation from 2D Simulation Data


I’m working with a 3D dataset in Python where some (x,y) points create frame-like structures, some of them with multiple z-values per (x,y) pair, which seems to lead to a problem with griddata.

The provided code produces the interpolation between 2 data sets as a working example, and then tries to interpolate 2 data sets, where one has multiple z values. The data and the interpolation are plotted for each case. I also provide an image with a marking, what seems to create the problem afaik.

How can I interpolate such data with griddata or is there another way?

simulation data and corresponding 3D interpolation plot

Code contains part of the original (simulation) data.

import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import distance
from scipy.interpolate import griddata
from scipy.ndimage import gaussian_filter
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib.colors import Normalize
from matplotlib import cm
import matplotlib.tri as mtri
from scipy.interpolate import LinearNDInterpolator


x1 = np.array([
    0, 0.08317108166803, 0.16393703317322, 0.24010197356808, 0.31189968667059,
    0.37964309047995, 0.44362962064596, 0.50416941327588, 0.56154833462750, 0.61603270573008,
    0.66785467575344, 0.71722870327800, 0.76434124363645, 0.80936126105512, 0.85243882934910,
    0.89371020366228, 0.93330023252176, 0.97132031393031, 1.0078733278234, 1.04305186588065,
    1.07694164659622, 1.10962198351184, 1.14116475234496, 1.17163490787683, 1.20109277181813,
    1.22959493653164, 1.25719327772516, 1.28393541084346, 1.30986533211137, 1.33502459428484,
    1.3594517119059, 1.38318253432743, 1.40625054167016, 1.428687092237, 1.45052182005806,
    1.47178239513572, 1.4924946731886, 1.51268282564772, 1.53236959683959, 1.55157657215197,
    1.5703240685486, 1.58863120287582, 1.60651600994447, 1.62399550071956, 1.64108576853907,
    1.65780207082065, 1.67415889980995, 1.69017008507776, 1.70584884180465, 1.72120783285127,
    1.73625905724844, 1.75101389043042, 1.76548311976239, 1.77967698639756, 1.79360519288652,
    1.80727689980216, 1.8207008202846, 1.83388518878035, 1.84683777315861, 1.85956584789141,
    1.87207616663999, 1.88437496210868, 1.89646789288332, 1.90835992552183, 1.92005539714572,
    1.9315578245162, 1.94286977773435, 1.95399269320514, 1.96492669014037, 1.97567031873621,
    1.98622026624029, 1.99657105652974, 2.00671480587649, 2.01664080378909, 2.02633512036696,
    2.03578034488985, 2.04495553215686, 2.05383630364206, 2.0623951776226, 2.0706018164916,
    2.07842481734728, 2.0858329876037, 2.09279775917234, 2.09929633971377, 2.10531538227125,
    2.11085508513429, 2.11593338459342, 2.12058937638755, 2.12488319044411, 2.12889284005344,
    2.13270923022591, 2.13642524129613
])


z1 = np.array([
    28.6619989743589, 28.6049608580482, 28.5450559014246, 28.4794835365082, 28.4092022241029,
    28.3350387336261, 28.2576380039013, 28.1775367384564, 28.0951654495223, 28.0108638015799,
    27.9249204615456, 27.8375597971889, 27.7489735572062, 27.6593178994861, 27.5687237423079,
    27.4773041964674, 27.385152945665, 27.2923533725037, 27.1989741902862, 27.1050785554844,
    27.0107190614389, 26.9159392616942, 26.8207798998579, 26.7252795524418, 26.6294706791138,
    26.5333790187747, 26.4370286448011, 26.3404423792824, 26.2436415876413, 26.1466433615791,
    26.0494627200553, 25.9521139974702, 25.8546109832136, 25.7569659319296, 25.6591888403289,
    25.5612891089313, 25.4632757574251, 25.3651575801692, 25.2669423848755, 25.168636129068,
    25.0702443614344, 24.9717723691753, 24.8732252294088, 24.7746078436806, 24.6759249761952,
    24.5771812831842, 24.4783811991822, 24.3795278208977, 24.2806239242195, 24.1816722476486,
    24.082675412506, 23.9836359405196, 23.8845562687947, 23.7854387639056, 23.6862857298263,
    23.5870993764416, 23.4878813512533, 23.3886330916743, 23.2893560024275, 23.1900514136945,
    23.0907205842034, 22.9913647072391, 22.8919849096176, 22.792582243255, 22.6931576855678,
    22.5937118769204, 22.4942453558146, 22.3947585791552, 22.2952518558259, 22.1957253129467,
    22.0961789025235, 21.9966124171542, 21.8970253898987, 21.7974170708687, 21.6977864181452,
    21.5981324310853, 21.4984538542985, 21.398749114135, 21.2990163991982, 21.1992542481573,
    21.0994617682189, 20.9996381351822, 20.8997827371874, 20.799896002592, 20.6999792732694,
    20.6000347502029, 20.5000657435458, 20.4000764848403, 20.3000713432661, 20.2000540343164,
    20.1000287433225, 20
])

y1 = np.full(len(x1),1)

x2 = np.array([
    0, 0.09970480868419, 0.19934990930324, 0.29892560671855, 0.39845053029212,
    0.49791294847979, 0.59729126792395, 0.69654894215390, 0.79562230931157,
    0.89441829468223, 0.99278184799703, 1.09041596346573, 1.18680564093363,
    1.2811247216627, 1.37173821384615, 1.45615592046124, 1.53140709459726,
    1.59481447147833, 1.64540118865725, 1.68420020117436, 1.7139567901805,
    1.73739377080012, 1.75669514508293, 1.77341533375185, 1.78855241978336,
    1.80270054725639, 1.81619925811863, 1.82923458834867, 1.84190868011591,
    1.8542787909028, 1.86637895717989, 1.87823123253782, 1.8898513429009,
    1.90125155489232, 1.91244211394041, 1.92343198232874, 1.93422925599876,
    1.94484137307261, 1.95527523060825, 1.96553726952681, 1.97563352932633,
    1.98556968830044, 1.99535109412568, 2.00498278811171, 2.01446952528177,
    2.02381579222306, 2.03302582656997, 2.04210365316528, 2.05105308938297,
    2.05987776623044, 2.06858114998522, 2.07716655334668, 2.08563714669058,
    2.09399597192344, 2.1022459544216, 2.11038991079032, 2.11843055346434,
    2.12637049464471, 2.13421224945851, 2.14195824744614, 2.14961083302462,
    2.15717226465044, 2.16464472287464, 2.17203031834828, 2.17933109740207,
    2.18654904729014, 2.19368610417827, 2.20074416242255, 2.20772507771543,
    2.21463066429355, 2.2214626950629, 2.22822290393084, 2.23491298448034,
    2.24153458547506, 2.24808930743357, 2.25457870003345, 2.26100425466765,
    2.26736739519304, 2.27366947406393, 2.27991176845626, 2.28609547194039,
    2.29222169050455, 2.29829141992375, 2.30430552303668, 2.31026470050494,
    2.31616945318094, 2.32202003758885, 2.32781640900641, 2.33355814983544,
    2.33924438051412, 2.3448736508916, 2.35044381228348, 2.3559518778997,
    2.36139385612463, 2.36676455738366, 2.37205741259143, 2.37726426201264,
    2.38237517331566, 2.38737831946074, 2.39225993069978, 2.39700436076639,
    2.40159431859356, 2.40601130725183, 2.41023630806784, 2.4142507588751,
    2.418037990112, 2.42158487083293, 2.42488372545427, 2.42793436504128,
    2.43074593332474, 2.4333379706335, 2.43574001568792, 2.4379889754186,
    2.44012113735539, 2.44214574160271
])

z2 = np.array([
    30.0078688964104, 30.0065512265173, 30.0029356360187, 29.9976735322442,
    29.9915262693748, 29.9844424862537, 29.9762659464609, 29.9667364364951,
    29.9554600427241, 29.9419800400481, 29.9256478232344, 29.9054159211507,
    29.8799561681087, 29.8476996253689, 29.8061730864421, 29.7532731986494,
    29.6880451094337, 29.6112448542866, 29.5253946922475, 29.4335819554567,
    29.3384411867613, 29.2415312959595, 29.1437049711797, 29.04539962879,
    28.9468401079325, 28.8481308935213, 28.7493306654707, 28.6504694225423,
    28.5515612207417, 28.4526148172972, 28.3536354145385, 28.2546261943038,
    28.1555895272291, 28.0565273761794, 27.9574414945533, 27.8583335076939,
    27.7592047062979, 27.6600561309973, 27.5608887508238, 27.4617034796921,
    27.3625011864055, 27.2632827022544, 27.1640488269094, 27.0648003331593,
    26.9655379708194, 26.866262470073, 26.7669745291472, 26.6676746328858,
    26.5683632230419, 26.4690407437848, 26.3697076224236, 26.2703642715209,
    26.171011090825, 26.0716484692661, 25.9722767866544, 25.8728964148864,
    25.7735077188227, 25.6741110570318, 25.5747068020388, 25.4752952472731,
    25.3758766209457, 25.2764511425401, 25.1770190239312, 25.077580470411,
    24.978135681488, 24.8786848515835, 24.7792281708969, 24.6797658262875,
    24.5802980016905, 24.4808248780774, 24.381346633632, 24.2818634440495,
    24.1823754825859, 24.0828829198801, 23.9833859238618, 23.8838846597107,
    23.7843792895138, 23.6848699718101, 23.5853568615186, 23.4858401098541,
    23.3863198605794, 23.2867961336504, 23.1872689742316, 23.0877384678003,
    22.9882046876333, 22.8886676931036, 22.7891275276662, 22.6895842161987,
    22.5900377616683, 22.4904881410033, 22.3909353002525, 22.2913791491932,
    22.1918194461756, 22.0922558825952, 21.9926883788735, 21.8931167722048,
    21.7935407990426, 21.6939600883707, 21.5943741599893, 21.4947824294466,
    21.395184222527, 21.2955788018895, 21.1959654064542, 21.0963434196656,
    20.9967131851386, 20.8970742537356, 20.7974262724062, 20.6977693400472,
    20.5981040167542, 20.4984312619284, 20.3987523065558, 20.2990684973495,
    20.1993811617366, 20.099691467103, 20
])

y2 = np.full(len(x2),2)

x3 = np.array([
    0, 0.106091997247487, 0.212282862868987, 0.318600341722176, 0.425123104761527,
    0.531901352872626, 0.638980673349671, 0.746408888895399, 0.85423835573243,
    0.962522013543275, 1.07131093710695, 1.18064439993891, 1.29054534660613,
    1.40102082494537, 1.51205952335725, 1.62363164712265, 1.73568405272677,
    1.84814589283292, 1.96093277164309, 2.07395098084042, 2.18706544796369,
    2.30006049798178, 2.41251892919309, 2.52351045392648, 2.63096976610101,
    2.73100121086301, 2.77148016441521, 2.78453458174498, 2.79186010794094,
    2.79751816975908, 2.80236248394865, 2.80670132496914, 2.81067659404222,
    2.81436810315714, 2.81782835677761, 2.81838473210957, 2.82109652791738,
    2.82420427153188, 2.82717868109766, 2.83004342751941, 2.83281945446213,
    2.83552519787279, 2.83817680575457, 2.84078819902162, 2.84337124785453,
    2.84593589783908, 2.84849035993836, 2.85104128587469, 2.85359396537556,
    2.85615250287907, 2.85872000627528, 2.86129873724824, 2.86389026304064,
    2.86649557135293, 2.86911518641933, 2.8717492484006, 2.87439759958004,
    2.87705983816952, 2.87973537526801, 2.88242347197052, 2.88512327706806,
    2.88783385231766, 2.88845703622116, 2.89055419723002, 2.89328326484414,
    2.89601997738632, 2.89876323695298, 2.90151193463858, 2.90426495815636,
    2.90702119863042, 2.90977955571619, 2.91253894330014, 2.91529829288703,
    2.91805655711182, 2.92081271230053, 2.92356576021251, 2.92631472933472,
    2.92905867580467, 2.93179668334584, 2.93452786251032, 2.93725134958514,
    2.93965292016822, 2.93996630533669, 2.94267191310587, 2.94536737736262,
    2.94805192150669, 2.95072478659992, 2.95338522921948, 2.9560325199586,
    2.95866594104728, 2.96128478419988, 2.96388834746919, 2.96647593163284,
    2.96904683568692, 2.97160035145282, 2.97413575724706, 2.97425125556441,
    2.97665231021929, 2.97914923756346, 2.98162572566976, 2.98408090824307,
    2.98651385151949, 2.98892353757412, 2.99130884415201, 2.99366851923393,
    2.99600114768388, 2.9962606601005, 2.99830510879971, 3.00057851901249,
    3.00281914620488, 3.00502428120326, 3.00719053828585, 3.00931351650196,
    3.00952011645174, 3.01138721018009, 3.0134029388599, 3.01534737564675,
    3.01699921085196, 3.01719889804888, 3.01892089400833, 3.02044973125651,
    3.02078939388317, 3.02167377283328, 3.02228920892799, 3.02239824880744
])

z3 = np.array([
    30.1186241174416, 30.1184849219899, 30.1171034471136, 30.1152951272103, 30.1133655615441,
    30.1113195993005, 30.1091433108655, 30.1068210776114, 30.1043270725803, 30.1016326866963,
    30.0986949871154, 30.095463219881, 30.091866695563, 30.087823561571, 30.0832240264785,
    30.0779411584852, 30.0718042824719, 30.064598064254, 30.0560271138372, 30.0456642379577,
    30.0328664792409, 30.0166381634173, 29.995383512023, 29.9666311419869, 29.9269062544625,
    29.8724446142301, 20, 20.0992866487574, 20.1987778658839, 20.2983155533319, 20.3978803414887,
    20.4974615256815, 20.5970538814448, 20.6966543922422, 20.796260963899, 29.8010645620936,
    20.8958720196992, 20.9954865830927, 21.0951039613303, 21.1947236485541, 21.2943452750121,
    21.3939685837807, 21.493593496968, 21.5932201164118, 21.6928485704969, 21.7924790313127,
    21.8921116969745, 21.9917467869875, 22.0913845424964, 22.1910252330904, 22.2906691240923,
    22.3903164843267, 22.4899675841224, 22.5896226941457, 22.6892820856006, 22.7889460266206,
    22.888614783746, 22.988288620861, 23.0879678014928, 23.1876525873797, 23.287343240002,
    23.3870400197103, 29.7142868959217, 23.486743187404, 23.5864530033367, 23.6861697290352,
    23.7858936263979, 23.885624958565, 23.9853639878029, 24.0851109788725, 24.1848661985415,
    24.2846299172733, 24.3844024085183, 24.4841839497909, 24.5839748236319, 24.6837753181654,
    24.7835857287758, 24.8834063591202, 24.9832375223667, 25.0830795430067, 25.1829327578276,
    29.6171448515155, 25.2827975190689, 25.3826741941813, 25.4825631716602, 25.5824648589361,
    25.6823796912861, 25.7823081276471, 25.8822506637417, 25.9822078278468, 26.0821801933113,
    26.1821683788585, 26.2821730607998, 26.3821949758609, 26.482234934283, 26.5822938276285,
    29.5148759694037, 26.6823726493158, 26.7824724970099, 26.8825946040959, 26.9827403478161,
    27.0829112850487, 27.1831091758242, 27.283336026885, 27.3835941282532, 27.4838861281617,
    29.4108704451581, 27.5842150845333, 27.6845846449453, 27.7849990464475, 27.8854632367103,
    27.9859831360428, 28.0865659498792, 29.3067361736195, 28.1872205245328, 28.2879579643369,
    28.3887923780716, 29.2030641097122, 28.4897421297855, 28.5908314496745, 28.6920929845482,
    29.0999759013086, 28.7935707828626, 28.997430828688, 28.8953250739844
])

y3 = np.full(len(x3),3)

def filter_data_by_indices(data, indices_to_keep):
 
    # Convert 1-based indices to 0-based indices for Python indexing
    indices_to_keep = [i - 1 for i in indices_to_keep]
    
    # Filter the data by the specified indices
    filtered_data = [data[i] for i in indices_to_keep if 0 <= i < len(data)]
    return filtered_data


def mirror_data(data):
    mirrored_data = []
    for x,y,z in data:
        
        # Combine positive and negative r values
        x_combined = np.concatenate([-x[::-1], x])
        z_combined = np.concatenate( [z[::-1], z ])-20 #offset of sim raw data
        y_combined = np.full_like(x_combined, y[0])
        mirrored_data.append((x_combined, y_combined, z_combined))
    return mirrored_data


def nearest_neighbor_sort(data):
    sorted_data = []
    for x, y, z in data:
        # Stack x and z to form (x, z) pairs
        xz_pairs = np.column_stack((x, z))

        # Nearest neighbor sorting
        sorted_indices = [0]  # Start with the first point
        remaining_indices = list(range(1, len(xz_pairs)))

        while remaining_indices:
            last_index = sorted_indices[-1]
            distances = [
                distance.euclidean(xz_pairs[last_index], xz_pairs[i]) for i in remaining_indices
            ]
            nearest_index = remaining_indices[np.argmin(distances)]
            sorted_indices.append(nearest_index)
            remaining_indices.remove(nearest_index)

        # Apply the sorted indices to x and z, y remains unchanged
        x_sorted = x[sorted_indices]
        z_sorted = z[sorted_indices]
        sorted_data.append((x_sorted, y, z_sorted))
    return sorted_data


def plot_data(filtered_data):
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')

    all_x, all_y, all_z = [], [], []

    for x, y, z in filtered_data:
        # log_t = np.log10(t)
        
        all_x.extend(x)
        all_y.extend(y)
        all_z.extend(z)

        ax.plot(x, y, z, color='black', alpha=0.8, linewidth=1.5)
       
    plt.show()
    
    # Interpolate surface
    X, Y, Z = interpolate_surface(all_x, all_y, all_z)

    X_flat, Y_flat, Z_flat = X.flatten(), Y.flatten(), Z.flatten()
    triangles = mtri.Triangulation(X_flat, Y_flat)

    norm = Normalize(vmin=min(Y_flat), vmax=max(Y_flat))
    cmap = cm.gnuplot
    colors = cmap(norm(Y_flat))

    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    # Build Poly3DCollection
    poly3d = []
    for tri in triangles.triangles:
        poly3d.append([(X_flat[tri[0]], Y_flat[tri[0]], Z_flat[tri[0]]),
                       (X_flat[tri[1]], Y_flat[tri[1]], Z_flat[tri[1]]),
                       (X_flat[tri[2]], Y_flat[tri[2]], Z_flat[tri[2]])])

    poly_collection = Poly3DCollection(poly3d, facecolors=colors[triangles.triangles].mean(axis=1),
                                        edgecolors='black', linewidths=0.06, alpha=1)
    ax.add_collection3d(poly_collection)

    plt.show()


def interpolate_surface(all_r, all_log_t, all_z, num_points=100, sigma=1):
  
  
    r_vals = np.linspace(min(all_r), max(all_r), num_points)
    t_vals = np.linspace(min(all_log_t), max(all_log_t), num_points)
    R, T = np.meshgrid(r_vals, t_vals)

    points = np.vstack((all_r, all_log_t)).T
    values = np.array(all_z)
    Z = griddata(points, values, (R, T), method='linear') # 'cubic' or 'nearest'
      
    ## Interpolate the data with linearNDInterpolator 
    # interpolator = LinearNDInterpolator(points, values)
    # Z = interpolator(np.array([R.flatten(), T.flatten()]).T).reshape(R.shape)
    
    Z = np.nan_to_num(Z, nan=0)
    # Z = gaussian_filter(Z, sigma=sigma)
    return R, T, Z


data = [(x1,y1,z1),(x2,y2,z2),(x3,y3,z3)]


# plot first 2 data sets
filtered_data = filter_data_by_indices(data, [1,2])

sorted_data = nearest_neighbor_sort(filtered_data)

mirrored_data = mirror_data(sorted_data)

plot_data(mirrored_data)

# plot last 2 data sets (here interpolation is wrong)

filtered_data = filter_data_by_indices(data, [2,3])

sorted_data = nearest_neighbor_sort(filtered_data)

mirrored_data = mirror_data(sorted_data)

plot_data(mirrored_data)

Solution

  • Don't interpolate in a rectilinear parameter space. Instead, you need to

    1. Transform to a semipolar (cylindrical) coordinate space with y as linear, and xz radius and xz angle as the other axes. If I had to guess a safe x-z origin, it would be (0, 0).
    2. Interpolate on that.
    3. Prior to graphing, transform back to xyz.

    This is the safest "dumb" solution that will fit your data. If your data have a different hidden parameter space that produce unique, somewhat uniform dependent coordinates then you should use that instead.

    I also don't understand you're making your own triangle mesh. It causes problems even with a well-parameterized dataset. Just use plot_surface instead:

    import typing
    
    import matplotlib
    import numpy as np
    import matplotlib.pyplot as plt
    import scipy
    from scipy.spatial.distance import euclidean
    from mpl_toolkits.mplot3d.art3d import Poly3DCollection
    
    
    def mirror_data(
        data: list[tuple[np.ndarray, np.ndarray, np.ndarray]],
    ) -> list[np.ndarray]:
        mirrored_data = []
        for x, y, z in data:
            # Combine positive and negative r values
            x_combined = np.concatenate([-x[::-1], x])
            z_combined = np.concatenate([z[::-1], z]) - 20  # offset of sim raw data
            y_combined = np.full_like(x_combined, y[0])
            mirrored_data.append(np.array((x_combined, y_combined, z_combined)))
        return mirrored_data
    
    
    def nearest_neighbor_sort(data: typing.Sequence[np.ndarray]) -> list[np.ndarray]:
        sorted_data = []
        for x, y, z in data:
            # Stack x and z to form (x, z) pairs
            xz_pairs = np.column_stack((x, z))
    
            # Nearest neighbor sorting
            sorted_indices = [0]  # Start with the first point
            remaining_indices = list(range(1, len(xz_pairs)))
    
            while remaining_indices:
                last_index = sorted_indices[-1]
                distances = [
                    euclidean(xz_pairs[last_index], xz_pairs[i]) for i in remaining_indices
                ]
                nearest_index = remaining_indices[np.argmin(distances)]
                sorted_indices.append(nearest_index)
                remaining_indices.remove(nearest_index)
    
            # Apply the sorted indices to x and z, y remains unchanged
            x_sorted = x[sorted_indices]
            z_sorted = z[sorted_indices]
            sorted_data.append(np.array((x_sorted, y, z_sorted)))
        return sorted_data
    
    
    def plot_data(filtered_data: list[np.ndarray]) -> None:
        fig, ax = plt.subplots(subplot_kw={'projection': '3d'})
        ax.set_xlabel('R')
        ax.set_ylabel('T')
        ax.set_zlabel('Z')
        all_x, all_y, all_z = np.concatenate(filtered_data, axis=-1)
    
        for x, y, z in filtered_data:
            # log_t = np.log10(t)
            ax.plot(x, y, z, color='black', alpha=0.8, linewidth=1.5)
    
        # Interpolate surface
        X, Y, Z = interpolate_surface(all_x, all_y, all_z, num_points=50)
    
        fig, ax = plt.subplots(subplot_kw={'projection': '3d'})
        ax.set_xlabel('R')
        ax.set_ylabel('T')
        ax.set_zlabel('Z')
        ax.plot_surface(X, Y, Z, cmap=matplotlib.cm.plasma)
    
    
    def interpolate_surface(
        all_r: np.ndarray,
        all_log_t: np.ndarray,
        all_z: np.ndarray,
        num_points: int = 100,
        sigma: float = 1.,
        gaussian: bool = False,
    ) -> tuple[
        np.ndarray,  # R
        np.ndarray,  # T
        np.ndarray,  # Z
    ]:
        # r_vals = np.linspace(all_r.min(), all_r.max(), num_points)
        t_vals = np.linspace(all_log_t.min(), all_log_t.max(), num_points)
        phi_vals = np.linspace(0, np.pi, num_points)
        P, T = np.meshgrid(phi_vals, t_vals)
    
        # t is kept as linear. Transform r,z to a cylindrical system.
        rmax = np.abs(all_r).max()
        zmax = np.abs(all_z).max()
        rscale = all_r/rmax
        zscale = all_z/zmax
        radius = np.hypot(zscale, rscale)
        angle = np.atan2(zscale, rscale)
    
        points = np.stack((angle, all_log_t), axis=-1)
        grid_radius = scipy.interpolate.griddata(points, radius, (P, T), method='linear')  # 'cubic' or 'nearest'
        if gaussian:
            grid_radius = scipy.ndimage.gaussian_filter(grid_radius, sigma=sigma)
    
        # Transform from cylindrical angle (P), T, grid_radius to R, T, Z
        R = rmax * grid_radius * np.cos(P)
        Z = zmax * grid_radius * np.sin(P)
    
        return R, T, Z
    
    
    def demo() -> None:
        x1 = np.array((
            0, 0.08317108166803, 0.16393703317322, 0.24010197356808, 0.31189968667059,
            0.37964309047995, 0.44362962064596, 0.50416941327588, 0.56154833462750, 0.61603270573008,
            0.66785467575344, 0.71722870327800, 0.76434124363645, 0.80936126105512, 0.85243882934910,
            0.89371020366228, 0.93330023252176, 0.97132031393031, 1.0078733278234, 1.04305186588065,
            1.07694164659622, 1.10962198351184, 1.14116475234496, 1.17163490787683, 1.20109277181813,
            1.22959493653164, 1.25719327772516, 1.28393541084346, 1.30986533211137, 1.33502459428484,
            1.3594517119059, 1.38318253432743, 1.40625054167016, 1.428687092237, 1.45052182005806,
            1.47178239513572, 1.4924946731886, 1.51268282564772, 1.53236959683959, 1.55157657215197,
            1.5703240685486, 1.58863120287582, 1.60651600994447, 1.62399550071956, 1.64108576853907,
            1.65780207082065, 1.67415889980995, 1.69017008507776, 1.70584884180465, 1.72120783285127,
            1.73625905724844, 1.75101389043042, 1.76548311976239, 1.77967698639756, 1.79360519288652,
            1.80727689980216, 1.8207008202846, 1.83388518878035, 1.84683777315861, 1.85956584789141,
            1.87207616663999, 1.88437496210868, 1.89646789288332, 1.90835992552183, 1.92005539714572,
            1.9315578245162, 1.94286977773435, 1.95399269320514, 1.96492669014037, 1.97567031873621,
            1.98622026624029, 1.99657105652974, 2.00671480587649, 2.01664080378909, 2.02633512036696,
            2.03578034488985, 2.04495553215686, 2.05383630364206, 2.0623951776226, 2.0706018164916,
            2.07842481734728, 2.0858329876037, 2.09279775917234, 2.09929633971377, 2.10531538227125,
            2.11085508513429, 2.11593338459342, 2.12058937638755, 2.12488319044411, 2.12889284005344,
            2.13270923022591, 2.13642524129613
        ))
    
        z1 = np.array((
            28.6619989743589, 28.6049608580482, 28.5450559014246, 28.4794835365082, 28.4092022241029,
            28.3350387336261, 28.2576380039013, 28.1775367384564, 28.0951654495223, 28.0108638015799,
            27.9249204615456, 27.8375597971889, 27.7489735572062, 27.6593178994861, 27.5687237423079,
            27.4773041964674, 27.385152945665, 27.2923533725037, 27.1989741902862, 27.1050785554844,
            27.0107190614389, 26.9159392616942, 26.8207798998579, 26.7252795524418, 26.6294706791138,
            26.5333790187747, 26.4370286448011, 26.3404423792824, 26.2436415876413, 26.1466433615791,
            26.0494627200553, 25.9521139974702, 25.8546109832136, 25.7569659319296, 25.6591888403289,
            25.5612891089313, 25.4632757574251, 25.3651575801692, 25.2669423848755, 25.168636129068,
            25.0702443614344, 24.9717723691753, 24.8732252294088, 24.7746078436806, 24.6759249761952,
            24.5771812831842, 24.4783811991822, 24.3795278208977, 24.2806239242195, 24.1816722476486,
            24.082675412506, 23.9836359405196, 23.8845562687947, 23.7854387639056, 23.6862857298263,
            23.5870993764416, 23.4878813512533, 23.3886330916743, 23.2893560024275, 23.1900514136945,
            23.0907205842034, 22.9913647072391, 22.8919849096176, 22.792582243255, 22.6931576855678,
            22.5937118769204, 22.4942453558146, 22.3947585791552, 22.2952518558259, 22.1957253129467,
            22.0961789025235, 21.9966124171542, 21.8970253898987, 21.7974170708687, 21.6977864181452,
            21.5981324310853, 21.4984538542985, 21.398749114135, 21.2990163991982, 21.1992542481573,
            21.0994617682189, 20.9996381351822, 20.8997827371874, 20.799896002592, 20.6999792732694,
            20.6000347502029, 20.5000657435458, 20.4000764848403, 20.3000713432661, 20.2000540343164,
            20.1000287433225, 20
        ))
    
        x2 = np.array((
            0, 0.09970480868419, 0.19934990930324, 0.29892560671855, 0.39845053029212,
            0.49791294847979, 0.59729126792395, 0.69654894215390, 0.79562230931157,
            0.89441829468223, 0.99278184799703, 1.09041596346573, 1.18680564093363,
            1.2811247216627, 1.37173821384615, 1.45615592046124, 1.53140709459726,
            1.59481447147833, 1.64540118865725, 1.68420020117436, 1.7139567901805,
            1.73739377080012, 1.75669514508293, 1.77341533375185, 1.78855241978336,
            1.80270054725639, 1.81619925811863, 1.82923458834867, 1.84190868011591,
            1.8542787909028, 1.86637895717989, 1.87823123253782, 1.8898513429009,
            1.90125155489232, 1.91244211394041, 1.92343198232874, 1.93422925599876,
            1.94484137307261, 1.95527523060825, 1.96553726952681, 1.97563352932633,
            1.98556968830044, 1.99535109412568, 2.00498278811171, 2.01446952528177,
            2.02381579222306, 2.03302582656997, 2.04210365316528, 2.05105308938297,
            2.05987776623044, 2.06858114998522, 2.07716655334668, 2.08563714669058,
            2.09399597192344, 2.1022459544216, 2.11038991079032, 2.11843055346434,
            2.12637049464471, 2.13421224945851, 2.14195824744614, 2.14961083302462,
            2.15717226465044, 2.16464472287464, 2.17203031834828, 2.17933109740207,
            2.18654904729014, 2.19368610417827, 2.20074416242255, 2.20772507771543,
            2.21463066429355, 2.2214626950629, 2.22822290393084, 2.23491298448034,
            2.24153458547506, 2.24808930743357, 2.25457870003345, 2.26100425466765,
            2.26736739519304, 2.27366947406393, 2.27991176845626, 2.28609547194039,
            2.29222169050455, 2.29829141992375, 2.30430552303668, 2.31026470050494,
            2.31616945318094, 2.32202003758885, 2.32781640900641, 2.33355814983544,
            2.33924438051412, 2.3448736508916, 2.35044381228348, 2.3559518778997,
            2.36139385612463, 2.36676455738366, 2.37205741259143, 2.37726426201264,
            2.38237517331566, 2.38737831946074, 2.39225993069978, 2.39700436076639,
            2.40159431859356, 2.40601130725183, 2.41023630806784, 2.4142507588751,
            2.418037990112, 2.42158487083293, 2.42488372545427, 2.42793436504128,
            2.43074593332474, 2.4333379706335, 2.43574001568792, 2.4379889754186,
            2.44012113735539, 2.44214574160271
        ))
    
        z2 = np.array((
            30.0078688964104, 30.0065512265173, 30.0029356360187, 29.9976735322442,
            29.9915262693748, 29.9844424862537, 29.9762659464609, 29.9667364364951,
            29.9554600427241, 29.9419800400481, 29.9256478232344, 29.9054159211507,
            29.8799561681087, 29.8476996253689, 29.8061730864421, 29.7532731986494,
            29.6880451094337, 29.6112448542866, 29.5253946922475, 29.4335819554567,
            29.3384411867613, 29.2415312959595, 29.1437049711797, 29.04539962879,
            28.9468401079325, 28.8481308935213, 28.7493306654707, 28.6504694225423,
            28.5515612207417, 28.4526148172972, 28.3536354145385, 28.2546261943038,
            28.1555895272291, 28.0565273761794, 27.9574414945533, 27.8583335076939,
            27.7592047062979, 27.6600561309973, 27.5608887508238, 27.4617034796921,
            27.3625011864055, 27.2632827022544, 27.1640488269094, 27.0648003331593,
            26.9655379708194, 26.866262470073, 26.7669745291472, 26.6676746328858,
            26.5683632230419, 26.4690407437848, 26.3697076224236, 26.2703642715209,
            26.171011090825, 26.0716484692661, 25.9722767866544, 25.8728964148864,
            25.7735077188227, 25.6741110570318, 25.5747068020388, 25.4752952472731,
            25.3758766209457, 25.2764511425401, 25.1770190239312, 25.077580470411,
            24.978135681488, 24.8786848515835, 24.7792281708969, 24.6797658262875,
            24.5802980016905, 24.4808248780774, 24.381346633632, 24.2818634440495,
            24.1823754825859, 24.0828829198801, 23.9833859238618, 23.8838846597107,
            23.7843792895138, 23.6848699718101, 23.5853568615186, 23.4858401098541,
            23.3863198605794, 23.2867961336504, 23.1872689742316, 23.0877384678003,
            22.9882046876333, 22.8886676931036, 22.7891275276662, 22.6895842161987,
            22.5900377616683, 22.4904881410033, 22.3909353002525, 22.2913791491932,
            22.1918194461756, 22.0922558825952, 21.9926883788735, 21.8931167722048,
            21.7935407990426, 21.6939600883707, 21.5943741599893, 21.4947824294466,
            21.395184222527, 21.2955788018895, 21.1959654064542, 21.0963434196656,
            20.9967131851386, 20.8970742537356, 20.7974262724062, 20.6977693400472,
            20.5981040167542, 20.4984312619284, 20.3987523065558, 20.2990684973495,
            20.1993811617366, 20.099691467103, 20
        ))
    
        x3 = np.array((
            0, 0.106091997247487, 0.212282862868987, 0.318600341722176, 0.425123104761527,
            0.531901352872626, 0.638980673349671, 0.746408888895399, 0.85423835573243,
            0.962522013543275, 1.07131093710695, 1.18064439993891, 1.29054534660613,
            1.40102082494537, 1.51205952335725, 1.62363164712265, 1.73568405272677,
            1.84814589283292, 1.96093277164309, 2.07395098084042, 2.18706544796369,
            2.30006049798178, 2.41251892919309, 2.52351045392648, 2.63096976610101,
            2.73100121086301, 2.77148016441521, 2.78453458174498, 2.79186010794094,
            2.79751816975908, 2.80236248394865, 2.80670132496914, 2.81067659404222,
            2.81436810315714, 2.81782835677761, 2.81838473210957, 2.82109652791738,
            2.82420427153188, 2.82717868109766, 2.83004342751941, 2.83281945446213,
            2.83552519787279, 2.83817680575457, 2.84078819902162, 2.84337124785453,
            2.84593589783908, 2.84849035993836, 2.85104128587469, 2.85359396537556,
            2.85615250287907, 2.85872000627528, 2.86129873724824, 2.86389026304064,
            2.86649557135293, 2.86911518641933, 2.8717492484006, 2.87439759958004,
            2.87705983816952, 2.87973537526801, 2.88242347197052, 2.88512327706806,
            2.88783385231766, 2.88845703622116, 2.89055419723002, 2.89328326484414,
            2.89601997738632, 2.89876323695298, 2.90151193463858, 2.90426495815636,
            2.90702119863042, 2.90977955571619, 2.91253894330014, 2.91529829288703,
            2.91805655711182, 2.92081271230053, 2.92356576021251, 2.92631472933472,
            2.92905867580467, 2.93179668334584, 2.93452786251032, 2.93725134958514,
            2.93965292016822, 2.93996630533669, 2.94267191310587, 2.94536737736262,
            2.94805192150669, 2.95072478659992, 2.95338522921948, 2.9560325199586,
            2.95866594104728, 2.96128478419988, 2.96388834746919, 2.96647593163284,
            2.96904683568692, 2.97160035145282, 2.97413575724706, 2.97425125556441,
            2.97665231021929, 2.97914923756346, 2.98162572566976, 2.98408090824307,
            2.98651385151949, 2.98892353757412, 2.99130884415201, 2.99366851923393,
            2.99600114768388, 2.9962606601005, 2.99830510879971, 3.00057851901249,
            3.00281914620488, 3.00502428120326, 3.00719053828585, 3.00931351650196,
            3.00952011645174, 3.01138721018009, 3.0134029388599, 3.01534737564675,
            3.01699921085196, 3.01719889804888, 3.01892089400833, 3.02044973125651,
            3.02078939388317, 3.02167377283328, 3.02228920892799, 3.02239824880744
        ))
    
        z3 = np.array((
            30.1186241174416, 30.1184849219899, 30.1171034471136, 30.1152951272103, 30.1133655615441,
            30.1113195993005, 30.1091433108655, 30.1068210776114, 30.1043270725803, 30.1016326866963,
            30.0986949871154, 30.095463219881, 30.091866695563, 30.087823561571, 30.0832240264785,
            30.0779411584852, 30.0718042824719, 30.064598064254, 30.0560271138372, 30.0456642379577,
            30.0328664792409, 30.0166381634173, 29.995383512023, 29.9666311419869, 29.9269062544625,
            29.8724446142301, 20, 20.0992866487574, 20.1987778658839, 20.2983155533319,
            20.3978803414887,
            20.4974615256815, 20.5970538814448, 20.6966543922422, 20.796260963899, 29.8010645620936,
            20.8958720196992, 20.9954865830927, 21.0951039613303, 21.1947236485541, 21.2943452750121,
            21.3939685837807, 21.493593496968, 21.5932201164118, 21.6928485704969, 21.7924790313127,
            21.8921116969745, 21.9917467869875, 22.0913845424964, 22.1910252330904, 22.2906691240923,
            22.3903164843267, 22.4899675841224, 22.5896226941457, 22.6892820856006, 22.7889460266206,
            22.888614783746, 22.988288620861, 23.0879678014928, 23.1876525873797, 23.287343240002,
            23.3870400197103, 29.7142868959217, 23.486743187404, 23.5864530033367, 23.6861697290352,
            23.7858936263979, 23.885624958565, 23.9853639878029, 24.0851109788725, 24.1848661985415,
            24.2846299172733, 24.3844024085183, 24.4841839497909, 24.5839748236319, 24.6837753181654,
            24.7835857287758, 24.8834063591202, 24.9832375223667, 25.0830795430067, 25.1829327578276,
            29.6171448515155, 25.2827975190689, 25.3826741941813, 25.4825631716602, 25.5824648589361,
            25.6823796912861, 25.7823081276471, 25.8822506637417, 25.9822078278468, 26.0821801933113,
            26.1821683788585, 26.2821730607998, 26.3821949758609, 26.482234934283, 26.5822938276285,
            29.5148759694037, 26.6823726493158, 26.7824724970099, 26.8825946040959, 26.9827403478161,
            27.0829112850487, 27.1831091758242, 27.283336026885, 27.3835941282532, 27.4838861281617,
            29.4108704451581, 27.5842150845333, 27.6845846449453, 27.7849990464475, 27.8854632367103,
            27.9859831360428, 28.0865659498792, 29.3067361736195, 28.1872205245328, 28.2879579643369,
            28.3887923780716, 29.2030641097122, 28.4897421297855, 28.5908314496745, 28.6920929845482,
            29.0999759013086, 28.7935707828626, 28.997430828688, 28.8953250739844
        ))
    
        y1 = np.full_like(x1, 1)
        y2 = np.full_like(x2, 2)
        y3 = np.full_like(x3, 3)
    
        data = (
            np.array((x1, y1, z1)), np.array((x2, y2, z2)), np.array((x3, y3, z3)),
        )
    
        # plot first 2 data sets
        sorted_data = nearest_neighbor_sort(data[:2])
        mirrored_data = mirror_data(sorted_data)
        plot_data(mirrored_data)
    
        # plot last 2 data sets (here interpolation is wrong)
        sorted_data = nearest_neighbor_sort(data[1:])
        mirrored_data = mirror_data(sorted_data)
        plot_data(mirrored_data)
    
        plt.show()
    
    
    if __name__ == '__main__':
        demo()
    

    plot