A general-purpose analysis and figure generation tool for tensile test data exported from Instron universal testing machines using Bluehill software.
For each tensile specimen, the tool computes Young's modulus, upper yield stress and strain, cold-drawing plateau stress, elongation at break, UTS, and toughness from raw Bluehill .csv exports. It then generates a standard set of publication-quality figures — stress–strain grids, overlays, and mechanical property summary plots — configurable for any two-additive polymer composite system.
All study-specific settings (file paths, formulation names, additive levels, colours, figure layout) live in a single config.py file. The analysis code itself contains no material- or project-specific references.
If you use this tool in published work, please cite both the software and the original study for which it was developed:
Software:
[JOSS citation to follow upon publication]
Original study (PEO–TA–GO composite):
Powar A, Ismail A, Vijayaraghavan A, Iliut M (2025).
Brittle-to-Ductile Transition and Exceptional Toughness in Ultra-High Molecular
Weight PEO–Tannic Acid–Graphene Oxide Composite Films with Recyclable Mechanical
Performance. Advanced Composites and Hybrid Materials.
[preprint DOI to follow]
| Package | Minimum version |
|---|---|
| Python | 3.9 |
| NumPy | 1.21 |
| SciPy | 1.7 |
| Matplotlib | 3.5 |
| Pandas | 1.3 |
Install all dependencies:
pip install -r requirements.txtOr with conda:
conda install numpy scipy matplotlib pandas- Edit
config.py— setDATA_DIRS,OUT_DIR, and define your formulations inSAMPLE_DEFS. See Configuration below. - Run:
python bluehill_tensile_analysis.py
cp config_peo_ta_go.py config.py
# edit DATA_DIRS and OUT_DIR in config.py to point to your copy of the raw data
python bluehill_tensile_analysis.pyThe script expects Bluehill's default export folder structure. Each formulation is exported to a sub-folder named <formulation_name>.is_tens_Exports/, containing one .csv file per specimen:
<DATA_DIR>/
Neat_Polymer.is_tens_Exports/
Neat_Polymer_1.csv
Neat_Polymer_2.csv
Neat_Polymer_3.csv
Polymer_5TA.is_tens_Exports/
Polymer_5TA_1.csv
Polymer_5TA_2.csv
Polymer_5TA_0.5GO.is_tens_Exports/
...
The folder names you use (e.g. Neat_Polymer, Polymer_5TA) must exactly match the folder_name field in SAMPLE_DEFS in config.py.
Each .csv file is a Bluehill export with a variable-length header block followed by columnar data. The script locates the data section automatically by finding the row beginning with Time. The columns read are:
| Column index | Content | Units |
|---|---|---|
| 3 | Tensile stress | MPa |
| 5 | Tensile strain (crosshead) | % |
Other columns are ignored. If your Bluehill export uses different column ordering, update the column indices in load_csv() in bluehill_core.py.
All settings are in config.py. The file is divided into nine numbered sections with inline documentation. Key sections:
DATA_DIRS = {
'series_a': '/path/to/virgin_data',
'series_b': '/path/to/recycled_data', # omit if single series
}
OUT_DIR = '/path/to/output'Each formulation is one tuple in SAMPLE_DEFS:
SAMPLE_DEFS = [
# (modifier1_wt%, modifier2_wt%, folder_name, regime, series_name)
(0, 0, 'Neat_Polymer', 'brittle', 'series_a'),
(5, 0, 'Polymer_5mod1', 'draw', 'series_a'),
(5, 0.5, 'Polymer_5mod1_0.5mod2', 'draw', 'series_a'),
(5, 1, 'Polymer_5mod1_1mod2', 'draw', 'series_a'),
(10, 0, 'Polymer_10mod1', 'draw', 'series_a'),
]- modifier1_wt% — the first additive (e.g. a plasticiser); becomes the row variable in panel figures
- modifier2_wt% — the second additive (e.g. a filler); becomes the x-axis in property summary plots
- regime —
'brittle','draw', or'stiff'(informational only; does not affect any calculation) - series_name — must match a key in
DATA_DIRS
ADDITIVE_VALS = [0, 0.5, 1, 2, 5] # modifier2 levels tested
ADDITIVE_COLORS = {0: '#444444', 0.5: '#5BC8E2', ...}
ADDITIVE_LABELS = {0: '0 wt% GO', 0.5: '0.5 wt% GO', ...}See the inline comments in config.py for full documentation of these sections.
config_peo_ta_go.py is a complete, commented configuration for the study described in Powar et al. (2025). It demonstrates how to set up a two-modifier, two-series study with five additive loadings across eleven formulations per series.
Material system:
- Base polymer: ultra-high molecular weight poly(ethylene oxide) (PEO)
- Modifier 1: tannic acid (TA) at 2 and 4 wt%
- Modifier 2: graphene oxide (GO) at 0, 0.5, 1, 2, and 5 wt%
- Two series: virgin (as-cast) and recycled (dissolution-recast)
Three mechanical regimes observed:
- Regime I (
'brittle'): fracture below 20% strain — pure PEO baseline - Regime II (
'draw'): cold-drawing to large elongation — TA-modified, low GO - Regime III (
'stiff'): stiffened cold-drawing — 5 wt% GO
The script generates up to twelve PNG figures, depending on how many series and modifier levels are configured. Output filenames are set in FIGURE_FILENAMES in config.py.
| Figure type | Content |
|---|---|
SS_*_panels.png |
Grid of all replicate curves (rows = modifier1 levels, columns = additive loadings) |
SS_*_overlay.png |
Two-panel overlay by additive loading for a single modifier1 level (initial region + full curve) |
SS_reference.png |
All replicates of the baseline / reference formulation |
Props_*.png |
6-panel property summary: E, σ_y, σ_draw, UTS, ε_b, U vs. additive content |
Comparison_*.png |
Series A vs. B comparison for a given modifier1 level |
SS_representative_panels.png |
Multi-panel figure: one representative curve per formulation |
Add a tuple to SAMPLE_DEFS in config.py and create the corresponding Bluehill export folder:
SAMPLE_DEFS = [
...
(5, 3, 'Polymer_5mod1_3mod2', 'draw', 'series_a'), # new intermediate loading
]Add the new additive value to ADDITIVE_VALS and assign it a colour in ADDITIVE_COLORS.
Add the value to MODIFIER_LEVELS, add an entry to MODIFIER_STYLES, and add the corresponding formulations to SAMPLE_DEFS.
The modulus regression window (0.3–2.5% strain) is defined in compute_modulus():
lo, hi = 0.003, 0.025 # fractional strain (multiply by 100 for %)Adjust these bounds if your material has a different linear-elastic range.
The plateau window (yield_idx + 8 to yield_idx + 80) is in compute_draw_stress(). The bounds reflect data acquired at ~10–20 Hz at 50 mm/min with a 25 mm gauge length. For different acquisition rates or speeds, adjust to span approximately the first 5–10% of the cold-drawing plateau.
SAVE_DPI = 600 # in config.py — higher DPI increases file size substantiallySpecimens can be excluded globally (same exclusion applied to every series) or
per-series (different exclusions for each series). Both forms are accepted by
OUTLIERS in config.py:
# Flat set — excludes these filenames from ALL series
OUTLIERS = {'Formulation_A_2.csv', 'Formulation_B_1.csv'}
# Dict — excludes different specimens per series (recommended for multi-series studies)
OUTLIERS = {
'series_a': {'Formulation_A_2.csv'}, # excluded from series_a only
'series_b': {'Formulation_B_1.csv'}, # excluded from series_b only
}Both forms accept the same filename-based exclusion syntax. Use the dict form when a specimen should be excluded from one series (e.g. recycled) but not another (e.g. virgin).
find_fracture_idx() uses two criteria to handle both fracture modes robustly. The ductile trigger (last point above 0.30 MPa) prevents including the zero-load noise tail after specimen separation. The brittle trigger (stress dropping to below 10% of peak within 15 points of the peak) correctly identifies catastrophic fracture and avoids integrating the post-fracture noise tail into toughness. Both thresholds were validated against a dataset of 110+ specimens. For materials with very different stiffness or at significantly different testing speeds, these may need recalibration.
find_upper_yield() applies Savitzky–Golay smoothing (window 9, order 2) to the stress signal before searching for the first local maximum. Savitzky–Golay filtering is used in preference to moving averages because it preserves peak positions and heights. A ±5-point neighbourhood search in the raw (unsmoothed) signal then recovers the true yield stress without the systematic reduction introduced by smoothing.
find_representative_idx() selects the specimen closest to the formulation mean in a z-scored three-property space {E, ε_b, U}. This three-property criterion is preferable to single-property selection (e.g. mean modulus alone) because it identifies the specimen that is simultaneously typical in stiffness, ductility, and energy absorption.
- All figures for the PEO–TA–GO study were generated with the exact script version archived in this repository.
- The
OUTLIERSdict inconfig_peo_ta_go.pydocuments all three excluded specimens and the reason for each (pre-loaded virgin PEO specimen; premature fracture in virgin 2TA-2GO; grip slippage in recycled 2TA-5GO). - Figure dimensions and font sizes are set so that text is ≥ 7 pt when reproduced at final journal column width (~85 mm single column / ~170 mm double column).
MIT License. See LICENSE for full text.
Aravind Vijayaraghavan and Maria Iliut Department of Materials and National Graphene Institute, The University of Manchester, UK
This tool was originally developed for the PEO–TA–GO composite study (Powar et al., 2025). Contributions for other material systems are welcome via pull request.