Getting Started
Tensile follows a simple, intuitive API inspired by scikit-learn. The basic workflow involves:
- Load CSV data
- Validate data quality
- Clean data (detect/correct errors)
- Segment into elastic/plastic regions
- Analyze to calculate properties
- 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'
}
)
CSV File Format
The expected CSV format:
- Semicolon-separated values (configurable)
- Multi-row headers allowed
- Columns for strain and stress (with units)
- Numerical data in rows
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%']
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
- Column existence: Stress and strain columns present
- Data completeness: No excessive missing values
- Value ranges: Physically reasonable values
- Monotonicity: Generally increasing strain
- Test completion: Evidence of fracture
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:
- Strain jumps: Sudden discontinuous strain increases
- Negative strain sequences: Strain decreasing (slippage)
- Post-fracture data: Data after specimen fracture
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
- Trim initial slack: Remove pre-test data where stress is near zero
- Detect slippages: Identify strain decreases and jumps
- Correct slippages: Apply cascading corrections
- Truncate post-fracture: Remove data after fracture
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()
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
]