▶ Try it in your browser — no installation required. Upload an XLS/XLSX/CSV, or use examples/reference_mesoporous.xlsx from this repo.
Validated against published reference values. On the BETSI round-robin
isotherms (Osterrieth et al., Adv. Mater. 2022, 34, 2201502), the
Rouquerol range selection reproduces the published BET areas for HKUST-1
(1556 m² g⁻¹) and Zeolite-13X (833 m² g⁻¹) to within 1.1 %. The classical
0.05–0.35 p/p₀ window gives negative C constants on both and underestimates
them by 24–27 %. This comparison runs in CI on every commit
(tests/test_betsi_reference.py).
Publication-Quality BET/BJH + T-Plot Analysis Tool
Author: Hoda Jafari | MIT License
ODS kinetics & catalytic activity?
→ See CatLab-Tools
A Python tool for publication-quality physisorption analysis from BET instrument XLS/XLSX output, with a Streamlit web app.
Designed for PhD-level materials characterisation — covers full IUPAC 2015 isotherm and hysteresis classification, BET regression with validity checks, Rouquerol auto BET range selection, BJH pore size distribution, T-Plot micropore analysis, and a Langmuir surface-area model.
Developed and validated for graphene-like carbon nitride (C₃N₄), MOFs, zeolites, and hierarchical porous materials.
Output of bet_analysis.py on the bundled reference dataset — a synthetic
Type IV isotherm whose true surface area is known by construction
(S_BET = n_m × 4.353 = 177.28 m² g⁻¹; the tool recovers 177.28, R² = 0.999999).
Reproduce it with:
python examples/make_reference_data.py
python bet_analysis.py --file examples/reference_mesoporous.xlsx --sample "reference"git clone https://github.com/Hj1308/BET_analyser.git
cd BET_analyser
pip install -r requirements.txt
# Run BET + BJH analysis (with Rouquerol auto range)
python bet_analysis.py --file C3N4.xls --sample "C3N4" --rouquerol
# Run T-Plot standalone (demo)
python tplot_analysis.py --s-bet 95.3 --vtot 0.38 --sample "C3N4"
# Or launch the web app
streamlit run app_bet.pyA plain two-column CSV isotherm (relative pressure, quantity adsorbed (cm3/g STP),
with a header row) is also accepted directly:
python bet_analysis.py --file examples/betsi_HKUST-1.csv --sample "HKUST-1" --rouquerolFor a raw isotherm — no desorption branch, no BJH table, no instrument summary — BJH, hysteresis classification, and the instrument comparison are declined rather than estimated, and the tool names what is missing instead of reporting zero.
app_bet.py provides a browser UI for the same analyses as the CLI, without
writing Python:
pip install -r requirements.txt
streamlit run app_bet.pyThe app exposes tabs for the Overview, BET, Langmuir, Rouquerol, BJH / PSD, T-Plot, and a Download tab that renders the publication figure and a CSV report. It accepts XLS, XLSX and CSV uploads, and surfaces the same IUPAC validity warnings and the t-plot sufficiency gate as the CLI.
| Module | File | Description |
|---|---|---|
| BET/BJH | bet_analysis.py |
Isotherm + hysteresis classification, BET regression, BJH PSD, cumulative pore volume, 4-panel figure |
| Rouquerol | rouquerol.py |
Auto BET linear-range selection via the four Rouquerol consistency criteria (IUPAC 2015 / ISO 9277); multi-window scan; instrument-range diagnosis |
| Langmuir | langmuir.py |
Langmuir monolayer capacity, affinity constant, surface area, and propagated regression uncertainty |
| T-Plot | tplot_analysis.py |
Harkins-Jura T-Plot: micropore volume, S_ext, pore type distribution (micro/meso/macro %), 2-panel figure |
| Web app | app_bet.py |
Streamlit UI: XLS/XLSX/CSV upload, all analyses, downloadable figure + CSV report |
| XLS reader | xls_reader.py |
Legacy .xls reader via xlrd API (bypasses pandas engine guard) |
The classical 0.05–0.35 p/p₀ window is not unique — two operators can report BET areas differing by ~20 % on the same isotherm. rouquerol.py replaces that subjective choice with the four consistency criteria of Rouquerol et al. (2007), adopted by IUPAC 2015 and ISO 9277:
| # | Criterion | Physical meaning |
|---|---|---|
| C1 | C > 0 (and intercept > 0) | Physically meaningful monolayer capacity |
| C2 | n(1 − p/p₀) increases continuously | Upper bound of the valid BET region (Rouquerol transform maximum) |
| C3 | p(n_m) lies inside the window | Monolayer pressure must be within the fitted range |
| C4 | 1/(√C + 1) ≈ p(n_m) within ±20 % | BET theory self-consistency |
All contiguous windows (≥ 4 points) are scanned; among fully valid windows the one with the most points (then highest R²) is selected. The instrument's own Starting/End point range is evaluated against the same criteria — matched by p/p₀ values, not sheet indices.
from rouquerol import select_bet_range, format_rouquerol_report
result = select_bet_range(ads[:, 0], ads[:, 1])
print(format_rouquerol_report(result, "C3N4"))Run the unit tests:
pip install pytest
pytest tests/ -vlangmuir.py provides a complementary monolayer-adsorption model alongside BET. It fits the linearised Langmuir isotherm (p/p0)/n vs p/p0 to report the monolayer capacity n_m, the affinity constant K, the specific surface area S_Langmuir = n_m × 4.353, the regression R², and the propagated first-order uncertainty on each quantity.
S_Langmuir uses the same N₂ cross-section factor as BET, so the two areas are directly comparable. However, Langmuir is not an automatic replacement for BET: it assumes monolayer adsorption on uniform, non-interacting sites, so it should be interpreted cautiously for heterogeneous, mesoporous, or multilayer-adsorption systems. It is particularly useful to compare alongside BET for Type I / microporous isotherms, where the monolayer model is often physically reasonable.
from langmuir import fit_langmuir_window, format_langmuir_report
result = fit_langmuir_window(ads[:, 0], ads[:, 1])
print(format_langmuir_report(result, "C3N4"))- 4-panel figure (300 dpi, publication-ready):
- Panel A — N₂ Adsorption–Desorption Isotherm with hysteresis fill
- Panel B — BET Plot with regression line + C constant validity flag (⚠ if C < 0)
- Panel C — BJH Differential Pore Size Distribution (adsorption branch) with N₂ cavitation marker
- Panel D — Cumulative Pore Volume + S_BET vs S_BJH comparison
- Console report: S_BET, C, Vm, Vp_total, d_avg, S_BJH, isotherm type, hysteresis type with scoring, Rouquerol range report (with
--rouquerol)
- 2-panel figure: t-plot with linear fit | pore type distribution bar
- Console report: S_BET, S_ext, S_micro, V_micro, V_meso, V_macro
Ref: Thommes et al., Pure Appl. Chem. 87, 1051–1069 (2015).
| IUPAC Type | Pore Structure | Typical Material |
|---|---|---|
| Type I(a) | Ultra-micropores < 1 nm; very sharp knee at p/p₀ < 0.01 | Zeolites, activated carbons |
| Type I(b) | Micropores 1–2.5 nm; knee extends to ~0.1 | MOFs, hierarchical carbons |
| Type II | Non-porous / macroporous; S-shaped | Silica, alumina |
| Type III | Weak adsorbate–adsorbent interaction; convex | PTFE, ice |
| Type IV | Mesoporous + hysteresis; capillary condensation | SBA-15, MCM-41 |
| Type V | Weak interaction + mesoporosity | Certain MOFs |
| Type VI | Stepped; uniform non-porous surface | Graphite |
Automatically scored using 6 physical features (area, slope ratio, closure point, plateau, flatness, loop shape).
| Type | Pore Geometry | Typical Material |
|---|---|---|
| H1 | Uniform open-ended cylinders; narrow symmetric loop | SBA-15, MCM-41 |
| H2 | Ink-bottle pores / pore blocking; triangular loop, steep desorption | Disordered silicas |
| H3 | Non-rigid slit-shaped aggregates; no limiting adsorption at p/p₀→1 | C₃N₄, clay minerals |
| H4 | Narrow slit + micropores; nearly flat parallel branches | Microporous carbons |
A t-plot micropore analysis needs adsorption points below p/p₀ ≈ 0.015. When a measurement lacks them, this tool says so rather than printing a zero:
| Before (v2.1.0) | After (v3.0.0) |
|---|---|
V_micro = 0.0 cm³/g · Micropore = 0.0 % |
⚠ Micropore analysis not possible + the reason below |
A reported zero is indistinguishable from a genuine absence of micropores. The refusal is not. The full message names what is missing:
Micropore analysis not possible: micropore volume and surface area cannot be
determined from this measurement (only 1 point(s) below p/p0 = 0.08 (need at
least 3); only 0 point(s) below p/p0 = 0.015 (need at least 1)). A t-plot
micropore analysis needs at least one adsorption point below p/p0 ~ 0.015,
ideally several lower still; check your instrument's low-pressure
specification and measurement-range setting (Thommes et al. 2015 §6.1;
Cychosz & Thommes 2018 §3). §6.1 also recommends argon at 87 K over nitrogen
at 77 K where surface functional groups interact with the N2 quadrupole.
This example message is from a measurement whose lowest adsorption point sits above p/p₀ = 0.015. The bundled reference dataset is purely mesoporous and does not trigger this gate.
| Check | Behaviour |
|---|---|
| BET C constant | UserWarning raised if C < 0 — invalid p/p₀ range; adjust start_pt/end_pt to 0.05 ≤ p/p₀ ≤ 0.35 |
| Monotonicity | UserWarning raised if BET y-values are not strictly increasing over the selected range |
| Rouquerol criteria | Four-criterion consistency check on every candidate window; PASS/FAIL reported per criterion |
| BJH branch | Adsorption branch used to avoid the ~3.4 nm N₂ cavitation artefact in desorption BJH at 77 K |
| Missing data | ValueError with descriptive message if required XLS sheets or row labels are absent |
Beyond raising on hard errors, the report (and the app) now surfaces IUPAC
validity caveats that were previously silent — a low BET C constant (interpretation
of n_m questionable when C < 50), the BET area on a Type I isotherm being an
apparent area, BJH underestimating narrow mesopores by 20–30 % below ~10 nm,
and the Gurvich-rule total pore volume being invalid without a high-p/p₀ plateau
(Thommes et al. 2015 §5.1.1, §5.2.2, §7.1, §7.2, §9).
A t-plot micropore analysis additionally requires adsorption points below
p/p₀ ≈ 0.015, ideally several lower still — check the instrument's
low-pressure specification and measurement-range setting. If the measurement
lacks them, the tool refuses to report a micropore volume rather than printing
0.0 (Thommes et al. 2015 §6.1). BJH is valid only above ~2 nm pore diameter;
below that, HK/SF or DFT methods are required (Thommes et al. 2015 §7.2, §9).
All constants are defined as named variables at the top of bet_analysis.py (no magic numbers).
| Constant | Value | Definition |
|---|---|---|
N2_BET_FACTOR |
4.353 m² g⁻¹ per cm³(STP) g⁻¹ | N₂ cross-section σ = 0.162 nm², Avogadro + molar volume |
N2_TPLOT_SLOPE_FACTOR |
15.47 m² g⁻¹ per cm³/(g·Å) | Harkins-Jura t-curve conversion |
N2_STP_TO_LIQUID |
1.5468e-3 cm³(liquid N₂) per cm³(STP) | Gurvich rule: V_liquid = V_STP × N2_STP_TO_LIQUID (77 K) |
N2_CAVITATION_NM |
3.4 nm | Forced closure diameter for N₂ at 77 K |
from bet_analysis import read_bet_xls, classify_isotherm, verify_bet
from tplot_analysis import TPlotAnalyser
# Read instrument XLS
data = read_bet_xls("C3N4.xls")
s = data["summary"]
# Isotherm classification
iso = classify_isotherm(data["ads"], data["des"])
print(iso["type"], iso["explanation"])
# BET regression with IUPAC validity check + Rouquerol auto range
bet = verify_bet(data["bet_pts"], s, ads=data["ads"])
print(f"S_BET = {bet['S_BET_calc']:.2f} m²/g | C = {bet['C']:.1f} | R² = {bet['R2']:.5f}")
# T-Plot micropore analysis
tp = TPlotAnalyser(
pressure = data["ads"][:, 0],
volume_adsorbed = data["ads"][:, 1],
s_bet = s["S_BET"],
total_pore_volume = s["Vp_total"]
)
tp.print_report(sample_name="C3N4")
tp.plot_tplot(save_path="C3N4_tplot.png", sample_name="C3N4")BET_analyser/
├── app_bet.py # Streamlit web application
├── bet_analysis.py # BET + BJH main script
├── langmuir.py # Langmuir monolayer-adsorption analysis
├── rouquerol.py # Rouquerol auto BET range selection
├── tplot_analysis.py # T-Plot analysis module
├── xls_reader.py # Legacy .xls reader (xlrd API)
├── conftest.py # pytest path configuration
├── assets/
│ └── bet_analysis_example.png # example 4-panel figure
├── examples/
│ ├── make_reference_data.py # regenerates the reference dataset
│ └── reference_mesoporous.xlsx # synthetic Type IV reference isotherm
├── tests/
│ ├── synthetic_isotherms.py # closed-form isotherm fixtures
│ ├── test_isotherm_classification.py
│ ├── test_langmuir.py
│ ├── test_rouquerol.py
│ └── test_tplot_two_segment.py
├── .streamlit/ # Streamlit config
├── .github/workflows/ # CI
├── .python-version # pinned dev Python (3.11)
├── packages.txt # Streamlit Cloud system deps
├── pyproject.toml # packaging metadata + dev extra
├── CITATION.cff # citation metadata
├── CHANGELOG.md # release history
├── LICENSE # MIT
├── requirements.txt # runtime deps (single source)
└── README.md
examples/reference_mesoporous.xlsx is a synthetic Type IV isotherm generated
from the BET equation with a known monolayer capacity, so the correct surface
area is known in advance rather than assumed. examples/make_reference_data.py
regenerates it and prints the check.
It contains no measured data. All four sheets (AdsDes, BET, BJH, Summary) derive from the same monolayer capacity and pore-size distribution, so the file is internally self-consistent: S_BET and S_BJH agree to within 1.2 %.
Every figure in this README was produced from this synthetic file. No measured instrument data is included in this repository.
- The t-plot uses the Harkins–Jura reference curve. Instrument software may use a different reference t-curve, so t-plot quantities are not directly comparable with instrument output.
- BJH is based on the Kelvin equation and loses physical validity below about 2 nm pore radius. Micropore volumes should be taken from the t-plot, not from BJH.
- On microporous reference materials the t-plot total surface area runs above the BET area; the cause is under investigation and the decomposition should be read as indicative.
- Raw two-column isotherms cannot support BJH, hysteresis classification, or comparison with instrument values. These are declined rather than estimated.
- Thommes, M. et al. Pure Appl. Chem. 2015, 87, 1051–1069. DOI: 10.1515/pac-2014-1117 — IUPAC 2015 physisorption classification
- Rouquerol, J.; Llewellyn, P.; Rouquerol, F. Stud. Surf. Sci. Catal. 2007, 160, 49–56. DOI: 10.1016/S0167-2991(07)80008-5 — Rouquerol consistency criteria
- ISO 9277:2010 — Determination of the specific surface area of solids by gas adsorption — BET method
- Osterrieth, J. W. M. et al. Adv. Mater. 2022, 34, 2201502. DOI: 10.1002/adma.202201502 — BETSI multi-region fitting
- Rouquerol, J. et al. Adsorption by Powders and Porous Solids, 2nd ed.; Academic Press, 2014.
- Gregg, S.J.; Sing, K.S.W. Adsorption, Surface Area and Porosity, 2nd ed.; Academic Press, 1982.
- Barrett, E.P.; Joyner, L.G.; Halenda, P.P. J. Am. Chem. Soc. 1951, 73, 373–380. DOI: 10.1021/ja01145a126 — BJH method
- Brunauer, S.; Emmett, P.H.; Teller, E. J. Am. Chem. Soc. 1938, 60, 309–319. DOI: 10.1021/ja01269a023 — BET theory
See CHANGELOG.md for the full release history.
| Repo | Purpose |
|---|---|
| CatLab-Tools | ODS kinetics, TOF/TON, Arrhenius, residual diagnostics |
| EISforge | EIS analysis + ML |
| sem-particle-analyzer | SEM particle sizing |
| Raman-analysis | Raman spectroscopy toolkit |
If you use BET_analyser in your research, please cite:
Jafari, H. (2026). BET_analyser: Publication-Quality BET/BJH + T-Plot Analysis Tool (v3.0.0). Zenodo.
DOI: 10.5281/zenodo.22116897
MIT — free to use, modify, and distribute.
