Skip to content

4.1 Crack detection (AI)

Eric Breitbarth edited this page Apr 7, 2024 · 1 revision

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

Crack detection for a single nodemap

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:

window_size_offset_marks

The offset is a tuple of the form (offset_x, offset_y). offset_x denotes the distance between $x=0$ and the left edge of the detection window. offset_y denotes the vertical shift of the detection window's center from $y=0$. Both offset values can be negative. The squared detection window should not contain any 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)

Crack tip detection

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}")

Crack path detection

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)

Crack angle estimation

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 $\alpha$ is determined. For a straight path $\alpha=0$, here $\alpha\approx -45^\circ$. A circle with radius 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.

angle_radius

Plot detection

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:

example plot

Crack detection pipeline

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
Clone this wiki locally