-
Notifications
You must be signed in to change notification settings - Fork 15
4.1 Crack detection (AI)
For the automatic crack detection in DIC data from images taken during fatigue crack growth experiments, we use trained artificial neural networks (see Strohmann et al. '21, Melching et al. '22). Example scripts are located in the folder scripts/crack_detection
.
Our crack detection backbone currently features
- Crack tip detection ("ParallelNets")
- Crack path detection ("UNetPath")
- Crack angle prediction
We start with an example of how to detect the crack path and crack tip for one nodemap. First, we import
import os
import numpy as np
import torch
import matplotlib.pyplot as plt
from crackpy.crack_detection.model import get_model
from crackpy.crack_detection.utils.plot import plot_prediction
from crackpy.crack_detection.detection import CrackTipDetection, CrackPathDetection, CrackAngleEstimation, CrackDetection
from crackpy.fracture_analysis.data_processing import InputData
from crackpy.structure_elements.data_files import Nodemap
The detection is based on the core modules CrackDetection
, CrackTipDetection
, CrackPathDetection
, and CrackAngleEstimation
.
Now, lets set the input and output paths
# Settings
NODEMAP_FILE = 'Dummy2_WPXXX_DummyVersuch_2_dic_results_1_52.txt'
DATA_PATH = os.path.join('..', '..', 'test_data', 'crack_detection', 'Nodemaps')
OUTPUT_PATH = 'prediction'
and initialize the crack detection
det = CrackDetection(
side='right',
detection_window_size=30,
offset=(5, 0), # use a negative offset if you analyze a left hand side cracktip
angle_det_radius=10,
device=torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
)
The attributes side
, detection_window_size
, and offset
specify the window to detect the crack tip and path. This figure illustrates the parameters detection_window_size
and offset
:
The offset
is a tuple of the form (offset_x, offset_y)
. offset_x
denotes the distance between offset_y
denotes the vertical shift of the detection window's center from NaN
values and exclude the specimen borders.
The parameter angle_det_radius
defines which radius around the crack tip position will be used to estimate the crack angle (see illustration below)
Now, we define the nodemap and preprocess the data.
# Get nodemap data
nodemap = Nodemap(name=NODEMAP_FILE, folder=DATA_PATH)
data = InputData(nodemap)
# Interpolate data on arrays (256 x 256 pixels)
interp_disps, interp_eps_vm = det.interpolate(data)
# Preprocess input
input_ch = det.preprocess(interp_disps)
We start with the crack tip detection by loading the tip detector model.
# Load crack tip detector
tip_detector = get_model('ParallelNets')
ct_det = CrackTipDetection(detection=det, tip_detector=tip_detector)
To perform the crack tip detection, the preprocessed input data is fed into the model and postprocessed to calculate the most likely crack tip position.
# Make prediction
pred = ct_det.make_prediction(input_ch)
# Calculate segmentation and most likely crack tip position
ct_segmentation = ct_det.calculate_segmentation(pred)
ct_pixels = ct_det.find_most_likely_tip_pos(pred)
# Calculate global crack tip positions in mm
ct_x, ct_y = ct_det.calculate_position_in_mm(ct_pixels)
print(f"Crack tip x [mm]: {ct_x}")
print(f"Crack tip y [mm]: {ct_y}")
The crack path detection works similarly. We load the trained path detector model and predict the crack path as a segmentation or skeletonized path.
# load crack path detector
path_detector = get_model('UNetPath')
cp_det = CrackPathDetection(detection=det, path_detector=path_detector)
# predict segmentation and path skeleton
cp_segmentation, cp_skeleton = cp_det.predict_path(input_ch)
The crack angle estimation uses the predicted crack tip and crack path. We initialize a crack angle estimator,
angle_est = CrackAngleEstimation(detection=det, crack_tip_in_px=ct_pixels)
and apply a circular mask around the crack tip position to the crack path segmentation. This is done to consider only the crack path near the crack tip for the crack angle estimation.
# Consider only crack path close to crack tip
cp_segmentation_masked = angle_est.apply_circular_mask(cp_segmentation)
# Filter for largest connected crack path
cp_segmentation_largest_region = angle_est.get_largest_region(cp_segmentation_masked)
Finally, we estimate the crack angle using linear regression on the filtered segmented crack path pixels.
# Estimate the angle
angle = angle_est.predict_angle(cp_segmentation_largest_region)
print(f"Crack angle [deg]: {angle}")
The figure on the left shows how the angle angle_det_radius
and center at ct_pixels
is created. The figure on the right shows how important the choice of the radius is. If chosen too small the angle estimaton is unstable due to instability of the linear regression. If chosen too large the estimated angle might have an undesired value.
First, we can specify global plotting paramters like the color map or resolution
# Set colormap and resolution
plt.rcParams['image.cmap'] = 'coolwarm'
plt.rcParams['figure.dpi'] = 300
Instead of a scientific colormap like 'coolwarm' or matplotlib's standard 'viridis', you are also free to specify colormaps matching GOM-Aramis as follows:
# Set colormap similar to GOM-Aramis
cm_jet = cm.get_cmap('jet', 512)
aramis_cmap = ListedColormap(cm_jet(np.linspace(0.1, 0.9, 256)))
cm.register_cmap(cmap=aramis_cmap, name='aramis')
plt.rcParams['image.cmap'] = 'aramis'
Finally, we can plot the prediction results using
plot_prediction(background=interp_eps_vm * 100,
interp_size=det.interp_size,
offset=det.offset,
save_name=NODEMAP_FILE + '_plot',
crack_tip_prediction=np.asarray([ct_pixels]),
crack_tip_seg=ct_segmentation,
crack_tip_label=None,
crack_path=cp_skeleton,
f_min=0,
f_max=0.68,
title=NODEMAP_FILE,
path=OUTPUT_PATH,
label='Von Mises strain [%]')
The result can be seen here:
We continue with an example of how to apply CrackPy's crack detection on a whole crack growth experiment. First, we need to make the following imports
import os
from matplotlib import pyplot as plt
from crackpy.crack_detection.model import get_model
from crackpy.crack_detection.pipeline.pipeline import CrackDetectionSetup, CrackDetectionPipeline
and can specify plotting parameters of our choice
plt.rcParams['image.cmap'] = 'coolwarm'
plt.rcParams['figure.dpi'] = 100
We set the path for the input data (DIC nodemaps) DATA_PATH
and the path where the output should be saved OUTPUT_PATH
. The path for the input data should only contain nodemaps.
# paths
DATA_PATH = os.path.join('..', '..', 'test_data', 'crack_detection', 'Nodemaps')
OUTPUT_PATH = 'Pipeline_Output'
We load the trained neural networks (path and tip detector models) as follows
# crack detectors
tip_detector = get_model('ParallelNets')
path_detector = get_model('UNetPath')
To finally setup the crack detection pipeline, we need to specify the specimen_size
, the specimen's sides
, which we want to run the detection on (only relevant for MT-specimen), the detection_window_size
(i.e. the size of the field of view of the network), and the initial left-hand side position of the detection window (start_offset
). This offset is automatically adjusted during the pipeline to account for the growing crack. To guarantee that the window never runs over the specimen boundaries, a detection_boundary
in the form of a tuple ('left', right', 'bottom', 'top') needs to be defined.
# setup
det_setup = CrackDetectionSetup(
specimen_size=160,
sides=['right'],
detection_window_size=40,
detection_boundary=(0, 70, -35, 35),
start_offset=(5, 0)
)
Then, we initialize the crack detection pipeline
# pipeline
cd_pipeline = CrackDetectionPipeline(
data_path=DATA_PATH,
output_path=OUTPUT_PATH,
tip_detector_model=tip_detector,
path_detector_model=path_detector,
setup=det_setup
)
We only want to run the detection for nodemaps, where the maximal force (here max_force
= 15000 [N]) is applied, since the crack is then clearly visible. Therefore we filter for the detection stages:
cd_pipeline.filter_detection_stages(max_force=15000)
Then, we run the detection:
cd_pipeline.run_detection()
Afterwards, the results are assigned also to their corresponding remaining (non-maximal load) stages and all results are written into the file "FAT_Input.txt".
cd_pipeline.assign_remaining_stages()
cd_pipeline.write_results()
The output file "FAT_Input.txt" looks like this:
Filename | Crack Tip x [mm] | Crack Tip y [mm] | Crack Angle | Side |
---|---|---|---|---|
Dummy2_WPXXX_DummyVersuch_2_dic_results_1_52.txt | 15.39 | 0.40 | -0.27 | right |
Dummy2_WPXXX_DummyVersuch_2_dic_results_1_53.txt | 15.87 | 0.40 | -0.01 | right |
Dummy2_WPXXX_DummyVersuch_2_dic_results_1_54.txt | 15.87 | 0.40 | -0.01 | right |
Dummy2_WPXXX_DummyVersuch_2_dic_results_1_55.txt | 15.87 | 0.40 | -0.01 | right |