Examples & Tutorials

🔬 Tensile
v0.4.0
Automated Tensile Test Analysis

Example 1: Basic Workflow

Complete pipeline for analyzing a single tensile test specimen.

Scenario

You have a CSV file 316L_specimen_A1.csv containing tensile test data and want to calculate mechanical properties.

from tensile import TensileTest

# Create test instance and run full pipeline
test = (TensileTest()
        .load("data/316L_specimen_A1.csv")
        .validate()
        .clean()
        .segment()
        .analyze())

# Print summary
print(test.summary())

# Export results
test.export("results/316L_A1_results.csv")
=== Tensile Test Analysis Summary ===
Specimen: 316L_specimen_A1.csv
Young's Modulus (E): 193.45 GPa
Yield Strength (Rp0.2): 280.32 MPa
Ultimate Tensile Strength (Rm): 625.78 MPa
Total Elongation (At): 45.23 %
Elastic R²: 0.9998

Quality:
  Status: VALID
  Slippages detected: 1
  Slippages corrected: 1
  Warnings: 0

Step-by-Step Explanation

# 1. Load data from CSV
test = TensileTest()
test.load("data/316L_specimen_A1.csv")

# 2. Validate data quality
test.validate()
if not test.quality_report['is_valid']:
    print("Validation failed!")
    print(test.quality_report['validation_issues'])

# 3. Clean data (detect/correct errors)
test.clean()
print(f"Slippages detected: {test.quality_report['num_slippages_detected']}")

# 4. Segment into elastic/plastic regions
test.segment()
print(f"Elastic region: indices {test.segments['elastic_start']} to {test.segments['elastic_end']}")

# 5. Calculate mechanical properties
test.analyze()
print(f"Young's Modulus: {test.results['youngs_modulus_GPa']:.2f} GPa")
print(f"Yield Strength: {test.results['Rp02_MPa']:.2f} MPa")
print(f"UTS: {test.results['Rm_MPa']:.2f} MPa")
print(f"Elongation: {test.results['At_percent']:.2f}%")

Example 2: Batch Processing

Process multiple test specimens from a folder and generate a comprehensive Excel report.

Scenario

You have 10 specimens in folder data/316L_batch_A/ and need summary statistics with outlier detection.

from tensile import TensileTestBatch

# Load all CSV files from folder
batch = TensileTestBatch.from_folder("data/316L_batch_A/")
print(f"Loaded {len(batch.tests)} tests")

# Analyze all tests (parallel for speed)
batch.analyze_all(parallel=True)

# Get summary statistics
summary = batch.summary_statistics()
print("\n=== Summary Statistics ===")
print(summary)

# Identify outliers using Z-score method
outliers = batch.identify_outliers(method='zscore', threshold=2.5)
print("\n=== Outliers ===")
print(outliers[outliers['is_outlier']])

# Export comprehensive Excel report
batch.export_summary("reports/316L_batch_A_report.xlsx")
print("\nExcel report saved!")
Loaded 10 tests

=== Summary Statistics ===
Specimen           E_GPa   Rp02_MPa   Rm_MPa   At_percent  Analysis_OK
specimen_1.csv     192.3    278.5     623.4      44.5          True
specimen_2.csv     194.1    281.2     627.8      45.8          True
specimen_3.csv     193.8    279.8     625.2      45.1          True
...
Mean                193.5    280.1     625.3      45.2            -
Std                   1.2      1.8       2.5       0.8            -
CV%                   0.6      0.6       0.4       1.8            -
Min                 191.8    277.2     621.1      43.9            -
Max                 195.2    282.9     628.7      46.3            -

=== Outliers ===
Specimen           Property        Value  is_outlier
specimen_8.csv     Rm_MPa         631.5      True

Excel report saved!

Processing Specific Files

# Process specific list of files
files = [
    "data/specimen_1.csv",
    "data/specimen_2.csv",
    "data/specimen_3.csv"
]

batch = TensileTestBatch.from_csv_list(files)
batch.analyze_all()
summary = batch.summary_statistics()
print(summary)

Example 3: Custom Configuration

Customize analysis parameters for specific materials or test conditions.

Scenario

Testing aluminum alloy with more sensitive slippage detection and custom elastic range.

from tensile import TensileTest, TensileTestConfig

# Create custom configuration for aluminum
al_config = TensileTestConfig(
    # More sensitive slippage detection
    jump_threshold=0.00005,
    min_strain_decrease_length=3,
    
    # Aluminum-specific elastic range
    elastic_strain_range=(0.0003, 0.0015),
    
    # Use piecewise regression segmentation
    segmentation_method='piecewise_regression',
    
    # Custom plot settings
    show_annotations=True,
    show_slippages=True,
    plot_width=1200,
    plot_height=700
)

# Use configuration with single test
test = TensileTest(config=al_config)
test.load("data/AlSi10Mg_specimen.csv").validate().clean().segment().analyze()

print(f"Detected {test.quality_report['num_slippages_detected']} slippages")
print(f"Young's Modulus: {test.results['youngs_modulus_GPa']:.2f} GPa")

# Use same configuration for batch
batch = TensileTestBatch.from_folder("data/AlSi10Mg/", config=al_config)
batch.analyze_all()
summary = batch.summary_statistics()
print(summary)

Configuration Comparison

from tensile import TensileTestConfig

# Default configuration
default_config = TensileTestConfig()

# High-sensitivity configuration (detect subtle errors)
sensitive_config = TensileTestConfig(
    jump_threshold=0.00001,
    min_strain_decrease_length=2,
    max_slippages_per_test=10
)

# Conservative configuration (only major errors)
conservative_config = TensileTestConfig(
    jump_threshold=0.001,
    min_strain_decrease_length=10,
    max_slippages_per_test=3
)

# Manual segmentation (no automatic detection)
manual_config = TensileTestConfig(
    segmentation_method='manual',
    elastic_strain_range=(0.0005, 0.002)
)

Example 4: Visualization

Create publication-quality interactive plots for single tests and batch comparisons.

Scenario

Generate interactive plots with annotations for presentation or publication.

Single Test Visualization

from tensile import TensileTest

# Analyze test
test = (TensileTest()
        .load("data/316L_specimen_A1.csv")
        .validate()
        .clean()
        .segment()
        .analyze())

# Create interactive plot with all features
fig = test.plot(
    show_segments=True,      # Highlight elastic region
    show_properties=True,    # Show Rp0.2 and Rm markers
    show_slippages=True,     # Mark slippage locations
    title="316L Stainless Steel - Specimen A1",
    width=1000,
    height=700
)

# Display in browser
fig.show()

# Save to HTML (interactive)
fig.write_html("plots/316L_A1_interactive.html")

# Save to PNG (static, requires kaleido)
fig.write_image("plots/316L_A1.png", width=1200, height=800, scale=2)

Customizing Plots

# Get figure and customize
fig = test.plot()

# Modify layout
fig.update_layout(
    title={
        'text': "Custom Title",
        'x': 0.5,
        'xanchor': 'center',
        'font': {'size': 24, 'family': 'Arial'}
    },
    xaxis_title="Engineering Strain (%)",
    yaxis_title="Engineering Stress (MPa)",
    font_family="Arial",
    template="plotly_white",
    showlegend=True,
    legend=dict(
        x=0.02,
        y=0.98,
        bgcolor="rgba(255,255,255,0.8)"
    )
)

# Modify colors
fig.update_traces(
    line=dict(color='darkblue', width=2),
    selector=dict(name='Stress-Strain')
)

fig.show()

Example 5: Error Handling

Robust error handling and quality control in production environments.

Scenario

Processing files with potential errors, missing data, or invalid measurements.

from tensile import TensileTest
import os

def analyze_test_safely(filepath):
    """
    Analyze a test with comprehensive error handling.
    Returns (test, success, error_message).
    """
    test = TensileTest()
    
    try:
        # Load data
        if not os.path.exists(filepath):
            return test, False, f"File not found: {filepath}"
        
        test.load(filepath)
        
        # Validate
        test.validate()
        if not test.quality_report['is_valid']:
            issues = '; '.join(test.quality_report['validation_issues'])
            return test, False, f"Validation failed: {issues}"
        
        # Process with cleaning and segmentation
        test.clean().segment().analyze()
        
        # Check analysis success
        if not test.results.get('analysis_successful', False):
            return test, False, "Analysis failed to compute properties"
        
        # Check for warnings
        if test.quality_report['warnings']:
            print(f"Warnings for {filepath}:")
            for warning in test.quality_report['warnings']:
                print(f"  - {warning}")
        
        return test, True, None
        
    except FileNotFoundError as e:
        return test, False, f"File error: {str(e)}"
    except ValueError as e:
        return test, False, f"Value error: {str(e)}"
    except Exception as e:
        return test, False, f"Unexpected error: {str(e)}"

# Use the safe function
files = ["specimen_1.csv", "specimen_2.csv", "specimen_3.csv"]

results = []
for filepath in files:
    test, success, error = analyze_test_safely(filepath)
    
    if success:
        print(f"✓ {filepath}: E={test.results['youngs_modulus_GPa']:.2f} GPa")
        results.append(test.results)
    else:
        print(f"✗ {filepath}: {error}")

print(f"\nSuccessfully analyzed {len(results)}/{len(files)} tests")

Batch Error Handling

from tensile import TensileTestBatch

# Load batch
batch = TensileTestBatch.from_folder("data/mixed_quality/")

# Analyze all (continues even if some fail)
batch.analyze_all()

# Get quality summary
quality = batch.quality_summary()

# Filter valid and invalid tests
valid_tests = [t for t in batch.tests if t.quality_report['is_valid']]
invalid_tests = [t for t in batch.tests if not t.quality_report['is_valid']]

print(f"Valid: {len(valid_tests)}, Invalid: {len(invalid_tests)}")

# Report issues
for test in invalid_tests:
    filename = test.metadata.get('filename', 'Unknown')
    print(f"\n{filename}:")
    if test.quality_report['errors']:
        print("  Errors:")
        for error in test.quality_report['errors']:
            print(f"    - {error}")
    if test.quality_report['validation_issues']:
        print("  Validation Issues:")
        for issue in test.quality_report['validation_issues']:
            print(f"    - {issue}")

# Export only valid tests
if valid_tests:
    valid_batch = TensileTestBatch()
    valid_batch.tests = valid_tests
    valid_batch.export_summary("valid_tests_only.xlsx")

Example 6: Working with Metadata

Add and use metadata for organizing and filtering test results.

Scenario

Organize tests by material, batch, orientation, and test conditions.

from tensile import TensileTest, TensileTestBatch

# Add metadata during loading
test = TensileTest.from_csv(
    "specimen.csv",
    metadata={
        'material': '316L',
        'batch': 'A',
        'specimen_id': '1',
        'orientation': 'horizontal',
        'test_date': '2026-01-15',
        'test_temperature': 20,
        'test_speed_mm_min': 2.0,
        'operator': 'Jane Smith',
        'machine': 'Instron_5982'
    }
)

test.validate().clean().segment().analyze()

# Metadata is included in exports
test.export("results_with_metadata.csv", include_metadata=True)

# Use metadata in analysis
print(f"Material: {test.metadata['material']}")
print(f"Batch: {test.metadata['batch']}")
print(f"E = {test.results['youngs_modulus_GPa']:.2f} GPa")

Batch Metadata Analysis

import pandas as pd
from tensile import TensileTestBatch

# Load batch
batch = TensileTestBatch.from_folder("data/multi_batch/")
batch.analyze_all()

# Extract metadata and results into DataFrame
data = []
for test in batch.tests:
    if test.results.get('analysis_successful'):
        row = {
            'material': test.metadata.get('material'),
            'batch': test.metadata.get('batch'),
            'orientation': test.metadata.get('orientation'),
            'E_GPa': test.results['youngs_modulus_GPa'],
            'Rp02_MPa': test.results['Rp02_MPa'],
            'Rm_MPa': test.results['Rm_MPa'],
            'At_percent': test.results['At_percent']
        }
        data.append(row)

df = pd.DataFrame(data)

# Group by batch
batch_stats = df.groupby('batch').agg({
    'E_GPa': ['mean', 'std'],
    'Rp02_MPa': ['mean', 'std'],
    'Rm_MPa': ['mean', 'std']
})
print(batch_stats)

# Group by orientation
orientation_stats = df.groupby('orientation').mean()
print(orientation_stats)

Automatic Metadata Loading

# If you have companion metadata files (.id_metal or .is_metal)
# they are automatically loaded

test = TensileTest()
test.load("specimen.csv", load_metadata=True)  # Default behavior

# Metadata from companion files is now available
print(test.metadata)
# {'filepath': '...', 'filename': 'specimen.csv', 'material': '316L', ...}

Example 7: Quality Control Workflow

Comprehensive quality control for production testing laboratories.

Scenario

QC workflow with automated acceptance criteria, outlier flagging, and report generation.

from tensile import TensileTestBatch
import pandas as pd

# Define acceptance criteria for 316L stainless steel
ACCEPTANCE_CRITERIA = {
    'youngs_modulus_GPa': (180, 210),
    'Rp02_MPa': (250, 350),
    'Rm_MPa': (550, 700),
    'At_percent': (35, 60)
}

def check_acceptance(test, criteria):
    """Check if test results meet acceptance criteria."""
    if not test.results.get('analysis_successful'):
        return False, ['Analysis failed']
    
    failures = []
    for prop, (min_val, max_val) in criteria.items():
        value = test.results.get(prop)
        if value is None:
            failures.append(f"{prop}: not calculated")
        elif value < min_val:
            failures.append(f"{prop}: {value:.2f} below minimum {min_val}")
        elif value > max_val:
            failures.append(f"{prop}: {value:.2f} above maximum {max_val}")
    
    return len(failures) == 0, failures

# Load and analyze batch
batch = TensileTestBatch.from_folder("data/316L_production/")
batch.analyze_all()

# Quality control report
qc_data = []
for test in batch.tests:
    filename = test.metadata.get('filename', 'Unknown')
    
    # Check acceptance
    accepted, failures = check_acceptance(test, ACCEPTANCE_CRITERIA)
    
    # Compile QC data
    qc_row = {
        'Specimen': filename,
        'Valid': test.quality_report['is_valid'],
        'Complete': test.quality_report.get('is_complete', False),
        'Slippages': test.quality_report['num_slippages_detected'],
        'Accepted': accepted,
        'Failures': '; '.join(failures) if failures else 'None',
        'E_GPa': test.results.get('youngs_modulus_GPa'),
        'Rp02_MPa': test.results.get('Rp02_MPa'),
        'Rm_MPa': test.results.get('Rm_MPa'),
        'At_percent': test.results.get('At_percent')
    }
    qc_data.append(qc_row)

# Create QC DataFrame
qc_df = pd.DataFrame(qc_data)

# Summary statistics
total = len(qc_df)
valid = qc_df['Valid'].sum()
complete = qc_df['Complete'].sum()
accepted = qc_df['Accepted'].sum()

print("=== Quality Control Summary ===")
print(f"Total tests: {total}")
print(f"Valid: {valid} ({valid/total*100:.1f}%)")
print(f"Complete: {complete} ({complete/total*100:.1f}%)")
print(f"Accepted: {accepted} ({accepted/total*100:.1f}%)")
print(f"\nRejected tests:")
print(qc_df[~qc_df['Accepted']][['Specimen', 'Failures']])

# Export QC report
qc_df.to_excel("QC_Report_316L.xlsx", index=False)
print("\nQC report exported to QC_Report_316L.xlsx")

Example 8: Advanced Usage

Advanced techniques for custom analysis and integration.

Custom Property Calculations

import numpy as np
from tensile import TensileTest

# Standard analysis
test = (TensileTest()
        .load("specimen.csv")
        .validate()
        .clean()
        .segment()
        .analyze())

# Add custom calculations
strain = test.cleaned_data[test._strain_col].values
stress = test.cleaned_data[test._stress_col].values

# Calculate toughness (area under entire curve)
toughness = np.trapz(stress, strain)
test.results['toughness_MJ_m3'] = toughness

# Calculate resilience (area under elastic curve)
elastic_end = test.segments['elastic_end']
resilience = np.trapz(stress[:elastic_end], strain[:elastic_end])
test.results['resilience_MJ_m3'] = resilience

# Calculate strain hardening exponent (n-value)
# From plastic region: σ = K * ε^n
plastic_start = test.segments.get('yield_index', 0)
rm_index = np.argmax(stress)

plastic_strain = strain[plastic_start:rm_index]
plastic_stress = stress[plastic_start:rm_index]

if len(plastic_strain) > 10:
    # Log-log fit
    log_strain = np.log(plastic_strain + 0.002)  # Offset to avoid log(0)
    log_stress = np.log(plastic_stress)
    
    from scipy.stats import linregress
    result = linregress(log_strain, log_stress)
    n_value = result.slope
    k_value = np.exp(result.intercept)
    
    test.results['strain_hardening_exponent_n'] = n_value
    test.results['strength_coefficient_K_MPa'] = k_value

print(f"Toughness: {test.results['toughness_MJ_m3']:.2f} MJ/m³")
print(f"Resilience: {test.results['resilience_MJ_m3']:.4f} MJ/m³")
print(f"Strain hardening exponent (n): {test.results.get('strain_hardening_exponent_n', 'N/A')}")
print(f"Strength coefficient (K): {test.results.get('strength_coefficient_K_MPa', 'N/A'):.1f} MPa")

Integration with Other Libraries

from tensile import TensileTestBatch
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd

# Load and analyze batch
batch = TensileTestBatch.from_folder("data/316L_batch/")
batch.analyze_all()

# Extract data for statistical analysis
data = []
for test in batch.tests:
    if test.results.get('analysis_successful'):
        data.append({
            'E': test.results['youngs_modulus_GPa'],
            'Rp02': test.results['Rp02_MPa'],
            'Rm': test.results['Rm_MPa'],
            'At': test.results['At_percent']
        })

df = pd.DataFrame(data)

# Statistical analysis with seaborn
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

# Histograms
sns.histplot(df['E'], kde=True, ax=axes[0, 0])
axes[0, 0].set_title("Young's Modulus Distribution")
axes[0, 0].set_xlabel("E (GPa)")

sns.histplot(df['Rp02'], kde=True, ax=axes[0, 1])
axes[0, 1].set_title("Yield Strength Distribution")
axes[0, 1].set_xlabel("Rp0.2 (MPa)")

sns.histplot(df['Rm'], kde=True, ax=axes[1, 0])
axes[1, 0].set_title("UTS Distribution")
axes[1, 0].set_xlabel("Rm (MPa)")

sns.histplot(df['At'], kde=True, ax=axes[1, 1])
axes[1, 1].set_title("Elongation Distribution")
axes[1, 1].set_xlabel("At (%)")

plt.tight_layout()
plt.savefig("statistical_analysis.png", dpi=300)
plt.show()

# Correlation matrix
correlation = df.corr()
print("\nCorrelation Matrix:")
print(correlation)

# Box plots for outlier visualization
fig, ax = plt.subplots(1, 4, figsize=(16, 4))
for i, col in enumerate(['E', 'Rp02', 'Rm', 'At']):
    sns.boxplot(y=df[col], ax=ax[i])
    ax[i].set_title(col)
plt.tight_layout()
plt.savefig("box_plots.png", dpi=300)
plt.show()

Exporting for External Tools

from tensile import TensileTestBatch
import json

batch = TensileTestBatch.from_folder("data/")
batch.analyze_all()

# Export to JSON for web applications
json_data = []
for test in batch.tests:
    if test.results.get('analysis_successful'):
        json_data.append({
            'filename': test.metadata.get('filename'),
            'material': test.metadata.get('material'),
            'results': {
                'E': float(test.results['youngs_modulus_GPa']),
                'Rp02': float(test.results['Rp02_MPa']),
                'Rm': float(test.results['Rm_MPa']),
                'At': float(test.results['At_percent'])
            },
            'quality': {
                'valid': bool(test.quality_report['is_valid']),
                'slippages': int(test.quality_report['num_slippages_detected'])
            }
        })

with open("results.json", "w") as f:
    json.dump(json_data, f, indent=2)

print("Exported to results.json")