mirror of
https://github.com/pim-n/pg-rad
synced 2026-03-22 21:48:11 +01:00
update tests and plotting
This commit is contained in:
@ -1,3 +1,7 @@
|
|||||||
|
from importlib.resources import files
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
from matplotlib import pyplot as plt
|
from matplotlib import pyplot as plt
|
||||||
from matplotlib.gridspec import GridSpec
|
from matplotlib.gridspec import GridSpec
|
||||||
|
|
||||||
@ -13,45 +17,79 @@ class ResultPlotter:
|
|||||||
self.source_res = output.sources
|
self.source_res = output.sources
|
||||||
|
|
||||||
def plot(self, landscape_z: float = 0):
|
def plot(self, landscape_z: float = 0):
|
||||||
fig = plt.figure(figsize=(12, 10), constrained_layout=True)
|
self._plot_main(landscape_z)
|
||||||
fig.suptitle(self.landscape.name)
|
self._plot_detector()
|
||||||
gs = GridSpec(
|
self._plot_metadata()
|
||||||
4,
|
|
||||||
2,
|
|
||||||
width_ratios=[0.5, 0.5],
|
|
||||||
height_ratios=[0.7, 0.1, 0.1, 0.1],
|
|
||||||
hspace=0.2)
|
|
||||||
|
|
||||||
ax1 = fig.add_subplot(gs[0, 0])
|
|
||||||
self._draw_count_rate(ax1)
|
|
||||||
|
|
||||||
ax2 = fig.add_subplot(gs[0, 1])
|
|
||||||
self._plot_landscape(ax2, landscape_z)
|
|
||||||
|
|
||||||
ax3 = fig.add_subplot(gs[1, :])
|
|
||||||
self._draw_table(ax3)
|
|
||||||
|
|
||||||
ax4 = fig.add_subplot(gs[2, :])
|
|
||||||
self._draw_detector_table(ax4)
|
|
||||||
|
|
||||||
ax5 = fig.add_subplot(gs[3, :])
|
|
||||||
self._draw_source_table(ax5)
|
|
||||||
|
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
|
def _plot_main(self, landscape_z):
|
||||||
|
fig = plt.figure(figsize=(12, 8))
|
||||||
|
fig.suptitle(self.landscape.name)
|
||||||
|
fig.canvas.manager.set_window_title("Main results")
|
||||||
|
|
||||||
|
gs = GridSpec(
|
||||||
|
2, 2, figure=fig,
|
||||||
|
height_ratios=[1, 2], width_ratios=[1, 1],
|
||||||
|
hspace=0.3, wspace=0.3)
|
||||||
|
|
||||||
|
ax_cps = fig.add_subplot(gs[0, 0])
|
||||||
|
self._draw_cps(ax_cps)
|
||||||
|
|
||||||
|
ax_counts = fig.add_subplot(gs[0, 1])
|
||||||
|
self._draw_count_rate(ax_counts)
|
||||||
|
|
||||||
|
ax_landscape = fig.add_subplot(gs[1, :])
|
||||||
|
self._plot_landscape(ax_landscape, landscape_z)
|
||||||
|
|
||||||
|
def _plot_detector(self):
|
||||||
|
det = self.landscape.detector
|
||||||
|
|
||||||
|
fig = plt.figure(figsize=(10, 4))
|
||||||
|
fig.canvas.manager.set_window_title("Detector")
|
||||||
|
|
||||||
|
gs = GridSpec(1, 2, figure=fig, width_ratios=[0.5, 0.5])
|
||||||
|
|
||||||
|
ax_table = fig.add_subplot(gs[0, 0])
|
||||||
|
self._draw_detector_table(ax_table)
|
||||||
|
|
||||||
|
if not det.is_isotropic:
|
||||||
|
ax_polar = fig.add_subplot(gs[0, 1], projection='polar')
|
||||||
|
|
||||||
|
energies = [
|
||||||
|
source.primary_gamma for source in self.source_res
|
||||||
|
]
|
||||||
|
|
||||||
|
self._draw_angular_efficiency_polar(ax_polar, det, energies[0])
|
||||||
|
|
||||||
|
def _plot_metadata(self):
|
||||||
|
fig, axs = plt.subplots(2, 1, figsize=(10, 6))
|
||||||
|
fig.canvas.manager.set_window_title("Simulation Metadata")
|
||||||
|
|
||||||
|
self._draw_table(axs[0])
|
||||||
|
self._draw_source_table(axs[1])
|
||||||
|
|
||||||
def _plot_landscape(self, ax, z):
|
def _plot_landscape(self, ax, z):
|
||||||
lp = LandscapeSlicePlotter()
|
lp = LandscapeSlicePlotter()
|
||||||
ax = lp.plot(landscape=self.landscape, z=z, ax=ax, show=False)
|
ax = lp.plot(landscape=self.landscape, z=z, ax=ax, show=False)
|
||||||
return ax
|
return ax
|
||||||
|
|
||||||
def _draw_count_rate(self, ax):
|
def _draw_cps(self, ax):
|
||||||
x = self.count_rate_res.arc_length
|
x = self.count_rate_res.sub_points
|
||||||
y = self.count_rate_res.count_rate
|
y = self.count_rate_res.cps
|
||||||
ax.plot(x, y, label='Count rate', color='r')
|
ax.plot(x, y, color='b')
|
||||||
ax.set_title('Count rate')
|
ax.set_title('Counts per second (CPS)')
|
||||||
ax.set_xlabel('Arc length s [m]')
|
ax.set_xlabel('Arc length s [m]')
|
||||||
ax.set_ylabel('Counts')
|
ax.set_ylabel('CPS [s$^{-1}$]')
|
||||||
ax.legend()
|
|
||||||
|
def _draw_count_rate(self, ax):
|
||||||
|
x = self.count_rate_res.acquisition_points
|
||||||
|
y = self.count_rate_res.integrated_counts
|
||||||
|
ax.plot(x, y, color='r', linestyle='--', alpha=0.2)
|
||||||
|
ax.scatter(x, y, color='r', marker='x')
|
||||||
|
ax.set_title('Integrated counts')
|
||||||
|
ax.set_xlabel('Arc length s [m]')
|
||||||
|
ax.set_ylabel('N')
|
||||||
|
|
||||||
def _draw_table(self, ax):
|
def _draw_table(self, ax):
|
||||||
ax.set_axis_off()
|
ax.set_axis_off()
|
||||||
@ -60,6 +98,7 @@ class ResultPlotter:
|
|||||||
data = [
|
data = [
|
||||||
["Air density (kg/m^3)", round(self.landscape.air_density, 3)],
|
["Air density (kg/m^3)", round(self.landscape.air_density, 3)],
|
||||||
["Total path length (m)", round(self.landscape.path.length, 3)],
|
["Total path length (m)", round(self.landscape.path.length, 3)],
|
||||||
|
["Readout points", len(self.count_rate_res.integrated_counts)],
|
||||||
]
|
]
|
||||||
|
|
||||||
ax.table(
|
ax.table(
|
||||||
@ -72,13 +111,28 @@ class ResultPlotter:
|
|||||||
|
|
||||||
def _draw_detector_table(self, ax):
|
def _draw_detector_table(self, ax):
|
||||||
det = self.landscape.detector
|
det = self.landscape.detector
|
||||||
print(det)
|
|
||||||
|
if det.is_isotropic:
|
||||||
|
det_type = "Isotropic"
|
||||||
|
else:
|
||||||
|
det_type = "Angular"
|
||||||
|
|
||||||
|
source_energies = [
|
||||||
|
source.primary_gamma for source in self.source_res
|
||||||
|
]
|
||||||
|
|
||||||
|
# list field efficiencies for each primary gamma in the landscape
|
||||||
|
effs = {e: det.get_efficiency(e) for e in source_energies}
|
||||||
|
formatted_effs = ", ".join(
|
||||||
|
f"{value:.3f} @ {key:.1f} keV"
|
||||||
|
for key, value in effs.items()
|
||||||
|
)
|
||||||
ax.set_axis_off()
|
ax.set_axis_off()
|
||||||
ax.set_title('Detector')
|
ax.set_title('Detector')
|
||||||
cols = ('Parameter', 'Value')
|
cols = ('Parameter', 'Value')
|
||||||
data = [
|
data = [
|
||||||
["Name", det.name],
|
["Detector", f"{det.name} ({det_type})"],
|
||||||
["Type", det.efficiency],
|
["Field efficiency", formatted_effs],
|
||||||
]
|
]
|
||||||
|
|
||||||
ax.table(
|
ax.table(
|
||||||
@ -119,3 +173,34 @@ class ResultPlotter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return ax
|
return ax
|
||||||
|
|
||||||
|
def _draw_angular_efficiency_polar(self, ax, detector, energy_keV):
|
||||||
|
# find the energies available for this detector.
|
||||||
|
csv = files('pg_rad.data.angular_efficiencies').joinpath(
|
||||||
|
detector.name + '.csv'
|
||||||
|
)
|
||||||
|
data = pd.read_csv(csv)
|
||||||
|
energy_cols = [col for col in data.columns if col != "angle"]
|
||||||
|
energies = np.array([float(col) for col in energy_cols])
|
||||||
|
|
||||||
|
# take energy column that is within 1% tolerance of energy_keV
|
||||||
|
rel_diff = np.abs(energies - energy_keV) / energies
|
||||||
|
match_idx = np.where(rel_diff <= 0.01)[0]
|
||||||
|
best_idx = match_idx[np.argmin(rel_diff[match_idx])]
|
||||||
|
col = energy_cols[best_idx]
|
||||||
|
|
||||||
|
theta_deg = data["angle"].to_numpy()
|
||||||
|
eff = data[col].to_numpy()
|
||||||
|
|
||||||
|
idx = np.argsort(theta_deg)
|
||||||
|
theta_deg = theta_deg[idx]
|
||||||
|
eff = eff[idx]
|
||||||
|
|
||||||
|
theta_deg = np.append(theta_deg, theta_deg[0])
|
||||||
|
eff = np.append(eff, eff[0])
|
||||||
|
|
||||||
|
theta_rad = np.radians(theta_deg)
|
||||||
|
|
||||||
|
print(theta_rad)
|
||||||
|
ax.plot(theta_rad, eff)
|
||||||
|
ax.set_title(f"Rel. angular efficiency @ {energy_keV:.1f} keV")
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pg_rad.physics import get_mass_attenuation_coeff
|
from pg_rad.utils.interpolators import get_mass_attenuation_coeff
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("energy,mu", [
|
@pytest.mark.parametrize("energy,mu", [
|
||||||
@ -8,7 +8,7 @@ from pg_rad.physics import get_mass_attenuation_coeff
|
|||||||
(1.00000E-02, 5.120E+00),
|
(1.00000E-02, 5.120E+00),
|
||||||
(1.00000E-01, 1.541E-01),
|
(1.00000E-01, 1.541E-01),
|
||||||
(1.00000E+00, 6.358E-02),
|
(1.00000E+00, 6.358E-02),
|
||||||
(1.00000E+01, 2.045E-02)
|
(1.00000E+01, 2.045E-02)
|
||||||
])
|
])
|
||||||
def test_exact_attenuation_retrieval(energy, mu):
|
def test_exact_attenuation_retrieval(energy, mu):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -3,20 +3,20 @@ import pytest
|
|||||||
|
|
||||||
from pg_rad.inputparser.parser import ConfigParser
|
from pg_rad.inputparser.parser import ConfigParser
|
||||||
from pg_rad.landscape.director import LandscapeDirector
|
from pg_rad.landscape.director import LandscapeDirector
|
||||||
from pg_rad.physics import calculate_fluence_at
|
from pg_rad.physics.fluence import phi
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def isotropic_detector():
|
def isotropic_detector():
|
||||||
from pg_rad.detector.detectors import IsotropicDetector
|
from pg_rad.detector.detector import load_detector
|
||||||
return IsotropicDetector(name="test_detector", eff=1.0)
|
return load_detector('dummy')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def phi_ref(test_landscape, isotropic_detector):
|
def phi_ref(test_landscape, isotropic_detector):
|
||||||
source = test_landscape.point_sources[0]
|
source = test_landscape.point_sources[0]
|
||||||
|
|
||||||
r = np.linalg.norm(np.array([10, 10, 0]) - np.array(source.pos))
|
r = np.linalg.norm(np.array([0, 0, 0]) - np.array(source.pos))
|
||||||
|
|
||||||
A = source.activity * 1E6
|
A = source.activity * 1E6
|
||||||
b = source.isotope.b
|
b = source.isotope.b
|
||||||
@ -43,12 +43,11 @@ def test_landscape():
|
|||||||
sources:
|
sources:
|
||||||
test_source:
|
test_source:
|
||||||
activity_MBq: 100
|
activity_MBq: 100
|
||||||
position: [0, 0, 0]
|
position: [0, 100, 0]
|
||||||
isotope: CS137
|
isotope: Cs137
|
||||||
|
gamma_energy_keV: 661
|
||||||
|
|
||||||
detector:
|
detector: dummy
|
||||||
name: dummy
|
|
||||||
is_isotropic: True
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cp = ConfigParser(test_yaml).parse()
|
cp = ConfigParser(test_yaml).parse()
|
||||||
@ -57,10 +56,15 @@ def test_landscape():
|
|||||||
|
|
||||||
|
|
||||||
def test_single_source_fluence(phi_ref, test_landscape, isotropic_detector):
|
def test_single_source_fluence(phi_ref, test_landscape, isotropic_detector):
|
||||||
phi = calculate_fluence_at(
|
s = test_landscape.point_sources[0]
|
||||||
test_landscape,
|
r = np.linalg.norm(np.array([0, 0, 0]) - np.array(s.pos))
|
||||||
np.array([10, 10, 0]),
|
phi_calc = phi(
|
||||||
isotropic_detector,
|
r,
|
||||||
tangent_vectors=None,
|
s.activity*1E6,
|
||||||
|
s.isotope.b,
|
||||||
|
s.isotope.mu_mass_air,
|
||||||
|
test_landscape.air_density,
|
||||||
|
isotropic_detector.get_efficiency(s.isotope.E)
|
||||||
)
|
)
|
||||||
assert pytest.approx(phi[0], rel=1E-3) == phi_ref
|
|
||||||
|
assert pytest.approx(phi_calc, rel=1E-6) == phi_ref
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pg_rad.objects import PointSource
|
from pg_rad.objects.sources import PointSource
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|||||||
Reference in New Issue
Block a user