User Guide

🔬 Tensile
v0.4.0
Automated Tensile Test Analysis

Getting Started

Tensile follows a simple, intuitive API inspired by scikit-learn. The basic workflow involves:

  1. Load CSV data
  2. Validate data quality
  3. Clean data (detect/correct errors)
  4. Segment into elastic/plastic regions
  5. Analyze to calculate properties
  6. Export or visualize results

Basic Workflow

from tensile import TensileTest

# Create a test instance and run the pipeline
test = (TensileTest()
        .load("specimen.csv")
        .validate()
        .clean()
        .segment()
        .analyze())

# View results
print(test.summary())

# Create plot
fig = test.plot()
fig.show()

Single Test Analysis

Loading Data

The library supports CSV files with multi-row headers. The column names for stress and strain are automatically detected.

from tensile import TensileTest

# Basic loading
test = TensileTest()
test.load("path/to/specimen.csv")

# Or using the class method
test = TensileTest.from_csv("path/to/specimen.csv")

# With custom metadata
test = TensileTest.from_csv(
    "specimen.csv",
    metadata={
        'material': '316L',
        'batch': 'A',
        'specimen_id': '1',
        'test_date': '2026-01-15'
    }
)
Automatic Column Detection: The library automatically identifies stress and strain columns by looking for keywords like "Tensile stress", "Stress", "σ" and "Tensile strain", "Strain", "ε" in column names.

CSV File Format

The expected CSV format:

Example CSV Structure:

Time;Strain;Stress;Load
s;%;MPa;N
0.00;0.000;0.0;0.0
0.10;0.005;10.5;150.2
0.20;0.010;21.0;300.5
...

Method Chaining

All main methods return self, allowing for elegant method chaining:

# Complete pipeline in one chain
test = (TensileTest()
        .load("specimen.csv")
        .validate()
        .clean()
        .segment()
        .analyze())

# With custom configuration
from tensile import TensileTestConfig

config = TensileTestConfig(
    jump_threshold=0.00001,
    segmentation_method='piecewise_regression'
)

test = (TensileTest(config=config)
        .load("specimen.csv")
        .validate()
        .clean()
        .segment()
        .analyze())

Accessing Results

# Get results dictionary
results = test.results
print(results['youngs_modulus_GPa'])
print(results['Rp02_MPa'])
print(results['Rm_MPa'])
print(results['At_percent'])

# Get formatted summary
summary = test.summary()
print(summary)

# Check quality report
print(test.quality_report['is_valid'])
print(test.quality_report['num_slippages_detected'])
print(test.quality_report['warnings'])

Batch Processing

Process multiple tests simultaneously with summary statistics and outlier detection.

Loading Multiple Tests

From Folder

from tensile import TensileTestBatch

# Load all CSV files from a folder
batch = TensileTestBatch.from_folder("Raw data/316L_A/")

# With custom pattern
batch = TensileTestBatch.from_folder(
    "Raw data/316L_A/",
    pattern="*.csv",
    recursive=False
)

# With shared configuration
from tensile import TensileTestConfig

config = TensileTestConfig(jump_threshold=0.00001)
batch = TensileTestBatch.from_folder("Raw data/316L_A/", config=config)

From File List

files = [
    "specimen_1.csv",
    "specimen_2.csv",
    "specimen_3.csv"
]

batch = TensileTestBatch.from_csv_list(files)

Analyzing Batches

# Analyze all tests sequentially
batch.analyze_all()

# Analyze with parallel processing (faster for large batches)
batch.analyze_all(parallel=True, n_jobs=4)

# Check progress
print(f"Valid tests: {batch.get_valid_count()}/{len(batch.tests)}")

Summary Statistics

# Get summary DataFrame
summary = batch.summary_statistics()
print(summary)

# Access specific statistics
mean_E = summary.loc['youngs_modulus_GPa', 'mean']
std_Rp02 = summary.loc['Rp02_MPa', 'std']
cv_Rm = summary.loc['Rm_MPa', 'CV%']
Summary Includes: mean, std (standard deviation), CV% (coefficient of variation), min, max, and count for each mechanical property.

Outlier Detection

# Z-score method (default threshold=3)
outliers_z = batch.identify_outliers(method='zscore', threshold=2.5)

# IQR method
outliers_iqr = batch.identify_outliers(method='iqr', factor=1.5)

# View outliers
print(outliers_z)

# Get outlier test names
outlier_tests = outliers_z[outliers_z['is_outlier']]['test_name'].tolist()

Batch Quality Assessment

# Get quality summary across all tests
quality_df = batch.quality_summary()

# Count issues
total_slippages = quality_df['num_slippages_detected'].sum()
valid_tests = quality_df['is_valid'].sum()
incomplete_tests = (~quality_df['is_complete']).sum()

Configuration

Customize analysis parameters using TensileTestConfig:

from tensile import TensileTestConfig

config = TensileTestConfig(
    # Segmentation parameters
    elastic_strain_range=(0.0005, 0.0025),
    offset_strain=0.002,  # 0.2% for Rp0.2
    segmentation_method='piecewise_regression',
    
    # Cleaning parameters
    min_strain_decrease_length=5,
    jump_threshold=0.0001,
    max_slippages_per_test=5,
    
    # Validation parameters
    min_data_points=100,
    max_strain_value=1.0,
    max_stress_value=5000.0,
    
    # Visualization parameters
    show_annotations=True,
    show_segments=True,
    show_slippages=True,
    plot_width=900,
    plot_height=600
)

# Use configuration
test = TensileTest(config=config)

Key Configuration Parameters

Parameter Default Description
elastic_strain_range (0.0005, 0.0025) Strain range for elastic region detection
offset_strain 0.002 Offset for Rp0.2 calculation (0.2%)
jump_threshold 0.0001 Minimum strain jump for slippage detection
segmentation_method 'piecewise_regression' Method for elastic region detection
max_slippages_per_test 5 Maximum slippages to detect per specimen

Data Validation

The validation step checks data quality and completeness:

test = TensileTest()
test.load("specimen.csv")
test.validate()

# Check validation results
if test.quality_report['is_valid']:
    print("Data is valid")
else:
    print("Validation issues found:")
    for issue in test.quality_report['validation_issues']:
        print(f"  - {issue}")

Validation Checks

Validation Severity Levels

Level Description Action
ERROR Critical issue preventing analysis Analysis stops
WARNING Potential data quality issue Analysis continues, flagged in report
INFO Informational message Logged for reference

Data Cleaning

Automatically detect and correct measurement errors:

Slippage Detection

The library can detect multiple types of errors:

test = TensileTest()
test.load("specimen.csv").validate().clean()

# Check slippage detection results
report = test.quality_report
print(f"Slippages detected: {report['num_slippages_detected']}")
print(f"Slippages corrected: {report['num_slippages_corrected']}")

# View slippage details
for slip in report['slippage_details']:
    print(f"  Slippage at index {slip['index']}, "
          f"magnitude: {slip['magnitude']:.6f}, "
          f"confidence: {slip['confidence']:.2f}")

Cleaning Steps

  1. Trim initial slack: Remove pre-test data where stress is near zero
  2. Detect slippages: Identify strain decreases and jumps
  3. Correct slippages: Apply cascading corrections
  4. Truncate post-fracture: Remove data after fracture
Note: Cleaning is automatic but can be disabled by setting the appropriate configuration parameters. Always review the quality report to ensure corrections are appropriate.

Segmentation

Segmentation identifies the elastic, plastic, and necking regions of the stress-strain curve.

Automatic Elastic Region Detection

The default method uses piecewise regression to automatically find the elastic region:

test = TensileTest()
test.load("specimen.csv").validate().clean().segment()

# Check segmentation results
segments = test.segments
print(f"Elastic region: {segments['elastic_start']} to {segments['elastic_end']}")
print(f"Yield point: {segments['yield_index']}")
print(f"Segmentation confidence: {test.quality_report['segmentation_confidence']:.2f}")

Segmentation Methods

Method Description Advantages
piecewise_regression Statistical breakpoint detection Fully automated, robust
manual Fixed strain range from config Predictable, fast
# Use manual segmentation
from tensile import TensileTestConfig

config = TensileTestConfig(
    segmentation_method='manual',
    elastic_strain_range=(0.0005, 0.002)
)

test = TensileTest(config=config)
test.load("specimen.csv").validate().clean().segment()

Analysis

Calculate mechanical properties from the stress-strain curve:

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

# Access calculated properties
E = test.results['youngs_modulus_GPa']
Rp02 = test.results['Rp02_MPa']
Rm = test.results['Rm_MPa']
At = test.results['At_percent']

print(f"Young's Modulus: {E:.2f} GPa")
print(f"Yield Strength (Rp0.2): {Rp02:.2f} MPa")
print(f"Ultimate Tensile Strength: {Rm:.2f} MPa")
print(f"Total Elongation: {At:.2f}%")

Calculated Properties

Property Symbol Method
Young's Modulus E Linear regression on elastic region
Yield Strength Rp0.2 0.2% offset method with interpolation
Ultimate Tensile Strength Rm Maximum stress value
Total Elongation At Final strain at fracture

Results Dictionary Structure

{
    'youngs_modulus_GPa': 193.5,
    'Rp02_MPa': 280.3,
    'Rp02_strain': 0.00345,
    'Rm_MPa': 625.8,
    'Rm_strain': 0.185,
    'At_percent': 45.2,
    'elastic_modulus_r_squared': 0.9998,
    'elastic_region_points': 125
}

Visualization

Create interactive, publication-quality plots using Plotly:

Single Test Plots

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

# Create interactive plot
fig = test.plot()

# Show in browser
fig.show()

# Save to file
fig.write_html("specimen_plot.html")

# Export to static image (requires kaleido)
fig.write_image("specimen_plot.png")

Customizing Plots

# Customize plot appearance
fig = test.plot(
    show_segments=True,       # Highlight elastic region
    show_properties=True,     # Show Rp0.2 and Rm markers
    show_slippages=True,      # Mark detected slippages
    title="316L Specimen A-1",
    width=1000,
    height=700
)

# Modify plot after creation
fig.update_layout(
    font_family="Arial",
    title_font_size=20
)

Batch Comparison Plots

batch = TensileTestBatch.from_folder("Raw data/316L_A/")
batch.analyze_all()

# Overlay all curves
fig = batch.plot_all(overlay=True)
fig.show()

# Separate subplots
fig = batch.plot_all(overlay=False, cols=3)
fig.show()

# Plot only valid tests
fig = batch.plot_all(overlay=True, include_invalid=False)
fig.show()
Tip: Interactive plots allow zooming, panning, and hovering to see exact values. Use the toolbar in the top-right corner of the plot.

Exporting Results

Single Test Export

# Export to CSV
test.export("results.csv")

# Export with metadata
test.export("results.csv", include_metadata=True)

# Get formatted summary string
summary = test.summary()
print(summary)

# Save summary to text file
with open("summary.txt", "w") as f:
    f.write(test.summary())

Batch Export

# Export comprehensive Excel workbook with multiple sheets
batch.export_summary("batch_results.xlsx")

# Sheets included:
# - Summary: Aggregated statistics (mean, std, CV%, etc.)
# - Individual_Results: All test results
# - Quality_Report: Validation and error information
# - Outliers: Outlier detection results

# Export individual results to CSV
batch.export_individual_csv("results/")

# Export quality report
quality_df = batch.quality_summary()
quality_df.to_csv("quality_report.csv")

Metadata Management

Adding Metadata

# During loading
test = TensileTest.from_csv(
    "specimen.csv",
    metadata={
        'material': '316L',
        'batch': 'A',
        'specimen_id': '1',
        'test_date': '2026-01-15',
        'operator': 'John Doe',
        'test_temperature': 20,
        'test_speed_mm_min': 2.0
    }
)

# After loading
test = TensileTest()
test.load("specimen.csv")
test.metadata['material'] = '316L'
test.metadata['batch'] = 'A'

Automatic Metadata Loading

The library can automatically load metadata from companion files:

# If you have: specimen.csv and specimen.id_metal or specimen.is_metal
test = TensileTest()
test.load("specimen.csv", load_metadata=True)

# Metadata is automatically loaded from companion files
print(test.metadata)

Advanced Usage

Conditional Processing

test = TensileTest()
test.load("specimen.csv")

# Only proceed if validation passes
test.validate()
if test.quality_report['is_valid']:
    test.clean().segment().analyze()
else:
    print("Validation failed, skipping analysis")
    print(test.quality_report['validation_issues'])

Accessing Raw Data

# Access original data
raw_strain = test.raw_data[test._strain_col].values
raw_stress = test.raw_data[test._stress_col].values

# Access cleaned data
if test.cleaned_data is not None:
    clean_strain = test.cleaned_data[test._strain_col].values
    clean_stress = test.cleaned_data[test._stress_col].values

Custom Analysis

import numpy as np

# Run standard pipeline
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 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

Batch Filtering

batch = TensileTestBatch.from_folder("Raw data/316L/")
batch.analyze_all()

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

# Filter by metadata
material_A = [t for t in batch.tests if t.metadata.get('batch') == 'A']

# Filter by property value
high_strength = [
    t for t in batch.tests 
    if t.results.get('Rm_MPa', 0) > 600
]
Next Steps: Explore the API Reference for detailed documentation of all classes and methods, or check out Examples for more practical usage scenarios.