Repository navigation
Python API and CLI Tools
python-cmat provides both a pure Python library API (cmat.py) for integration into custom scripts and Jupyter notebooks, and a standalone command-line converter (cmat2amat.py) for converting .cmat files into standard ASCII formats.
The core library revolves around the CMATReader class, which handles binary parsing, IVF segment navigation, and on-demand block decompression.
from cmat import CMATReader
# 1. Open .cmat file (supports symmetric, asymmetric, and arbitrary step sizes)
reader = CMATReader("GeE-symm.cmat")
# 2. Inspect file header and IVF metadata
info = reader.get_info()
print("Filename: ", info["filename"])
print("Shape: ", info["shape"]) # (res1, res2)
print("Matrix Mode: ", info["matrix_mode"]) # 0 = symmetric, 1 = normal, etc.
print("Step Size: ", info["step"]) # (step1, step2)
print("Total Blocks: ", info["blocks"])
# 3. Decompress the full matrix into a NumPy 2D array (int32)
# Resulting array shape is (res2, res1) = (Height/Y, Width/X)
matrix = reader.to_numpy()
print(f"Decompressed shape: {matrix.shape}")
print(f"Total matrix counts: {matrix.sum():,}")
# 4. Extract total 1D projections
# axis=0: Det 1 (X) projection; axis=1: Det 2 (Y) projection
proj_det1 = reader.get_projection(axis=0)
proj_det2 = reader.get_projection(axis=1)
# 5. Extract coincidence-gated 1D projection
# Gate channels 500 to 520 on Det 1 -> returns coincidence spectrum on Det 2
net_spec, bg_spec, raw_spec = reader.get_gate(500, 520, axis=0)
# 6. Extract 2D Banana Graphical Polygon ROI & Net Counts
# Computes pixel mask, geometric area, raw counts, and normalized subtraction
banana = reader.get_banana_roi(
polygon_peak=[[100, 100], [150, 120], [140, 160], [90, 140]],
polygon_bg=[[80, 80], [170, 100], [160, 180], [70, 160]]
)
print(f"Net Banana Area: {banana['net_counts']:,.1f} +/- {banana['net_err']:,.1f}")
# 7. Export directly to ASCII (.amat) format
reader.export_amat("GeE-symm.amat", format_type="dense")Initializes the reader, parses IVF header records, builds block offset tables, and extracts stored projections if available.
Returns a metadata dictionary containing:
-
filename: Absolute path to the matrix file. -
shape: Tuple(res1, res2)representing matrix dimensions. -
step: Tuple(step1, step2)representing sub-matrix block size. -
matrix_mode: Integer mode flag (e.g. 0 for folded symmetric, 1 for normal full). -
blocks: Total count of IVF blocks stored in the container.
Decompresses all sub-blocks into a 2D NumPy array of int32 elements.
- Returns array with shape
(res2, res1). - For symmetric matrices, automatically mirrors the triangular blocks across the diagonal (
$M_{ij} = M_{ji}$ ).
Returns a 1D NumPy array representing the total projection:
-
axis=0: Det 1 / X projection. -
axis=1: Det 2 / Y projection. - Uses IVF pre-computed projection segments when available, falling back to full-matrix summation if missing.
get_gate(gate_min: int, gate_max: int, axis: int = 0, bg_min: int = None, bg_max: int = None) -> tuple
Extracts a coincidence-gated 1D slice along the specified axis:
- Slices between
gate_minandgate_max. - If
bg_minandbg_maxare provided, performs normalized background subtraction scaled by channel window width. - Returns
(net_spectrum, bg_spectrum, raw_spectrum).
Calculates discrete pixel containment, continuous geometric Shoelace area, raw counts, area-normalized scale factor, and net background-subtracted counts for arbitrary 2D polygon ROIs:
-
polygon_peak: List of[x, y]coordinate pairs defining the Peak polygon ROI. -
polygon_bg: Optional list of[x, y]coordinate pairs defining the Background polygon ROI. - Returns dictionary containing:
-
peak: Sub-dictionary witharea_geom,pixels,counts, andvertices. -
bg: Sub-dictionary with background metrics (orNone). -
scale: Area normalization factor ($\text{Area}{\text{peak}} / \text{Area}{\text{bg}}$). -
net_counts: Area-normalized net peak counts ($C_{\text{peak}} - \text{scale} \times C_{\text{bg}}$ ). -
net_err: Statistical uncertainty$\sqrt{C_{\text{peak}} + \text{scale}^2 \times C_{\text{bg}}}$ .
-
Exports matrix counts to an ASCII .amat file:
-
format_type="dense": Rectangular grid of count values. -
format_type="sparse": Triplet list ofx y countsfor non-zero channels. -
roi: Optional(xmin, xmax, ymin, ymax)tuple to restrict exported area.
pycmat is a standalone executable Python dispatcher that automatically inspects input .cmat file headers in ndim), seamlessly forwarding all CLI flags and options to the appropriate viewer:
-
2D Matrices (
ndim == 2): Launchescmat_webviewer.py -
3D Matrices (
ndim == 3): Launchescmat3d_webviewer.py
# Launch interactive viewer for any 2D or 3D matrix (auto-detected)
./pycmat /path/to/matrix.cmat
# Forward options such as custom port, calibration, or browser settings
./pycmat run1.cmat --port 8085 --no-browser
# Execute batch headless spectroscopy commands
./pycmat GeE-symm.cmat -c "info; search 0"python-cmat provides bidirectional conversion between proprietary binary .cmat files and standard plain-text ASCII (.amat, .dat, .txt, .csv) or NumPy (.npy) files.
Converts .cmat files into dense 2D ASCII grids, sparse coordinate lists, or NumPy .npy arrays.
# Convert entire matrix to a dense 2D ASCII grid:
python3 cmat2amat.py GeE-symm.cmat -o GeE-symm.amat
# Convert a specific Region of Interest (ROI):
python3 cmat2amat.py GeE-symm.cmat -o GeE_roi.amat --range-x 0 200 --range-y 0 200
# Export as a sparse list of non-zero channels (x y counts):
python3 cmat2amat.py GeE-symm.cmat -o GeE_sparse.amat --format sparse
# Save decompressed array to NumPy binary format (.npy):
python3 cmat2amat.py GeE-symm.cmat --npyInverse converter that takes any 2D ASCII grid, sparse triplet list (x y counts), or NumPy array, and compresses it into a GASPware-compliant .cmat binary file.
# Convert dense ASCII matrix to .cmat:
python3 amat2cmat.py GeE-symm.amat -o GeE-symm_reconstructed.cmat
# Convert sparse ASCII matrix with explicit shape:
python3 amat2cmat.py GeE_sparse.amat -o GeE_sparse.cmat --shape 4096 4096
# Convert NumPy binary array (.npy) to symmetric .cmat:
python3 amat2cmat.py matrix.npy -o matrix.cmat --symmetric
# Inspect ASCII matrix statistics without exporting:
python3 amat2cmat.py GeE-symm.amat --infoYou can also compress and save matrices directly from Python scripts:
from cmat import write_cmat, CMATWriter
import numpy as np
# Create or load a 2D matrix
matrix = np.random.poisson(lam=5, size=(4096, 4096)).astype(np.int32)
# Symmetrize if desired
matrix = (matrix + matrix.T) // 2
# Method 1: Functional write_cmat
write_cmat("simulated.cmat", matrix, symmetric=True, step1=128, step2=128)
# Method 2: OOP CMATWriter
writer = CMATWriter(matrix, symmetric=True)
writer.save("simulated.cmat")python-cmat includes CMAT3DReader for handling 3D matrix cubes with memory-mapped array caching and multi-plane slicing.
from cmat3d import CMAT3DReader
# 1. Initialize reader and build/load memory-mapped cache
reader3d = CMAT3DReader("GeE-Rings3D.cmat")
print(f"3D Shape: {reader3d.shape}") # (4096, 4096, 128)
print(f"Total counts: {reader3d.total_counts:,}")
# 2. Access 3D volume directly as NumPy array / memmap (int32)
vol = reader3d.to_memmap()
# 3. Extract 2D orthogonal plane projection
# plane: '0-1' (Det 1 vs Det 2), '0-2' (Det 1 vs Rings), '1-2' (Det 2 vs Rings)
plane_01 = reader3d.get_2d_plane("0-1")
plane_01_sub = reader3d.get_2d_plane("0-1", x0=1000, x1=2000, y0=1000, y1=2000)
# 4. Extract total 1D projection along any axis (0, 1, or 2)
spec_det1 = reader3d.get_projection_1d(axis=0)
# 5. Extract 1D coincidence cuts with background subtraction
net_spec, bg_spec = reader3d.get_gate_1d(
target_axis=0,
w_gates={2: [[11, 11]]}, # Gate on Ring 11 (axis 2)
b_gates={2: [[1, 2], [20, 21]]} # Background rings
)
# 6. Extract 2D Banana Graphical Polygon Cut
banana_cut = reader3d.get_banana_gate(
plane="0-2",
polygon=[[1000, 10], [1500, 12], [1480, 15], [980, 13]]
)
print("Banana cut net spectrum sum:", banana_cut["net_spec"].sum())python-cmat includes ENSDFSearchEngine for automated offline isotope identification of 1D photopeaks and 2D coincidence cascades using physical cascade topologies and global mass-clustering parsimony.
# Identify full fit results log file:
python3 ensdf_search.py fit_results_20260922_155405.txt --top 5
# Manual 2D coincidence search:
python3 ensdf_search.py -c 1434.2 935.3 --tol 1.5 --top 5
# Manual 1D single-energy search:
python3 ensdf_search.py -g 1480.3 --tol 1.5
# Database status & statistics:
python3 ensdf_search.py --status
# Reaction-channel soft prior (declared beam/target/ejectile channels):
python3 ensdf_search.py fit_results.txt --reaction "100Mo(18O,16O)102Mo; 181Ta(18O,xn); 181Ta"
# With decay-chain daughters and a strict whitelist:
python3 ensdf_search.py fit_results.txt --reaction "100Mo(18O,16O)102Mo" --decay-depth 2 --strict
# Channels from a file (specs or a 'Z A [weight]' table), decay products only:
python3 ensdf_search.py fit_results.txt --channels channels.txt --decay-onlyfrom ensdf_search import ENSDFSearchEngine
engine = ENSDFSearchEngine()
# Identify entire fit results file with global parsimonious set cover
report = engine.identify_fit_results_file(
"fit_results_20260922_155405.txt",
tol=1.5,
top_candidates=5
)
# With a reaction-channel soft prior (issue #13): in-channel candidates are
# scaled by weight/w_max, off-channel ones demoted (x0.05) but never hidden.
from ensdf_search import build_nuclide_prior
prior, reaction_info = build_nuclide_prior(
engine, spec="100Mo(18O,16O)102Mo; 181Ta", decay_depth=2)
report = engine.identify_fit_results_file(
"fit_results_20260922_155405.txt",
tol=1.5,
nuclide_prior=prior,
strict=False,
reaction_info=reaction_info,
)
print("Channels:", [c["label"] for c in report["reaction"]["channels"]])
print("Daughters:", report["reaction"]["daughter_isotopes"])
print(f"Dominant Mass Center: A ≈ {report['dominant_mass']}")
print(f"Minimal Isotope Set: {report['parsimonious_isotopes']}")
for res in report["results_2d"]:
fit = res["fit"]
best = res["best_match"]
print(f"2D Fit {fit['energy1']} x {fit['energy2']} keV -> {best['nuclide']} (Score: {best['score']}, {best['cascade_type']})")halflife.py is an analytical nuclear lifetime fitting engine based on halflife.c for analyzing time-difference spectra (TAC, TDC, digital CFD timestamp differences) exported from cmat_webviewer.py and cmat3d_webviewer.py.
It supports both an interactive terminal REPL (matching the classic menu-driven workflow) and a fully scriptable command-line interface.
Development status: The half-life fitting workflow has been improved, but additional fitting, analysis, and interface changes are still upcoming.
# 1. Interactive terminal menu REPL (classic halflife.c experience):
python3 halflife.py -i spectrum.dat
# 2. Scriptable automated fitting with initial parameters:
python3 halflife.py spectrum.dat --t12 19.5 --fwhm 15.2 --centroid 482.0 --bg 10.0 --range 450 750
# 3. Fit and export ASCII .fit file and publication vector PDF:
python3 halflife.py spectrum.dat --t12 20.0 --fwhm 15.0 --range 450 750 --out fit_result.fit --pdf fit_plot.pdf
# 4. Perform chi-square profile scan over background:
python3 halflife.py spectrum.dat --scan-bg 0.0 50.0 50
# 5. Compress spectrum by factor of 2 and zero-suppress:
python3 halflife.py spectrum.dat --compress 2 --zero-suppress --t12 10.0 --range 200 600| Argument | Type | Default | Description |
|---|---|---|---|
input_file |
str |
None | Path to input ASCII spectrum (.dat, .txt, .csv) |
-i, --interactive
|
flag |
False |
Launch interactive terminal REPL menu |
--t12 |
float |
Auto | Initial half-life estimate in channels ( |
--fix-t12 |
flag |
False |
Fix half-life during fit (e.g. to fit pure prompt IRF) |
--fwhm |
float |
Auto | Initial prompt time resolution FWHM ( |
--fix-fwhm |
flag |
False |
Fix FWHM during fit |
--centroid |
float |
Auto | Initial prompt centroid / peak position |
--fix-centroid |
flag |
False |
Fix centroid during fit |
--bg |
float |
Auto | Initial constant background baseline |
--fix-bg |
flag |
False |
Fix background during fit |
--range |
int int |
[min, max] |
Fit range window bounds in channels |
--compress |
int |
1 |
Rebin / compress spectrum channels by factor |
--zero-suppress |
flag |
False |
Suppress non-positive counts during Poisson weighting |
--scan-bg |
float float int |
Auto | Background [min max steps]
|
--out |
str |
None | Save fit parameters, covariance, and model curve to .fit
|
--pdf |
str |
None | Export publication-quality vector PDF plot |
from halflife import HalfLifeFitter
# 1. Load spectrum from file or NumPy arrays
# Supports 1, 2, 3, or 4 column ASCII exports
fitter = HalfLifeFitter.from_file("LaE-TAC.cmat_Det2_Y_Gated_1437_2458.dat")
# 2. Configure initial parameters & free/fixed flags
fitter.set_parameters(
t12=20.0, fix_t12=False,
fwhm=15.0, fix_fwhm=False,
centroid=482.0, fix_centroid=False,
bg=10.0, fix_bg=False,
fit_min=450, fit_max=750
)
# 3. Perform Levenberg-Marquardt / TRF non-linear fit
res = fitter.fit()
print(f"Half-life: {res.t12:.4f} +/- {res.t12_err:.4f} ch")
print(f"FWHM: {res.fwhm:.4f} +/- {res.fwhm_err:.4f} ch")
print(f"Centroid: {res.centroid:.4f} +/- {res.centroid_err:.4f} ch")
print(f"Chi2/NDF: {res.chi2_ndf:.3f} (NDF={res.ndf})")
# 4. Perform background chi-square exploration scan
# Args: b_min, b_max, steps (not bg_min / num_steps)
scan = fitter.scan_background(b_min=0.0, b_max=40.0, steps=40)
print(f"Best background from scan: {scan['best_bg']:.2f}")
# 5. Export results
# save_fit_file is a module-level helper; export_plot is the instance method for PDF/PNG
from halflife import save_fit_file
save_fit_file("output.fit", "spectrum.dat", fitter.spec, res)
fitter.export_plot("output.pdf", title="138La Nuclear Lifetime Fit")From the 2D webviewer, use the ⏱️ Half-Life Fit button in the header of the Det 1/X or Det 2/Y 1D projection. The popup receives that histogram’s exact currently visible range and current gated counts. The 📥 Pull from WebViewer button refreshes the snapshot after changing the matrix, 2D viewport, gates, or projection. If the time spectrum is reversed, enable ⇄ Mirror Spectrum before fitting; the model then fits the mirrored, ascending right-decay representation and reports a nonnegative half-life.
The plot’s X-axis zoom, pan, drag zoom, and reset update the fit bounds automatically. The parameter controls use practical data-adaptive domains: t₁/₂ and FWHM use zero-aware logarithmic ranges, centroid uses a linear range around the visible fit window, scale uses a positive logarithmic range, and background is free by default with a late-time-tail initial estimate. L/l toggles linear/log display scale.
python-cmat © 2026. Released under the MIT License. Return to GitHub Repository
Interactive 2D & 3D Matrix Analysis & Spectroscopy
- Home
- Interactive Web Viewer
- 3D Matrix Analysis & Web Viewer
- Keyboard Shortcuts & Controls
- ROOT Files & Live Histograms
- ENSDF Isotope Identification
- Spectroscopy Commands & Macros
- Configuration Guide
- Python API & CLI Tools