GammaFlow
A Python library for working with time series of gamma ray spectra and listmode data.
Features
Core Classes
-
Spectrum: Comprehensive base class for gamma ray spectra
- Optional energy calibration (uncalibrated = channel mode)
- Arithmetic operations (add, subtract, scalar multiply/divide)
- Energy operations (rebinning, calibration, slicing)
- Analysis methods (integration, normalization)
- Uncertainty propagation (Poisson statistics by default)
- Optional live_time (falls back to real_time for count rates)
-
Spectra: Base class for collections of spectra (not time-ordered)
- Manages shared or independent energy calibration
- Vectorized access to counts via 2D NumPy array
- Statistical methods (mean, median, std, sum spectra)
- Automatic detection of compatible calibrations
- Individual Spectrum objects with metadata preserved
- NumPy array protocol support for advanced operations
-
SpectralTimeSeries: Time-ordered collection of spectra (inherits from Spectra)
- All Spectra features plus time-series specific functionality
- Auto-detection of integration_time and stride_time
- Reintegration to coarser time scales (multiples only)
- Create from 2D arrays or list mode data
- Time-based slicing and filtering
- Copy-on-write for safe modifications
-
ListMode: Event-by-event data handling
- Encapsulates time deltas and energies
- Filtering and slicing operations
- Conversion to SpectralTimeSeries
Operations
- Energy ROIs (Regions of Interest)
- Define labeled energy windows for analysis
- Rebin spectra by integrating over ROIs
- Support for overlapping and non-consecutive ROIs
- Track creation method (manual, peak search, censored windows, etc.)
- Time series analysis with ROIs
Algorithms
- Censored Energy Window (CEW)
- Gradient-based optimization for optimal energy window selection
- Maximizes signal-to-noise ratio for source detection
- Ridge regression predictor with constrained coefficients
- Score spectra for anomaly detection or source presence
- Convert optimized windows to EnergyROI objects
- Serializable predictors for deployment
Visualization
- Publication-quality plotting with matplotlib and seaborn
plot_spectrum(): Single spectrum with log scale, uncertainty bandsplot_count_rate_time_series(): Total count rate vs timeplot_waterfall(): 2D heatmap of spectral evolution (time vs energy)plot_roi_time_series(): Time evolution of specific energy ROIsplot_spectrum_comparison(): Compare multiple spectra- Flexible styling and customization options
Performance
- Vectorized Operations: Leverage NumPy for performance
- Direct array access via
.countsproperty - Broadcasting and fancy indexing support
- Efficient batch processing
- Direct array access via
Installation
pip install -e .
Quick Start
Basic Spectrum Operations
import numpy as np
from gammaflow import Spectrum, SpectralTimeSeries
# Create a spectrum
counts = np.random.poisson(100, size=1024)
spectrum = Spectrum(counts, real_time=10.0)
# Apply energy calibration
energy_edges = np.linspace(0, 3000, 1025) # 0-3000 keV
calibrated = Spectrum(counts, energy_edges=energy_edges)
# Arithmetic operations
combined = spectrum1 + spectrum2
scaled = spectrum * 2.0
# Energy operations
roi = spectrum.slice_energy(e_min=500, e_max=1000)
integrated = spectrum.integrate(e_min=600, e_max=700)
normalized = spectrum.normalize(method='area')
# Count rate (uses live_time if available, else real_time)
rate = spectrum.count_rate
Time Series Creation
# Method 1: From list of Spectrum objects
spectra = [Spectrum(np.random.poisson(100, 1024), real_time=1.0) for _ in range(100)]
ts = SpectralTimeSeries(spectra)
# Method 2: From 2D array (most common)
counts = np.random.poisson(100, size=(100, 1024))
timestamps = np.arange(100) * 1.0
real_times = np.ones(100) * 1.0
energy_edges = np.linspace(0, 3000, 1025)
ts = SpectralTimeSeries.from_array(
counts,
energy_edges=energy_edges,
timestamps=timestamps,
real_times=real_times
)
# Method 3: From list mode data
from gammaflow import ListMode
time_deltas = np.random.exponential(0.001, size=100000)
energies = np.random.gamma(2, 500, size=100000)
# Direct from arrays
ts = SpectralTimeSeries.from_list_mode(
time_deltas, energies,
integration_time=10.0,
stride_time=10.0,
energy_bins=1024
)
# Or using ListMode object (for filtering first)
listmode = ListMode(time_deltas, energies)
filtered = listmode.filter_energy(e_min=200, e_max=800)
ts = SpectralTimeSeries.from_list_mode(filtered, integration_time=10.0)
Time Series Operations
# Timing is auto-detected from data
print(f"Integration time: {ts.integration_time}") # Auto-detected from real_time
print(f"Stride time: {ts.stride_time}") # Auto-detected from timestamps
# Reintegrate to coarser time resolution (must be even multiple)
ts_20s = ts.reintegrate(new_integration_time=20.0) # 2x coarser
ts_40s = ts.reintegrate(new_integration_time=40.0) # 4x coarser
# Vectorized operations
background = np.mean(ts.counts, axis=0)
ts.counts[:] -= background # In-place modification
# Time slicing
ts_subset = ts.slice_time(t_min=100, t_max=500)
# Object-oriented access
for spec in ts:
if spec.timestamp > 200:
spec.metadata['processed'] = True
ROI Analysis
from gammaflow.operations import EnergyROI, rebin_spectrum_rois, rebin_time_series_rois
# Define regions of interest
rois = [
EnergyROI(e_min=655, e_max=668, label="Cs-137 (661.7 keV)"),
EnergyROI(e_min=1450, e_max=1470, label="K-40 (1460.8 keV)"),
EnergyROI(e_min=1165, e_max=1180, label="Co-60 Peak 1"),
EnergyROI(e_min=1325, e_max=1340, label="Co-60 Peak 2")
]
# Integrate single spectrum over ROIs
roi_counts, labels = rebin_spectrum_rois(spectrum, rois, return_labels=True)
print(f"Cs-137 counts: {roi_counts[0]}")
# Analyze time evolution of ROIs
roi_ts = rebin_time_series_rois(time_series, rois)
# Shape: (n_spectra, n_rois) - rows are time, columns are ROIs
# Plot K-40 evolution
import matplotlib.pyplot as plt
plt.plot(time_series.timestamps, roi_ts[:, 1])
plt.xlabel("Time (s)")
plt.ylabel("K-40 Peak Counts")
Working with Spectra Collections
from gammaflow import Spectra
# Create a collection of spectra (not necessarily time-ordered)
spectra_list = [
Spectrum(np.random.poisson(100, 1024), metadata={'sample': 'background'}),
Spectrum(np.random.poisson(120, 1024), metadata={'sample': 'source_1'}),
Spectrum(np.random.poisson(110, 1024), metadata={'sample': 'source_2'})
]
collection = Spectra(spectra_list)
# Vectorized operations on all spectra
background_subtracted = collection.counts - collection.counts[0]
# Statistical analysis
mean_spectrum = collection.mean_spectrum()
median_spectrum = collection.median_spectrum()
std_spectrum = collection.std_spectrum()
total_spectrum = collection.sum_spectrum()
# Access individual spectra with metadata
for i, spec in enumerate(collection):
print(f"Sample {spec.metadata['sample']}: {np.sum(spec.counts)} counts")
# NumPy protocol support
stacked = np.array(collection) # (n_spectra, n_bins)
normalized = collection.counts / np.sum(collection.counts, axis=1, keepdims=True)
Censored Energy Window (CEW) Algorithm
from gammaflow.algorithms import optimize_cew_windows, fit_cew_predictor, CEWPredictor
# Load training data: background spectra
background_spectra = Spectra([...]) # Your background training data
# Define expected source spectrum (or use measured/simulated)
source_counts = np.loadtxt('cs137_spectrum.txt')
source_spectrum = Spectrum(source_counts)
# Get mean background for window optimization
mean_background = background_spectra.mean_spectrum()
# Step 1: Optimize window selection to maximize SNR
window_mask = optimize_cew_windows(
source_spectrum=source_spectrum,
background_spectrum=mean_background,
method='gradient', # Options: 'gradient', 'greedy', 'scipy'
learning_rate=0.01,
max_iterations=1000
)
print(f"Window bins: {np.sum(window_mask)} of {len(window_mask)}")
# Step 2: Fit ridge regression predictor
predictor = fit_cew_predictor(
background_spectra=background_spectra,
window_mask=window_mask,
alpha=1.0 # Ridge regularization
)
# Step 3: Score new observations
test_spectrum = Spectrum(np.random.poisson(100, 1024))
score = predictor.score(test_spectrum)
print(f"CEW score: {score:.3f}") # Higher = more anomalous
# Score time series for anomaly detection
# (monitoring for when a source appears)
time_series = SpectralTimeSeries(...) # Time-ordered measurements
scores = predictor.score_time_series(time_series)
# Detect anomalies (times when source appeared)
threshold = np.percentile(scores, 99) # 99th percentile
anomaly_indices = np.where(scores > threshold)[0]
anomaly_times = time_series.timestamps[anomaly_indices]
print(f"Source detected at {len(anomaly_indices)} time points")
print(f"Detection times: {anomaly_times}")
# Convert optimized windows to ROIs
rois = predictor.to_rois(energy_edges=test_spectrum.energy_edges)
for roi in rois:
print(f"ROI {roi.label}: {roi.e_min:.1f} - {roi.e_max:.1f} keV")
# Save predictor for later use
predictor_dict = predictor.to_dict()
np.savez('cew_predictor.npz', **predictor_dict)
# Load and use
loaded_dict = np.load('cew_predictor.npz')
loaded_predictor = CEWPredictor.from_dict(loaded_dict)
Advanced Features
# Overlapping ROIs (useful for peak fitting)
overlapping_rois = [
EnergyROI(e_min=650, e_max=670, label="Peak + Background"),
EnergyROI(e_min=655, e_max=665, label="Peak Core")
]
# Non-consecutive ROIs (gaps allowed - perfect for peaks)
peak_rois = [
EnergyROI(e_min=661, e_max=663, label="Cs-137"),
EnergyROI(e_min=1460, e_max=1462, label="K-40") # Large gap
]
# Create ROI collection with metadata
from gammaflow.operations import create_roi_collection
censored = create_roi_collection([
(100, 600, "Low Window"),
(700, 1400, "Mid Window"),
(1500, 1900, "High Window")
], method="Censored Energy Windows")
# NumPy integration for advanced analysis
total_counts = np.sum(ts.counts, axis=1)
spectral_std = np.std(ts.counts, axis=0)
peak_locations = np.argmax(ts.counts, axis=1)
Visualization
from gammaflow.visualization import (
plot_spectrum,
plot_count_rate_time_series,
plot_waterfall,
plot_roi_time_series,
plot_spectrum_comparison,
)
import matplotlib.pyplot as plt
# Plot a single spectrum (log scale, with uncertainty)
fig, ax = plot_spectrum(spectrum, log_y=True, show_uncertainty=True)
plt.show()
# Plot total count rate over time
fig, ax = plot_count_rate_time_series(time_series, show_uncertainty=True)
plt.show()
# Create waterfall plot (2D heatmap: time vs energy)
fig, ax = plot_waterfall(
time_series,
mode='count_rate',
log_scale=True,
cmap='viridis'
)
plt.show()
# Plot time evolution of specific ROIs
rois = [
EnergyROI(655, 668, label="Cs-137"),
EnergyROI(1450, 1470, label="K-40")
]
fig, ax = plot_roi_time_series(time_series, rois, mode='count_rate')
plt.show()
# Compare multiple spectra
fig, ax = plot_spectrum_comparison(
[background, source],
labels=['Background', 'Source'],
log_y=True
)
plt.show()
# Create multi-panel figure
fig = plt.figure(figsize=(14, 10))
ax1 = plt.subplot(2, 2, 1)
plot_spectrum(spectrum, fig=fig, ax=ax1)
ax1.set_title('Spectrum')
ax2 = plt.subplot(2, 2, 2)
plot_count_rate_time_series(time_series, fig=fig, ax=ax2)
ax2.set_title('Count Rate vs Time')
ax3 = plt.subplot(2, 1, 2)
plot_waterfall(time_series, fig=fig, ax=ax3)
ax3.set_title('Spectral Evolution')
plt.tight_layout()
plt.show()
Project Structure
gammaflow/
├── gammaflow/
│ ├── __init__.py
│ ├── core/
│ │ ├── __init__.py
│ │ ├── calibration.py
│ │ ├── spectrum.py
│ │ ├── spectra.py
│ │ ├── time_series.py
│ │ └── listmode.py
│ ├── operations/
│ │ ├── __init__.py
│ │ ├── energy.py
│ │ ├── temporal.py
│ │ └── roi.py
│ ├── algorithms/
│ │ ├── __init__.py
│ │ └── censored_energy_window.py
│ ├── utils/
│ │ ├── __init__.py
│ │ └── exceptions.py
│ └── visualization/
│ └── __init__.py
├── tests/
├── examples/
└── README.md
Examples
See the examples/ directory for comprehensive examples:
basic_usage.py- Spectrum and time series basicsadvanced_usage.py- Advanced operations and workflowslist_mode_example.py- Working with list mode datalistmode_class_example.py- ListMode class usagereintegrate_example.py- Time resolution reintegrationtiming_inference_example.py- Automatic timing detectionroi_example.py- Energy ROI analysiscew_example.py- Censored Energy Window algorithm usagevisualization_example.py- Plotting spectra and time series
Documentation
Documentation is integrated into docstrings throughout the codebase. Key concepts are explained in the README above. For detailed API documentation, use Python's built-in help:
from gammaflow import Spectrum, Spectra, SpectralTimeSeries
from gammaflow.algorithms import CEWPredictor
help(Spectrum)
help(Spectra)
help(SpectralTimeSeries)
help(CEWPredictor)
Key Concepts
Spectrum Collections
- Spectra: Base class for non-time-ordered collections
- Shared or independent calibration modes
- Statistical methods (mean, median, std, sum)
- Individual metadata preservation
- SpectralTimeSeries: Time-ordered subclass of Spectra
- All Spectra features plus time-specific functionality
- Automatic timing parameter detection
Energy Calibration
- Spectra can be calibrated (with energy_edges) or uncalibrated (channel mode)
- Shared calibration in collections for memory efficiency
- Auto-detection of compatible calibrations
- Copy-on-write for safe modifications
Timing
real_time: Clock time (including dead time)live_time: Actual counting time (excluding dead time)integration_time: Time window width for binningstride_time: Time between consecutive windows- Auto-detection from consistent data
ROIs (Regions of Interest)
- Define labeled energy windows for analysis
- Can overlap (for peak fitting, background estimation)
- Can be non-consecutive (for peak-only analysis)
- Track creation method and metadata
List Mode
- Event-by-event data: time deltas + energies
- Filter and slice before binning
- Convert to time series with flexible windowing
Testing
Run the test suite:
pytest tests/ -v
Current status: 367+ tests passing ✓
Test coverage includes:
- Core functionality (Spectrum, Spectra, SpectralTimeSeries, ListMode)
- Energy operations (rebinning, calibration, ROIs)
- Time series operations (reintegration, slicing, filtering)
- Censored Energy Window algorithm
- Shared calibration and copy-on-write behavior
- NumPy interoperability
License
MIT
Release files for gammaflow 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| gammaflow-0.1.1.tar.gz | 108.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gammaflow-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 203.8 kB
Release files / gammaflow-0.1.1.tar.gz
| Download URL | gammaflow-0.1.1.tar.gz |
|---|---|
| Size | 108.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
62a683138a533ada4191dd793aeb8b0fa2d767e9e3275eb2dce76f6c01632c8a
|
|
BLAKE2b-256 checksum How to use checksums |
50b9d01d45ef39151d1f1267b112dcc0c0987547e13eb15718b5fbeb90d381d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.16
|
Release files / gammaflow-0.1.1-py3-none-any.whl
| Download URL | gammaflow-0.1.1-py3-none-any.whl |
|---|---|
| Size | 95.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7bdc33642c8413e8f5f5834e05e2b59b74372b88ce76e3b03d9731bb65a1fe31
|
|
BLAKE2b-256 checksum How to use checksums |
0e6320b82911d85fde4429342bcc387b2ebc25d7784c4be54174acb1a44733ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.16
|