radiapy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- radiapy-0.1.0/LICENSE +21 -0
- radiapy-0.1.0/PKG-INFO +285 -0
- radiapy-0.1.0/README.md +228 -0
- radiapy-0.1.0/pyproject.toml +60 -0
- radiapy-0.1.0/radiapy/__init__.py +20 -0
- radiapy-0.1.0/radiapy/__main__.py +82 -0
- radiapy-0.1.0/radiapy/cli.py +70 -0
- radiapy-0.1.0/radiapy/core.py +263 -0
- radiapy-0.1.0/radiapy/exporters/__init__.py +5 -0
- radiapy-0.1.0/radiapy/exporters/html_exporter.py +320 -0
- radiapy-0.1.0/radiapy/parsers/__init__.py +14 -0
- radiapy-0.1.0/radiapy/parsers/base_parser.py +127 -0
- radiapy-0.1.0/radiapy/parsers/csv_parser.py +114 -0
- radiapy-0.1.0/radiapy/parsers/geant4_parser.py +121 -0
- radiapy-0.1.0/radiapy/physics/__init__.py +16 -0
- radiapy-0.1.0/radiapy/physics/attenuation.py +134 -0
- radiapy-0.1.0/radiapy/physics/inverse_square.py +97 -0
- radiapy-0.1.0/radiapy/physics/nist_data.py +128 -0
- radiapy-0.1.0/radiapy/visualizers/__init__.py +17 -0
- radiapy-0.1.0/radiapy/visualizers/attenuation_viz.py +243 -0
- radiapy-0.1.0/radiapy/visualizers/comparison_viz.py +242 -0
- radiapy-0.1.0/radiapy/visualizers/distance_viz.py +172 -0
- radiapy-0.1.0/radiapy/visualizers/particle_viz.py +226 -0
- radiapy-0.1.0/radiapy.egg-info/PKG-INFO +285 -0
- radiapy-0.1.0/radiapy.egg-info/SOURCES.txt +28 -0
- radiapy-0.1.0/radiapy.egg-info/dependency_links.txt +1 -0
- radiapy-0.1.0/radiapy.egg-info/entry_points.txt +2 -0
- radiapy-0.1.0/radiapy.egg-info/requires.txt +13 -0
- radiapy-0.1.0/radiapy.egg-info/top_level.txt +1 -0
- radiapy-0.1.0/setup.cfg +4 -0
radiapy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Radiapy Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
radiapy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: radiapy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactive visualization of radiation experiments and radiation-matter interactions
|
|
5
|
+
Author: Radiapy Contributors
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2024 Radiapy Contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/radviz/radiapy
|
|
29
|
+
Project-URL: Documentation, https://radiapy.readthedocs.io
|
|
30
|
+
Project-URL: Bug Tracker, https://github.com/radviz/radiapy/issues
|
|
31
|
+
Keywords: radiation,physics,visualization,geiger,attenuation,beer-lambert
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Intended Audience :: Science/Research
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
40
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
41
|
+
Requires-Python: >=3.10
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Requires-Dist: plotly>=5.15.0
|
|
45
|
+
Requires-Dist: pandas>=2.0.0
|
|
46
|
+
Requires-Dist: numpy>=1.24.0
|
|
47
|
+
Requires-Dist: scipy>=1.10.0
|
|
48
|
+
Requires-Dist: click>=8.1.0
|
|
49
|
+
Provides-Extra: dev
|
|
50
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
51
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
52
|
+
Requires-Dist: jupyter>=1.0.0; extra == "dev"
|
|
53
|
+
Requires-Dist: kaleido>=0.2.1; extra == "dev"
|
|
54
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
55
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
56
|
+
Dynamic: license-file
|
|
57
|
+
|
|
58
|
+
# Radiapy
|
|
59
|
+
|
|
60
|
+
**Interactive visualization of radiation experiments and radiation-matter interactions.**
|
|
61
|
+
|
|
62
|
+
Radiapy is an open-source Python library for analysing and visualising Geiger-counter radiation attenuation experiments. It implements the Beer-Lambert law, the inverse-square law, and NIST photon attenuation data, and renders every plot as an interactive Plotly figure exportable to standalone HTML — no ROOT, VTK, or GEANT4 required.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install radiapy
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or from source:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git clone https://github.com/radviz/radiapy
|
|
76
|
+
cd radiapy
|
|
77
|
+
pip install -e ".[dev]"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Requirements:** Python ≥ 3.10, plotly, pandas, numpy, scipy, click
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Quick start
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from radviz import RadViz
|
|
88
|
+
|
|
89
|
+
rv = RadViz("radviz/demo/dataset_geiger.csv")
|
|
90
|
+
|
|
91
|
+
# Generate a complete standalone HTML dashboard
|
|
92
|
+
rv.generate_dashboard("dashboard.html")
|
|
93
|
+
|
|
94
|
+
# Individual interactive plots
|
|
95
|
+
fig = rv.plot_attenuation() # Bar chart: T by material + NIST overlay
|
|
96
|
+
fig = rv.plot_inverse_square() # Counts vs distance + 1/r² fit
|
|
97
|
+
fig = rv.plot_heatmap() # 2-D transmission heatmap
|
|
98
|
+
fig = rv.plot_comparison() # Cd109 vs Am241 side-by-side
|
|
99
|
+
fig = rv.plot_particles() # Animated photon trajectories
|
|
100
|
+
fig.show()
|
|
101
|
+
|
|
102
|
+
# Physics report
|
|
103
|
+
print(rv.physics_report())
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Command-line interface
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Full dashboard
|
|
110
|
+
radviz dashboard dataset_geiger.csv --output dashboard.html
|
|
111
|
+
|
|
112
|
+
# Physics report
|
|
113
|
+
radviz report dataset_geiger.csv
|
|
114
|
+
|
|
115
|
+
# Particle animation (no CSV needed)
|
|
116
|
+
radviz simulate --isotope Cd109 --material Pb --output anim.html
|
|
117
|
+
|
|
118
|
+
# Legacy single-command form
|
|
119
|
+
radviz --input dataset_geiger.csv --output dashboard.html
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Dataset format
|
|
125
|
+
|
|
126
|
+
The bundled `dataset_geiger.csv` has the following columns:
|
|
127
|
+
|
|
128
|
+
| Column | Description |
|
|
129
|
+
|--------|-------------|
|
|
130
|
+
| `isotope` | Source isotope (`Cd109` or `Am241`) |
|
|
131
|
+
| `distance_cm` | Source-detector distance (10, 20, 30 cm) |
|
|
132
|
+
| `counts_free` | Raw counts without absorber |
|
|
133
|
+
| `counts_Al` | Counts through aluminium filter |
|
|
134
|
+
| `counts_Cu` | Counts through copper filter |
|
|
135
|
+
| `counts_Pb` | Counts through lead filter |
|
|
136
|
+
| `counts_free_mean` | Mean free-field counts for this (isotope, distance) group |
|
|
137
|
+
| `Al_ratio` | Transmission fraction: `counts_Al / counts_free` |
|
|
138
|
+
| `Cu_ratio` | Transmission fraction: `counts_Cu / counts_free` |
|
|
139
|
+
| `Pb_ratio` | Transmission fraction: `counts_Pb / counts_free` |
|
|
140
|
+
|
|
141
|
+
To use a custom CSV, pass a `schema` dict to `RadViz`:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
schema = {
|
|
145
|
+
"isotope_col": "source",
|
|
146
|
+
"distance_col": "dist_cm",
|
|
147
|
+
"free_count_col": "N_free",
|
|
148
|
+
"filter_cols": {"Al": "N_Al", "Cu": "N_Cu", "Pb": "N_Pb"},
|
|
149
|
+
}
|
|
150
|
+
rv = RadViz("my_data.csv", schema=schema)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Physics Background
|
|
156
|
+
|
|
157
|
+
### Beer-Lambert attenuation law
|
|
158
|
+
|
|
159
|
+
When a beam of photons passes through a material of thickness *x* (cm), its intensity is reduced according to:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
I = I₀ · exp(−μ · x)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
where:
|
|
166
|
+
- `I₀` is the incident intensity (counts without absorber),
|
|
167
|
+
- `I` is the transmitted intensity,
|
|
168
|
+
- `μ` (cm⁻¹) is the **linear attenuation coefficient**, which depends on the material and the photon energy,
|
|
169
|
+
- `T = I / I₀ ∈ [0, 1]` is the **transmission fraction**.
|
|
170
|
+
|
|
171
|
+
The linear coefficient relates to the tabulated **mass attenuation coefficient** as `μ = (μ/ρ) · ρ`, where `ρ` is the material density (g/cm³).
|
|
172
|
+
|
|
173
|
+
Radiapy fits `μ` experimentally from the measured transmission ratios via `scipy.optimize.curve_fit` (with full covariance matrix for uncertainty propagation), and compares each fitted value against the NIST reference. The experimental coefficient is back-calculated as `μ_exp = −ln(T_exp) / x`.
|
|
174
|
+
|
|
175
|
+
### Inverse-square law
|
|
176
|
+
|
|
177
|
+
For a point source radiating isotropically, the intensity at distance *r* from the source falls off as:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
I(r) = A / r²
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
where `A` is a constant proportional to the source activity and detector efficiency. This geometric dilution of intensity over the surface of an expanding sphere means that doubling the distance reduces counts to one quarter. Radiapy fits `A` with uncertainty via `scipy.optimize.curve_fit` and reports R² as a goodness-of-fit metric.
|
|
184
|
+
|
|
185
|
+
A diagnostic plot of `I · r²` vs `r` (the "1/r² diagnostic" tab) should yield a horizontal line; deviations indicate room scatter, geometry effects, or a non-point-like source.
|
|
186
|
+
|
|
187
|
+
### The Pb K-edge and the counterintuitive Cd-109 / Am-241 reversal
|
|
188
|
+
|
|
189
|
+
Every element has characteristic **absorption edges** — energies at which a new inner-shell photoelectric absorption channel opens as photon energy increases past the binding energy of that electron shell. For **lead (Pb)**, the K-shell binding energy is **88.005 keV**.
|
|
190
|
+
|
|
191
|
+
The two isotopes in this experiment straddle that edge:
|
|
192
|
+
|
|
193
|
+
| Isotope | Main γ energy | Position relative to Pb K-edge |
|
|
194
|
+
|---------|--------------|-------------------------------|
|
|
195
|
+
| Am-241 | 59.54 keV | **Below** — K shell cannot be ionised |
|
|
196
|
+
| Cd-109 | 88.034 keV | **Just above** — K shell is ionised |
|
|
197
|
+
|
|
198
|
+
This creates a **counterintuitive reversal**: even though Cd-109 photons are *higher energy* than Am-241 photons (and high-energy photons generally penetrate matter more easily), Pb attenuates Cd-109 *more strongly* because the K-shell photoelectric cross-section is suddenly available. The `μ/ρ` of Pb jumps from ~2.0 cm²/g just below the edge to ~13.9 cm²/g just above — a ~7× increase. This effect is clearly visible in the **Cd109 vs Am241** comparison tab of the dashboard.
|
|
199
|
+
|
|
200
|
+
### NIST reference data
|
|
201
|
+
|
|
202
|
+
All theoretical values are taken from:
|
|
203
|
+
|
|
204
|
+
- **NIST XCOM** — *Photon Cross Sections Database*, Standard Reference Database 8 (XGAM).
|
|
205
|
+
Berger, M.J., Hubbell, J.H., Seltzer, S.M., Chang, J., Coursey, J.S., Sukumar, R., Zucker, D.S., and Olsen, K.
|
|
206
|
+
https://physics.nist.gov/PhysRefData/Xcom/html/xcom1.html
|
|
207
|
+
|
|
208
|
+
- **NIST Standard Reference Database 126** — *X-Ray Mass Attenuation Coefficients*, J.H. Hubbell and S.M. Seltzer.
|
|
209
|
+
https://www.nist.gov/pml/x-ray-mass-attenuation-coefficients
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## NIST data
|
|
214
|
+
|
|
215
|
+
Hardcoded `μ/ρ` values (cm²/g) from [NIST XCOM](https://physics.nist.gov/PhysRefData/Xcom/html/xcom1.html):
|
|
216
|
+
|
|
217
|
+
| Material | ρ (g/cm³) | μ/ρ @ 59.5 keV | μ/ρ @ 88.034 keV |
|
|
218
|
+
|----------|-----------|----------------|------------------|
|
|
219
|
+
| Al | 2.699 | 0.3219 | 0.2226 |
|
|
220
|
+
| Cu | 8.960 | 0.8156 | 0.5082 |
|
|
221
|
+
| Pb | 11.35 | 5.549 | 13.93* |
|
|
222
|
+
|
|
223
|
+
\* Above Pb K-edge (88.005 keV).
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Demo dataset
|
|
228
|
+
|
|
229
|
+
Regenerate the synthetic demo dataset (Poisson-distributed, 50 replicates × 6 conditions):
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
python scripts/generate_demo_data.py
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Project structure
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
radiapy/
|
|
241
|
+
├── __init__.py # from radiapy import RadViz
|
|
242
|
+
├── __main__.py # python -m radiapy CSV [--output FILE]
|
|
243
|
+
├── core.py # RadViz orchestrator class
|
|
244
|
+
├── cli.py # Click CLI (radiapy dashboard / report / simulate)
|
|
245
|
+
├── parsers/
|
|
246
|
+
│ ├── base_parser.py # Abstract base + GroupStats + ParsedDataset
|
|
247
|
+
│ ├── csv_parser.py # Generic CSV → ParsedDataset
|
|
248
|
+
│ └── geant4_parser.py # GEANT4 ASCII output → ParsedDataset
|
|
249
|
+
├── physics/
|
|
250
|
+
│ ├── attenuation.py # Beer-Lambert fit (curve_fit + pcov)
|
|
251
|
+
│ ├── inverse_square.py# 1/r² fit
|
|
252
|
+
│ └── nist_data.py # NIST μ/ρ table, filter thicknesses
|
|
253
|
+
├── visualizers/
|
|
254
|
+
│ ├── attenuation_viz.py
|
|
255
|
+
│ ├── distance_viz.py
|
|
256
|
+
│ ├── comparison_viz.py
|
|
257
|
+
│ └── particle_viz.py # Photon trajectories with thickness slider
|
|
258
|
+
├── exporters/
|
|
259
|
+
│ └── html_exporter.py # Standalone HTML dashboard (Plotly.js inline)
|
|
260
|
+
└── demo/
|
|
261
|
+
└── dataset_geiger.csv
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Citation
|
|
267
|
+
|
|
268
|
+
If you use Radiapy in scientific work, please cite:
|
|
269
|
+
|
|
270
|
+
```bibtex
|
|
271
|
+
@software{radiapy2024,
|
|
272
|
+
title = {Radiapy: Interactive visualization of radiation experiments},
|
|
273
|
+
year = {2024},
|
|
274
|
+
url = {https://github.com/radviz/radiapy},
|
|
275
|
+
version = {0.1.0}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Dataset: experimental Geiger-counter measurements of Cd-109 and Am-241 attenuation through Al, Cu, and Pb absorbers.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## License
|
|
284
|
+
|
|
285
|
+
MIT — see [LICENSE](LICENSE).
|
radiapy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Radiapy
|
|
2
|
+
|
|
3
|
+
**Interactive visualization of radiation experiments and radiation-matter interactions.**
|
|
4
|
+
|
|
5
|
+
Radiapy is an open-source Python library for analysing and visualising Geiger-counter radiation attenuation experiments. It implements the Beer-Lambert law, the inverse-square law, and NIST photon attenuation data, and renders every plot as an interactive Plotly figure exportable to standalone HTML — no ROOT, VTK, or GEANT4 required.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install radiapy
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or from source:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/radviz/radiapy
|
|
19
|
+
cd radiapy
|
|
20
|
+
pip install -e ".[dev]"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Requirements:** Python ≥ 3.10, plotly, pandas, numpy, scipy, click
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from radviz import RadViz
|
|
31
|
+
|
|
32
|
+
rv = RadViz("radviz/demo/dataset_geiger.csv")
|
|
33
|
+
|
|
34
|
+
# Generate a complete standalone HTML dashboard
|
|
35
|
+
rv.generate_dashboard("dashboard.html")
|
|
36
|
+
|
|
37
|
+
# Individual interactive plots
|
|
38
|
+
fig = rv.plot_attenuation() # Bar chart: T by material + NIST overlay
|
|
39
|
+
fig = rv.plot_inverse_square() # Counts vs distance + 1/r² fit
|
|
40
|
+
fig = rv.plot_heatmap() # 2-D transmission heatmap
|
|
41
|
+
fig = rv.plot_comparison() # Cd109 vs Am241 side-by-side
|
|
42
|
+
fig = rv.plot_particles() # Animated photon trajectories
|
|
43
|
+
fig.show()
|
|
44
|
+
|
|
45
|
+
# Physics report
|
|
46
|
+
print(rv.physics_report())
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Command-line interface
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Full dashboard
|
|
53
|
+
radviz dashboard dataset_geiger.csv --output dashboard.html
|
|
54
|
+
|
|
55
|
+
# Physics report
|
|
56
|
+
radviz report dataset_geiger.csv
|
|
57
|
+
|
|
58
|
+
# Particle animation (no CSV needed)
|
|
59
|
+
radviz simulate --isotope Cd109 --material Pb --output anim.html
|
|
60
|
+
|
|
61
|
+
# Legacy single-command form
|
|
62
|
+
radviz --input dataset_geiger.csv --output dashboard.html
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Dataset format
|
|
68
|
+
|
|
69
|
+
The bundled `dataset_geiger.csv` has the following columns:
|
|
70
|
+
|
|
71
|
+
| Column | Description |
|
|
72
|
+
|--------|-------------|
|
|
73
|
+
| `isotope` | Source isotope (`Cd109` or `Am241`) |
|
|
74
|
+
| `distance_cm` | Source-detector distance (10, 20, 30 cm) |
|
|
75
|
+
| `counts_free` | Raw counts without absorber |
|
|
76
|
+
| `counts_Al` | Counts through aluminium filter |
|
|
77
|
+
| `counts_Cu` | Counts through copper filter |
|
|
78
|
+
| `counts_Pb` | Counts through lead filter |
|
|
79
|
+
| `counts_free_mean` | Mean free-field counts for this (isotope, distance) group |
|
|
80
|
+
| `Al_ratio` | Transmission fraction: `counts_Al / counts_free` |
|
|
81
|
+
| `Cu_ratio` | Transmission fraction: `counts_Cu / counts_free` |
|
|
82
|
+
| `Pb_ratio` | Transmission fraction: `counts_Pb / counts_free` |
|
|
83
|
+
|
|
84
|
+
To use a custom CSV, pass a `schema` dict to `RadViz`:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
schema = {
|
|
88
|
+
"isotope_col": "source",
|
|
89
|
+
"distance_col": "dist_cm",
|
|
90
|
+
"free_count_col": "N_free",
|
|
91
|
+
"filter_cols": {"Al": "N_Al", "Cu": "N_Cu", "Pb": "N_Pb"},
|
|
92
|
+
}
|
|
93
|
+
rv = RadViz("my_data.csv", schema=schema)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Physics Background
|
|
99
|
+
|
|
100
|
+
### Beer-Lambert attenuation law
|
|
101
|
+
|
|
102
|
+
When a beam of photons passes through a material of thickness *x* (cm), its intensity is reduced according to:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
I = I₀ · exp(−μ · x)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
where:
|
|
109
|
+
- `I₀` is the incident intensity (counts without absorber),
|
|
110
|
+
- `I` is the transmitted intensity,
|
|
111
|
+
- `μ` (cm⁻¹) is the **linear attenuation coefficient**, which depends on the material and the photon energy,
|
|
112
|
+
- `T = I / I₀ ∈ [0, 1]` is the **transmission fraction**.
|
|
113
|
+
|
|
114
|
+
The linear coefficient relates to the tabulated **mass attenuation coefficient** as `μ = (μ/ρ) · ρ`, where `ρ` is the material density (g/cm³).
|
|
115
|
+
|
|
116
|
+
Radiapy fits `μ` experimentally from the measured transmission ratios via `scipy.optimize.curve_fit` (with full covariance matrix for uncertainty propagation), and compares each fitted value against the NIST reference. The experimental coefficient is back-calculated as `μ_exp = −ln(T_exp) / x`.
|
|
117
|
+
|
|
118
|
+
### Inverse-square law
|
|
119
|
+
|
|
120
|
+
For a point source radiating isotropically, the intensity at distance *r* from the source falls off as:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
I(r) = A / r²
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
where `A` is a constant proportional to the source activity and detector efficiency. This geometric dilution of intensity over the surface of an expanding sphere means that doubling the distance reduces counts to one quarter. Radiapy fits `A` with uncertainty via `scipy.optimize.curve_fit` and reports R² as a goodness-of-fit metric.
|
|
127
|
+
|
|
128
|
+
A diagnostic plot of `I · r²` vs `r` (the "1/r² diagnostic" tab) should yield a horizontal line; deviations indicate room scatter, geometry effects, or a non-point-like source.
|
|
129
|
+
|
|
130
|
+
### The Pb K-edge and the counterintuitive Cd-109 / Am-241 reversal
|
|
131
|
+
|
|
132
|
+
Every element has characteristic **absorption edges** — energies at which a new inner-shell photoelectric absorption channel opens as photon energy increases past the binding energy of that electron shell. For **lead (Pb)**, the K-shell binding energy is **88.005 keV**.
|
|
133
|
+
|
|
134
|
+
The two isotopes in this experiment straddle that edge:
|
|
135
|
+
|
|
136
|
+
| Isotope | Main γ energy | Position relative to Pb K-edge |
|
|
137
|
+
|---------|--------------|-------------------------------|
|
|
138
|
+
| Am-241 | 59.54 keV | **Below** — K shell cannot be ionised |
|
|
139
|
+
| Cd-109 | 88.034 keV | **Just above** — K shell is ionised |
|
|
140
|
+
|
|
141
|
+
This creates a **counterintuitive reversal**: even though Cd-109 photons are *higher energy* than Am-241 photons (and high-energy photons generally penetrate matter more easily), Pb attenuates Cd-109 *more strongly* because the K-shell photoelectric cross-section is suddenly available. The `μ/ρ` of Pb jumps from ~2.0 cm²/g just below the edge to ~13.9 cm²/g just above — a ~7× increase. This effect is clearly visible in the **Cd109 vs Am241** comparison tab of the dashboard.
|
|
142
|
+
|
|
143
|
+
### NIST reference data
|
|
144
|
+
|
|
145
|
+
All theoretical values are taken from:
|
|
146
|
+
|
|
147
|
+
- **NIST XCOM** — *Photon Cross Sections Database*, Standard Reference Database 8 (XGAM).
|
|
148
|
+
Berger, M.J., Hubbell, J.H., Seltzer, S.M., Chang, J., Coursey, J.S., Sukumar, R., Zucker, D.S., and Olsen, K.
|
|
149
|
+
https://physics.nist.gov/PhysRefData/Xcom/html/xcom1.html
|
|
150
|
+
|
|
151
|
+
- **NIST Standard Reference Database 126** — *X-Ray Mass Attenuation Coefficients*, J.H. Hubbell and S.M. Seltzer.
|
|
152
|
+
https://www.nist.gov/pml/x-ray-mass-attenuation-coefficients
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## NIST data
|
|
157
|
+
|
|
158
|
+
Hardcoded `μ/ρ` values (cm²/g) from [NIST XCOM](https://physics.nist.gov/PhysRefData/Xcom/html/xcom1.html):
|
|
159
|
+
|
|
160
|
+
| Material | ρ (g/cm³) | μ/ρ @ 59.5 keV | μ/ρ @ 88.034 keV |
|
|
161
|
+
|----------|-----------|----------------|------------------|
|
|
162
|
+
| Al | 2.699 | 0.3219 | 0.2226 |
|
|
163
|
+
| Cu | 8.960 | 0.8156 | 0.5082 |
|
|
164
|
+
| Pb | 11.35 | 5.549 | 13.93* |
|
|
165
|
+
|
|
166
|
+
\* Above Pb K-edge (88.005 keV).
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Demo dataset
|
|
171
|
+
|
|
172
|
+
Regenerate the synthetic demo dataset (Poisson-distributed, 50 replicates × 6 conditions):
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
python scripts/generate_demo_data.py
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Project structure
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
radiapy/
|
|
184
|
+
├── __init__.py # from radiapy import RadViz
|
|
185
|
+
├── __main__.py # python -m radiapy CSV [--output FILE]
|
|
186
|
+
├── core.py # RadViz orchestrator class
|
|
187
|
+
├── cli.py # Click CLI (radiapy dashboard / report / simulate)
|
|
188
|
+
├── parsers/
|
|
189
|
+
│ ├── base_parser.py # Abstract base + GroupStats + ParsedDataset
|
|
190
|
+
│ ├── csv_parser.py # Generic CSV → ParsedDataset
|
|
191
|
+
│ └── geant4_parser.py # GEANT4 ASCII output → ParsedDataset
|
|
192
|
+
├── physics/
|
|
193
|
+
│ ├── attenuation.py # Beer-Lambert fit (curve_fit + pcov)
|
|
194
|
+
│ ├── inverse_square.py# 1/r² fit
|
|
195
|
+
│ └── nist_data.py # NIST μ/ρ table, filter thicknesses
|
|
196
|
+
├── visualizers/
|
|
197
|
+
│ ├── attenuation_viz.py
|
|
198
|
+
│ ├── distance_viz.py
|
|
199
|
+
│ ├── comparison_viz.py
|
|
200
|
+
│ └── particle_viz.py # Photon trajectories with thickness slider
|
|
201
|
+
├── exporters/
|
|
202
|
+
│ └── html_exporter.py # Standalone HTML dashboard (Plotly.js inline)
|
|
203
|
+
└── demo/
|
|
204
|
+
└── dataset_geiger.csv
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Citation
|
|
210
|
+
|
|
211
|
+
If you use Radiapy in scientific work, please cite:
|
|
212
|
+
|
|
213
|
+
```bibtex
|
|
214
|
+
@software{radiapy2024,
|
|
215
|
+
title = {Radiapy: Interactive visualization of radiation experiments},
|
|
216
|
+
year = {2024},
|
|
217
|
+
url = {https://github.com/radviz/radiapy},
|
|
218
|
+
version = {0.1.0}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Dataset: experimental Geiger-counter measurements of Cd-109 and Am-241 attenuation through Al, Cu, and Pb absorbers.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "radiapy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Interactive visualization of radiation experiments and radiation-matter interactions"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {file = "LICENSE"}
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "Radiapy Contributors"}
|
|
13
|
+
]
|
|
14
|
+
keywords = ["radiation", "physics", "visualization", "geiger", "attenuation", "beer-lambert"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
25
|
+
]
|
|
26
|
+
requires-python = ">=3.10"
|
|
27
|
+
dependencies = [
|
|
28
|
+
"plotly>=5.15.0", # interactive figures + offline JS bundle
|
|
29
|
+
"pandas>=2.0.0", # CSV parsing and DataFrame operations
|
|
30
|
+
"numpy>=1.24.0", # numerical arrays and Poisson statistics
|
|
31
|
+
"scipy>=1.10.0", # curve_fit (Beer-Lambert + 1/r² fits), t-distribution
|
|
32
|
+
"click>=8.1.0", # CLI (radiapy dashboard / report / simulate)
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=7.0",
|
|
38
|
+
"pytest-cov",
|
|
39
|
+
"jupyter>=1.0.0",
|
|
40
|
+
"kaleido>=0.2.1", # static image export (PNG/SVG) from Plotly figures
|
|
41
|
+
"build>=1.0.0", # python -m build
|
|
42
|
+
"twine>=4.0.0", # twine upload dist/*
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
radiapy = "radiapy.cli:main"
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/radviz/radiapy"
|
|
50
|
+
Documentation = "https://radiapy.readthedocs.io"
|
|
51
|
+
"Bug Tracker" = "https://github.com/radviz/radiapy/issues"
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.packages.find]
|
|
54
|
+
where = ["."]
|
|
55
|
+
include = ["radiapy*"]
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.package-data]
|
|
58
|
+
# The demo CSV is in demo/ at the project root, not inside the package.
|
|
59
|
+
# Users access it via the bundled path or their own datasets.
|
|
60
|
+
radiapy = []
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Radiapy — Interactive visualization of radiation experiments.
|
|
3
|
+
|
|
4
|
+
Quick start::
|
|
5
|
+
|
|
6
|
+
from radiapy import RadViz
|
|
7
|
+
|
|
8
|
+
rv = RadViz("dataset_geiger.csv")
|
|
9
|
+
rv.generate_dashboard("dashboard.html")
|
|
10
|
+
|
|
11
|
+
Or visualize a single plot::
|
|
12
|
+
|
|
13
|
+
fig = rv.plot_attenuation()
|
|
14
|
+
fig.show()
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from radiapy.core import RadViz
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.0"
|
|
20
|
+
__all__ = ["RadViz"]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Entry point for ``python -m radiapy CSV_FILE [options]``.
|
|
2
|
+
|
|
3
|
+
Usage
|
|
4
|
+
-----
|
|
5
|
+
python -m radiapy demo/dataset_geiger.csv
|
|
6
|
+
python -m radiapy demo/dataset_geiger.csv --output my_report.html
|
|
7
|
+
python -m radiapy demo/dataset_geiger.csv --report-only
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
18
|
+
p = argparse.ArgumentParser(
|
|
19
|
+
prog="python -m radiapy",
|
|
20
|
+
description="Radiapy — interactive radiation experiment visualizer",
|
|
21
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
22
|
+
epilog="""
|
|
23
|
+
examples:
|
|
24
|
+
python -m radiapy demo/dataset_geiger.csv
|
|
25
|
+
python -m radiapy demo/dataset_geiger.csv --output report.html
|
|
26
|
+
python -m radiapy demo/dataset_geiger.csv --report-only
|
|
27
|
+
""",
|
|
28
|
+
)
|
|
29
|
+
p.add_argument("csv_file", metavar="CSV_FILE", help="Geiger-counter CSV dataset")
|
|
30
|
+
p.add_argument(
|
|
31
|
+
"--output", "-o",
|
|
32
|
+
default="dashboard.html",
|
|
33
|
+
metavar="FILE",
|
|
34
|
+
help="Output HTML dashboard path (default: dashboard.html)",
|
|
35
|
+
)
|
|
36
|
+
p.add_argument(
|
|
37
|
+
"--report-only",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="Print the physics report to stdout without generating HTML",
|
|
40
|
+
)
|
|
41
|
+
return p.parse_args(argv)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main(argv: list[str] | None = None) -> int:
|
|
45
|
+
args = _parse_args(argv)
|
|
46
|
+
csv_path = Path(args.csv_file)
|
|
47
|
+
|
|
48
|
+
if not csv_path.exists():
|
|
49
|
+
print(f"ERROR: file not found — {csv_path}", file=sys.stderr)
|
|
50
|
+
return 1
|
|
51
|
+
|
|
52
|
+
from radiapy.core import RadViz
|
|
53
|
+
|
|
54
|
+
print("=" * 60)
|
|
55
|
+
print(" Radiapy v0.1.0 — Radiation Experiment Visualizer")
|
|
56
|
+
print("=" * 60)
|
|
57
|
+
|
|
58
|
+
rv = RadViz(csv_path)
|
|
59
|
+
|
|
60
|
+
print(f"\nDataset loaded: {csv_path}")
|
|
61
|
+
print(f" Isotopes : {', '.join(rv.dataset.isotopes)}")
|
|
62
|
+
print(f" Distances : {rv.dataset.distances} cm")
|
|
63
|
+
print(f" Materials : {', '.join(rv.dataset.materials)}")
|
|
64
|
+
print(f" Total rows : {len(rv.dataset.raw)}")
|
|
65
|
+
print()
|
|
66
|
+
|
|
67
|
+
print(rv.physics_report())
|
|
68
|
+
|
|
69
|
+
if args.report_only:
|
|
70
|
+
return 0
|
|
71
|
+
|
|
72
|
+
out = rv.generate_dashboard(args.output)
|
|
73
|
+
size_kb = out.stat().st_size // 1024
|
|
74
|
+
print(f"\nDashboard saved : {out.resolve()}")
|
|
75
|
+
print(f"File size : {size_kb} KB")
|
|
76
|
+
print(f"\nOpen in browser :")
|
|
77
|
+
print(f" open {out}")
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
sys.exit(main())
|