def get_interf_orientations(df: pd.DataFrame, fault: str, nbins: iter = (5, 5, 4), extent=None): """Fits planes to binned fault interpretations (per interpretation to estimate orientation, returns GemPy-compatible pd.DataFrame with orientation data. Args: df (pd.DataFrame): DataFrame containing the fault stick interpretation points. fault (str): Formation string of the fault to be extracted. nbins (iter): List or tuple containing the number of bins in each spatial direction (x,y,z), default (5,5,4) extent (tuple): (x,X,y,Y,z,Z), if not given will determin extent from data Returns: pd.DataFrame with GemPy-compatible orientations structure """ df = df[df.formation == fault] bin_df(df, nbins, extent=extent) groups = df.groupby(["xbin", "ybin", "zbin", "interp"]).groups df_columns = "X Y Z G_x G_y G_z dip azimuth polarity formation interp xbin ybin zbin".split( ) orientations = pd.DataFrame(columns=df_columns) for key, i in groups.items(): if len(i) < 3: continue # can't fit plane points = df.loc[i][["X", "Y", "Z"]].values centroid, normal = plane_fit(points.T) dip, azimuth = mplstereonet.vector2plunge_bearing( normal[0], normal[1], normal[2]) orientation = [ centroid[0], centroid[1], centroid[2], normal[0], normal[1], normal[2], float(dip), float(azimuth), 1, fault, key[3], key[0], key[1], key[2] ] orientations.loc[len(orientations)] = orientation return orientations
def orient_for_interp(centroids: np.ndarray, normals: np.ndarray, formation: str, interp: int, filter_: bool = True): """Converts given centroids and normals into pandas.DataFrame compatible with GemPy orientation data structure. Args: centroids (np.ndarray): Centroids of planes (N,3) normals (np.ndarray): Normals of planes (N,3) formation (str): Formation name interp (int): Interpretation id filter_ (bool): If orientation data should be filtered or not (default: True). Currently only filters completely horizontal planes. Returns: (pd.DataFrame) Orientations dataframe compatible with GemPy's data structure. """ ndf = pd.DataFrame(columns="X Y Z G_x G_y G_z".split(" ")) ndf.X, ndf.Y, ndf.Z = centroids[:, 0], centroids[:, 1], centroids[:, 2] ndf.G_x, ndf.G_y, ndf.G_z = normals[:, 0], normals[:, 1], normals[:, 2] ndf["formation"] = formation ndf["interp"] = interp plunge, bearing = mplstereonet.vector2plunge_bearing( normals[:, 0], normals[:, 1], normals[:, 2]) ndf["dip"], ndf["azimuth"] = plunge, bearing ndf["polarity"] = 1 if filter_: vertbool = ndf["dip"].astype(int) != 0 return ndf[vertbool] else: return ndf
""" Illustrates plotting normal vectors in "world" coordinates as orientations on a stereonet. """ import numpy as np import matplotlib.pyplot as plt import mplstereonet # Load in a series of normal vectors from a triangulated normal fault surface normals = np.loadtxt('normal_vectors.txt') x, y, z = normals.T # Convert these to plunge/bearings for plotting. # Alternately, we could use xyz2stereonet (it doesn't correct for bi-directional # measurements, however) or vector2pole. plunge, bearing = mplstereonet.vector2plunge_bearing(x, y, z) # Set up the figure fig = plt.figure() ax = fig.add_subplot(111, projection='stereonet') # Make a density contour plot of the orientations ax.density_contourf(plunge, bearing, measurement='lines') # Plot the vectors as points on the stereonet. ax.line(plunge, bearing, marker='o', color='black') plt.show()
def plotMultiPlanes(self, structures, **kwds): #get the dataset dataset = kwds.get("data", [self.dataDict]) #split into structure types structure_data = [self.filterByName(regx) for regx in structures] #get drawing kwds symbols = kwds.get("symbols", ['.'] * len(structure_data)) cols = kwds.get("colours", ['k'] * len(structure_data)) contours = kwds.get("contours", ["None"] * len(structure_data)) cmaps = kwds.get("cmap", ["Blues"] * len(structure_data)) size = kwds.get("marker_size", [6] * len(structure_data)) alphas = kwds.get("alphas", [0.75] * len(structure_data)) values = kwds.get("values", None) showCMaps = kwds.get("showCMaps", True) mean = kwds.get("mean", [False] * len(structure_data)) models = kwds.get("models", [None] * len(structure_data)) outlier_thresh = kwds.get("outlier_thresh", [None] * len(structure_data)) #length of all arrays should match assert len(structure_data) == len( symbols), "Error - symbols array is the incorrect length." assert len(structure_data) == len( cols), "Error - symbols cols is the incorrect length." assert len(structure_data) == len( contours), "Error - contours array is the incorrect length." assert len(structure_data) == len( cmaps), "Error - cmaps array is the incorrect length." assert len(structure_data) == len( size), "Error - size array is the incorrect length." assert len(structure_data) == len( alphas), "Error - alphas array is the incorrect length." assert len(structure_data) == len( mean), "Error - mean array is the incorrect length." assert len(structure_data) == len( models), "Error - models array is the incorrect length." assert len(structure_data) == len( outlier_thresh ), "Error - outlier_thresh array is the incorrect length." if values != None: assert len(structure_data) == len(values) #init plot fig = plt.figure() ax = fig.add_subplot(111, projection='stereonet') cax = [] #plot each structure type for i, data in enumerate(structure_data): #init orientations lists strikes = [] dips = [] x = [] y = [] #loop through data finding plane objects for o in data: planes = self.filterByKey('PLANE', data=o) for p in planes: strikes += [ float(p['DipDir']) - 90 ] #n.b. we use this to avoid ambiguity in strike direction (though Compass uses british RHR) dips += [float(p['Dip'])] x += [float(p['Cx'])] y += [float(p['Cy'])] strikes = np.array(strikes) dips = np.array(dips) x = np.array(x) y = np.array(y) #outlier detection? out_mask = np.array([True] * len(strikes)) #default is all outliers if models[i] != None and outlier_thresh[ i] != None: #do we have enough info to determine outliers? print("\tComputing outliers for threshold %d" % outlier_thresh[i]) strike_pred = models[i].predict(np.stack( [x, y]).T) #generate predicted values given the model diff = models[i].loss( strikes, strike_pred ) #calculate the difference between predicted and observed out_mask = diff > outlier_thresh[ i] #define outliers (boolean array) in_mask = np.logical_not(out_mask) #calculate inliers #plot poles #ax.pole(strikes[out_mask], dips[out_mask], symbols[i], markersize=size[i], color=cols[i], alpha=alphas[i]) #ax.pole(strikes[in_mask], dips[in_mask], '^', markersize=size[i], color=cols[i], alpha=alphas[i]) ax.pole(strikes, dips, symbols[i], markersize=size[i], color=cols[i], alpha=alphas[i]) #plot model? if models[i] != None and outlier_thresh[i] != None: #calculate centroid of inlier points cx = np.mean(x[in_mask]) cy = np.mean(y[in_mask]) #calculate expected strike given this point strike_pred = models[i].predict(np.stack([[cx], [cy]]).T)[0] #plot domain ax.plane( strike_pred - outlier_thresh[i] + 90, 90, '--', c='gray' ) #n.b. + 90 as we are mapping the domain of poles not the actual planes! ax.plane(strike_pred + outlier_thresh[i] + 90, 90, '--', c='gray') #plot radial line ax.plane(strike_pred, 90, '-', c='gray') #plot contours? if contours[i] == "Line": if (values == None): cax.append( ax.density_contour(strikes, dips, measurement='poles', cmap=cmaps[i], sigma=2)) else: cax.append( ax.density_contour(strikes, dips, levels=values[i], measurement='poles', cmap=cmaps[i], sigma=2)) if showCMaps: fig.colorbar(cax[-1]) if contours[i] == "Filled": if (values == None): cax.append( ax.density_contourf(strikes, dips, measurement='poles', cmap=cmaps[i], sigma=2)) else: cax.append( ax.density_contourf(strikes, dips, levels=values[i], measurement='poles', cmap=cmaps[i], sigma=2)) if showCMaps: fig.colorbar(cax[-1]) #plot n-observations xloc = 0.95 yloc = -0.04 + 0.032 * i ax.text(xloc, yloc, "n=%d" % len(strikes), color=cols[i], transform=ax.transAxes, ha='left', va='center') #calculate mean? if mean[i] == True: if len(strikes) > 0: strike, dip = mplstereonet.fit_pole(strikes, dips, measurement='poles') ax.plane(strike, dip, cols[i]) #plot outcrop orientation to give indication of uncertainty/outcrop bias if (kwds.get("show_outcrop", False)): #list to store normals in normals = [[], [], []] #[x-coords, y-coords, z-coords] #loop through trace datasets for o in dataset: traces = self.filterByKey('TRACE', data=o) for t in traces: #does this trace have normals? if t['POINTS']['@normals'] == "True": #extract normal coords normals[0] += list( map(float, t['POINTS']['nx'].split(","))) normals[1] += list( map(float, t['POINTS']['ny'].split(","))) normals[2] += list( map(float, t['POINTS']['nz'].split(","))) #plot normals as contours plunge, bearing = mplstereonet.vector2plunge_bearing( normals[0], normals[1], normals[2]) cax = ax.density_contour(plunge, bearing, measurement='lines', cmap="winter") fig.colorbar(cax) #draw grid ax.set_xticks([x / (2 * np.pi) for x in range(-180, 180, 45)]) ax.set_yticks([x / (2 * np.pi) for x in range(-180, 180, 45)]) ax.grid() return fig, ax, cax
def plotPlanes(self, **kwds): #get the dataset dataset = kwds.get("data", [self.dataDict]) #init plot fig = plt.figure() ax = fig.add_subplot(111, projection='stereonet') #load orientations lists strikes = [] dips = [] dip_dirs = [] #loop through data dicts for o in dataset: planes = self.filterByKey('PLANE', data=o) for p in planes: strikes += [ float(p['DipDir']) - 90 ] #n.b. we use this to avoid ambiguity in strike direction (though Compass uses british RHR) dips += [float(p['Dip'])] #dip_dirs += [float (p['DipDir'])] #plot poles ax.pole(strikes, dips, 'x', markersize=6, color='k') #plot contours cax = ax.density_contourf(strikes, dips, measurement='poles', cmap="Blues") fig.colorbar(cax) #plot outcrop orientation to give indication of uncertainty/outcrop bias if (kwds.get("show_outcrop", False)): #list to store normals in normals = [[], [], []] #[x-coords, y-coords, z-coords] #loop through trace datasets for o in dataset: traces = self.filterByKey('TRACE', data=o) for t in traces: #does this trace have normals? if t['POINTS']['@normals'] == "True": #extract normal coords normals[0] += list( map(float, t['POINTS']['nx'].split(","))) normals[1] += list( map(float, t['POINTS']['ny'].split(","))) normals[2] += list( map(float, t['POINTS']['nz'].split(","))) #plot normals as contours plunge, bearing = mplstereonet.vector2plunge_bearing( normals[0], normals[1], normals[2]) cax = ax.density_contour(plunge, bearing, measurement='lines', cmap="winter") fig.colorbar(cax) #ax.line(plunge, bearing, marker='o', color='grey', markersize=1) #draw grid ax.set_xticks([x / (2 * np.pi) for x in range(-180, 180, 45)]) ax.set_yticks([x / (2 * np.pi) for x in range(-180, 180, 45)]) ax.grid() return fig, ax