Skip to content

Commit 95e94c6

Browse files
authored
fix: add debug info to thickness calculators (#161)
* fix: initial commit * fix: add debug info and warning for bad calculations * fix: add line length control to thickness calculators * fix: refactor to avoid repetitive code * fix: typo * fix: remove list comprehension - wky * fix list to df * fix: revert to lst comprehension * fix: make line length attribute of the TC class * fix: typos * fix: syntax * fix: add location tracking * fix: init commit to remove lst comprehension
1 parent 7bf14b1 commit 95e94c6

File tree

1 file changed

+84
-16
lines changed

1 file changed

+84
-16
lines changed

map2loop/thickness_calculator.py

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ class ThicknessCalculator(ABC):
3333
ABC (ABC): Derived from Abstract Base Class
3434
"""
3535

36-
def __init__(self):
36+
def __init__(self, max_line_length: float = None):
3737
"""
3838
Initialiser of for ThicknessCalculator
3939
"""
4040
self.thickness_calculator_label = "ThicknessCalculatorBaseClass"
41+
self.max_line_length = max_line_length
4142

4243
def type(self):
4344
"""
@@ -84,7 +85,17 @@ def compute(
8485
)
8586
return units
8687

88+
def _check_thickness_percentage_calculations(self, thicknesses: pandas.DataFrame):
89+
units_with_no_thickness = len(thicknesses[thicknesses['ThicknessMean'] == -1])
90+
total_units = len(thicknesses)
8791

92+
if total_units > 0 and (units_with_no_thickness / total_units) >= 0.75:
93+
logger.warning(
94+
f"More than {int(0.75 * 100)}% of units ({units_with_no_thickness}/{total_units}) "
95+
f"have a calculated thickness of -1. This may indicate that {self.thickness_calculator_label} "
96+
f"is not suitable for this dataset."
97+
)
98+
8899
class ThicknessCalculatorAlpha(ThicknessCalculator):
89100
"""
90101
ThicknessCalculator class which estimates unit thickness based on units, basal_contacts and stratigraphic order
@@ -176,6 +187,8 @@ def compute(
176187
val = min(distance, thicknesses.at[idx, "ThicknessMean"])
177188
thicknesses.loc[idx, "ThicknessMean"] = val
178189

190+
self._check_thickness_percentage_calculations(thicknesses)
191+
179192
return thicknesses
180193

181194

@@ -193,12 +206,14 @@ class InterpolatedStructure(ThicknessCalculator):
193206
-> pandas.DataFrame: Calculates a thickness map for the overall map area.
194207
"""
195208

196-
def __init__(self):
209+
def __init__(self, max_line_length: float = None):
197210
"""
198211
Initialiser for interpolated structure version of the thickness calculator
199212
"""
213+
super().__init__(max_line_length)
200214
self.thickness_calculator_label = "InterpolatedStructure"
201-
self.lines = []
215+
self.lines = None
216+
202217

203218
@beartype.beartype
204219
def compute(
@@ -297,7 +312,11 @@ def compute(
297312
interpolated_orientations = interpolated_orientations[
298313
["geometry", "dip", "UNITNAME"]
299314
].copy()
300-
315+
316+
_lines = []
317+
_dips = []
318+
_location_tracking = []
319+
301320
for i in range(0, len(stratigraphic_order) - 1):
302321
if (
303322
stratigraphic_order[i] in basal_unit_list
@@ -319,11 +338,18 @@ def compute(
319338
dip = interpolated_orientations.loc[
320339
interpolated_orientations["UNITNAME"] == stratigraphic_order[i], "dip"
321340
].to_numpy()
341+
322342
_thickness = []
343+
323344
for _, row in basal_contact.iterrows():
324345
# find the shortest line between the basal contact points and top contact points
325346
short_line = shapely.shortest_line(row.geometry, top_contact_geometry)
326-
self.lines.append(short_line)
347+
_lines.append(short_line[0])
348+
349+
# check if the short line is
350+
if self.max_line_length is not None and short_line.length > self.max_line_length:
351+
continue
352+
327353
# extract the end points of the shortest line
328354
p1 = numpy.zeros(3)
329355
p1[0] = numpy.asarray(short_line[0].coords[0][0])
@@ -336,18 +362,27 @@ def compute(
336362
p2[1] = numpy.asarray(short_line[0].coords[-1][1])
337363
# get the elevation Z of the end point p2
338364
p2[2] = map_data.get_value_from_raster(Datatype.DTM, p2[0], p2[1])
339-
# get the elevation Z of the end point p2
340-
p2[2] = map_data.get_value_from_raster(Datatype.DTM, p2[0], p2[1])
341365
# calculate the length of the shortest line
342366
line_length = scipy.spatial.distance.euclidean(p1, p2)
343367
# find the indices of the points that are within 5% of the length of the shortest line
344368
indices = shapely.dwithin(short_line, interp_points, line_length * 0.25)
345369
# get the dip of the points that are within
346-
# 10% of the length of the shortest line
347370
_dip = numpy.deg2rad(dip[indices])
348-
# get the end points of the shortest line
349-
# calculate the true thickness t = L . sin dip
371+
_dips.append(_dip)
372+
# calculate the true thickness t = L * sin(dip)
350373
thickness = line_length * numpy.sin(_dip)
374+
375+
# add location tracking
376+
location_tracking = pandas.DataFrame(
377+
{
378+
"p1_x": [p1[0]], "p1_y": [p1[1]], "p1_z": [p1[2]],
379+
"p2_x": [p2[0]], "p2_y": [p2[1]], "p2_z": [p2[2]],
380+
"thickness": [thickness],
381+
"unit": [stratigraphic_order[i]]
382+
}
383+
)
384+
_location_tracking.append(location_tracking)
385+
351386
# Average thickness along the shortest line
352387
if all(numpy.isnan(thickness)):
353388
pass
@@ -372,10 +407,22 @@ def compute(
372407
logger.warning(
373408
f"Thickness Calculator InterpolatedStructure: Cannot calculate thickness between {stratigraphic_order[i]} and {stratigraphic_order[i + 1]}\n"
374409
)
375-
410+
411+
# Combine all location_tracking DataFrames into a single DataFrame
412+
combined_location_tracking = pandas.concat(_location_tracking, ignore_index=True)
413+
414+
# Save the combined DataFrame as an attribute of the class
415+
self.location_tracking = combined_location_tracking
416+
417+
# Create GeoDataFrame for lines
418+
self.lines = geopandas.GeoDataFrame(geometry=_lines, crs=basal_contacts.crs)
419+
self.lines['dip'] = _dips
420+
421+
# Check thickness calculation
422+
self._check_thickness_percentage_calculations(thicknesses)
423+
376424
return thicknesses
377425

378-
379426
class StructuralPoint(ThicknessCalculator):
380427
'''
381428
This class is a subclass of the ThicknessCalculator abstract base class. It implements the thickness calculation using a deterministic workflow based on stratigraphic measurements.
@@ -390,10 +437,12 @@ class StructuralPoint(ThicknessCalculator):
390437
391438
'''
392439

393-
def __init__(self):
440+
def __init__(self, max_line_length: float = None):
441+
super().__init__(max_line_length)
394442
self.thickness_calculator_label = "StructuralPoint"
395-
self.line_length = 10000
396443
self.strike_allowance = 30
444+
self.lines = None
445+
397446

398447
@beartype.beartype
399448
def compute(
@@ -474,6 +523,8 @@ def compute(
474523
# create empty lists to store thicknesses and lithologies
475524
thicknesses = []
476525
lis = []
526+
_lines = []
527+
_dip = []
477528

478529
# loop over each sampled structural measurement
479530
for s in range(0, len(sampled_structures)):
@@ -505,7 +556,9 @@ def compute(
505556
)
506557

507558
# draw orthogonal line to the strike (default value 10Km), and clip it by the bounding box of the lithology
508-
B = calculate_endpoints(measurement_pt, strike, self.line_length, bbox_poly)
559+
if self.max_line_length is None:
560+
self.max_line_length = 10000
561+
B = calculate_endpoints(measurement_pt, strike, self.max_line_length, bbox_poly)
509562
b = geopandas.GeoDataFrame({'geometry': [B]}).set_crs(basal_contacts.crs)
510563

511564
# find all intersections
@@ -577,15 +630,28 @@ def compute(
577630
if not (b_s[0] < strike1 < b_s[1] and b_s[0] < strike2 < b_s[1]):
578631
continue
579632

633+
#build the debug info
634+
line = shapely.geometry.LineString([int_pt1, int_pt2])
635+
_lines.append(line)
636+
_dip.append(measurement['DIP'])
637+
580638
# find the lenght of the segment
581639
L = math.sqrt(((int_pt1.x - int_pt2.x) ** 2) + ((int_pt1.y - int_pt2.y) ** 2))
582640

641+
# if length is higher than max_line_length, skip
642+
if self.max_line_length is not None and L > self.max_line_length:
643+
continue
644+
583645
# calculate thickness
584646
thickness = L * math.sin(math.radians(measurement['DIP']))
585647

586648
thicknesses.append(thickness)
587649
lis.append(litho_in)
588-
650+
651+
# create the debug gdf
652+
self.lines = geopandas.GeoDataFrame(geometry=_lines, crs=basal_contacts.crs)
653+
self.lines["DIP"] = _dip
654+
589655
# create a DataFrame of the thicknesses median and standard deviation by lithology
590656
result = pandas.DataFrame({'unit': lis, 'thickness': thicknesses})
591657
result = result.groupby('unit')['thickness'].agg(['median', 'mean', 'std']).reset_index()
@@ -640,4 +706,6 @@ def compute(
640706
output_units.loc[output_units["name"] == unit, "ThicknessMean"] = -1
641707
output_units.loc[output_units["name"] == unit, "ThicknessStdDev"] = -1
642708

709+
self._check_thickness_percentage_calculations(output_units)
710+
643711
return output_units

0 commit comments

Comments
 (0)