Note
Go to the end to download the full example code.
Automatic azimuth set detection
Detect axial azimuth set centers and ranges with
fractopo.analysis.automatic_azimuth_sets and (optionally) trim them with
trim_azimuth_set_ranges. The example compares the detected centers with a
rose plot of the same network.
The definition of azimuth sets based only on the orientation and lengths of fractures in an area might not reflect how they have actually been formed geologically. Consequently, any automatic set detection algorithm result should be critically evaluated during deeper analysis of fracturing in an area.
The example here tries to help with a case when fractures seem to have been, within the whole target area, clustered in orientation to few specific directions. However, not all fractures follow these specific directions and they are, consequently, considered “background” fractures. This is only one interpretation.
Initializing
from pprint import pprint
from textwrap import fill
import matplotlib.pyplot as plt
import numpy as np
# Load the KB11 network from examples/example_data.py
from example_data import KB11_NETWORK
from fractopo import Network
from fractopo.analysis.automatic_azimuth_sets import (
automatic_azimuth_sets,
trim_azimuth_set_ranges,
)
Input azimuths
The KB11 trace azimuths are axial values in the range [0, 180). Thus, 0° and 180° represent the same direction.
azimuths = KB11_NETWORK.trace_azimuth_array
lengths = KB11_NETWORK.trace_length_array
print(f"Number of trace azimuths: {azimuths.size}")
pprint(azimuths[:10])
Number of trace azimuths: 709
array([147.29097308, 49.07138633, 51.40545212, 149.17608927,
57.16578588, 147.55474179, 154.23270791, 34.42233604,
80.57016745, 42.86524189])
Detect sets automatically
Set the number of groups to find. The clustering weights each azimuth by
fracture length, so longer traces have more influence on the centers.
random_state keeps the example output reproducible; omit it otherwise.
n_sets = 2
centers, ranges = automatic_azimuth_sets(
azimuths,
lengths,
n_sets=n_sets,
random_state=0,
)
print(f"Detected {n_sets} sets")
print("Detected center azimuths (degrees):")
pprint(np.round(np.sort(centers), 1))
print("Detected set ranges (degrees):")
pprint(tuple(tuple(np.round(range_tuple, 1)) for range_tuple in ranges))
Detected 2 sets
Detected center azimuths (degrees):
array([ 71.5, 154.9])
Detected set ranges (degrees):
((np.float64(114.5), np.float64(21.2)), (np.float64(23.2), np.float64(113.3)))
Plot the detected centers on a rose plot
_, fig, ax = KB11_NETWORK.plot_trace_azimuth()
for center in centers:
radians = np.deg2rad(center)
ax.plot([radians, radians], [0, ax.get_ylim()[1]], linestyle="--", linewidth=2)
ax.plot(
[radians + np.pi, radians + np.pi],
[0, ax.get_ylim()[1]],
linestyle="--",
linewidth=2,
)
ax.set_title(
fill("KB11 trace azimuths with automatically detected set centers", 30),
)
plt.show()

Narrow the detected ranges and label the remaining fractures as background
trimmed_ranges, trimmed_labels = trim_azimuth_set_ranges(
azimuths,
lengths,
ranges,
retained_length_fraction=0.8,
)
trimmed_set_names = tuple(f"{start:.0f}-{end:.0f}" for start, end in trimmed_ranges)
print("Trimmed set ranges (degrees):")
pprint(tuple(tuple(np.round(range_tuple, 1)) for range_tuple in trimmed_ranges))
print("Classified trace counts, including background fractures:")
pprint(
{
str(key): int(val)
for key, val in zip(*np.unique(trimmed_labels, return_counts=True), strict=True)
}
)
Trimmed set ranges (degrees):
((np.float64(139.9), np.float64(164.8)), (np.float64(53.2), np.float64(96.0)))
Classified trace counts, including background fractures:
{'0': 236, '1': 232, 'background': 241}
Build a new Network with the trimmed set ranges
kb11_network_automatic_sets = Network(
trace_gdf=KB11_NETWORK.trace_gdf[["geometry"]],
area_gdf=KB11_NETWORK.area_gdf,
name="KB11 automatic sets",
truncate_traces=KB11_NETWORK.truncate_traces,
circular_target_area=KB11_NETWORK.circular_target_area,
determine_branches_nodes=KB11_NETWORK.determine_branches_nodes,
snap_threshold=KB11_NETWORK.snap_threshold,
azimuth_set_names=trimmed_set_names,
azimuth_set_ranges=trimmed_ranges,
)
pprint(kb11_network_automatic_sets.trace_azimuth_set_counts)
kb11_network_automatic_sets.plot_trace_azimuth(
visualize_sets=True, add_abundance_order=True, append_azimuth_set_text=True
)

{'140-165': 236, '53-96': 240}
(AzimuthBins(bin_width=10.0, bin_locs=array([ 5., 15., 25., 35., 45., 55., 65., 75., 85., 95., 105.,
115., 125., 135., 145., 155., 165., 175.]), bin_heights=array([ 24.65251191, 9.55228874, 24.80249385, 28.43888846,
25.07495018, 61.0915944 , 176.85972845, 274.24150465,
85.0267333 , 41.60359186, 33.59424712, 21.44456621,
9.24622342, 64.65547302, 160.87857165, 384.27467555,
107.62579801, 30.51269213])), <Figure size 650x510 with 1 Axes>, <PolarAxes: title={'center': 'KB11\nautomatic\nsets'}>)
Total running time of the script: (0 minutes 4.228 seconds)