Module ctsimu.evaluation.test2D_SW_2
Test 2D-SW-2: Spectral filtering
In this test scenario, spectral filtering is tested. Two spherical wedges with 9 steps of different thicknesses are simulated: one made of aluminum, the other one made of iron. For both wedges, we define two scenarios each: one at an X-ray spectrum of 200 kV without any additional filters (apart from the tube window), the other one with an additional copper filter (2 mm) by which the spectrum shall be filtered. This will result in grey value ratios between the wedge steps which are characteristic to each scenario. Those ratios are compared to the ratios obtained from particle-transport Monte-Carlo simulations of the corresponding scenarios.
When running the test evaluation, please take care to use the correct keywords for each sub-test, as shown in the code example below. It is not necessary to run all tests at once, but possible.
from ctsimu.toolbox import Toolbox
Toolbox("2D-SW-2",
Al = "2D-SW-2_Al_200kV_noFilter_metadata.json",
AlCu = "2D-SW-2_Al_200kV_Filter2mmCu_metadata.json",
Fe = "2D-SW-2_Fe_200kV_noFilter_metadata.json",
FeCu = "2D-SW-2_Fe_200kV_Filter2mmCu_metadata.json"
)
The regions of interest (ROI) where the gray values are measured are the same as shown for scenario ctsimu.evaluation.test2D_SW_1
. The errors of the gray value ratios between the steps of the wedges in the Monte-Carlo simulations are obtained by Gaussian error propagation in analogy to the procedure shown for 2D-SW-1.
The result files list the measured grey values and their corresponding ROIs, as well as the measured grey value ratios between subsequent steps. For the ratios, it also lists the Monte-Carlo reference values and their uncertainties. Just like for scenario 2D-SW-1, values are given for simulations considering only primary
radiation (without scatter radiation), and simulations which consider both (labelled scatter
in the result files). Plots such as the example shown below are produced for each sub-test.
The example evaluation result displays the gray value ratios between each pair of neighboring steps of the iron wedge. Small crosses represent the grey value ratios calculated from the Monte-Carlo simulations. Their error bars cover the ratio's uncertainty u in both directions. Solid circles without error bars show the measured ratios from the projections of the simulation software.
Expand source code
# -*- coding: UTF-8 -*-
"""# Test 2D-SW-2: Spectral filtering
.. include:: ./test2D_SW_2.md
"""
from ..test import *
from ..helpers import *
import pkgutil
import io
class Test2D_SW_2_results:
""" Results for one sub test of the filtering scenario. """
def __init__(self):
self.longName = ""
# Grey value means and ratios (per wedge step):
self.means = [] # Mean value for each step
self.ratios = None # Grey value ratios between the steps
# Grey values from Monte-Carlo simuation:
self.means_rosi_total = None # accounting for scatter radiation
self.means_rosi_primary = None # accounting only for primary radiation
self.means_mcray_total = None # accounting for scatter radiation
self.means_mcray_primary = None # accounting only for primary radiation
# Reference values for Monte-Carlo simulation (calculated in loadReference())
self.means_mc_total = None # accounting for scatter radiation
self.means_mc_primary = None # accounting only for primary radiation
self.error_mc_total_upper = None
self.error_mc_total_lower = None
self.error_mc_primary_upper = None
self.error_mc_primary_lower = None
# reference grey value ratios
self.ratios_mc_total = None
self.ratios_mc_primary = None
self.error_ratios_mc_total_upper = []
self.error_ratios_mc_total_lower = []
self.error_ratios_mc_primary_upper = []
self.error_ratios_mc_primary_lower = []
def loadReference(self, name):
dataText = pkgutil.get_data(__name__, "data/2D-SW-2_scenario{name}.txt".format(name=name)).decode()
dataIO = io.StringIO(dataText)
allData = numpy.loadtxt(dataIO, delimiter='\t') # ignore free beam
# --- total radiation
means_rosi_total = allData[:,1]
means_mcray_total = allData[:,4]
means_total = (means_rosi_total + means_mcray_total) * 0.5
delta_total = numpy.absolute(means_rosi_total - means_mcray_total)
stddev_rosi_total = allData[:,2]
stddev_mcray_total = allData[:,5]
#err_total = 0.5*(delta_total + numpy.sqrt(numpy.square(stddev_rosi_total) + numpy.square(stddev_mcray_total)))
err_total_rosi = numpy.fmax(stddev_rosi_total, stddev_mcray_total-delta_total) + 0.5*delta_total
err_total_mcray = numpy.fmax(stddev_mcray_total, stddev_rosi_total-delta_total) + 0.5*delta_total
err_total_upper = numpy.zeros_like(err_total_rosi)
err_total_lower = numpy.zeros_like(err_total_rosi)
for i in range(len(err_total_upper)):
if means_rosi_total[i] > means_total[i]: # ROSI is upper bound
err_total_upper[i] = err_total_rosi[i]
err_total_lower[i] = err_total_mcray[i]
else: # McRay is upper bound
err_total_upper[i] = err_total_mcray[i]
err_total_lower[i] = err_total_rosi[i]
# --- primary radiation
means_rosi_primary = allData[:,9]
means_mcray_primary = allData[:,12]
means_primary = (means_rosi_primary + means_mcray_primary) * 0.5
delta_primary = numpy.absolute(means_rosi_primary - means_mcray_primary)
stddev_rosi_primary = allData[:,10]
stddev_mcray_primary = allData[:,13]
#err_primary = 0.5*(delta_primary + numpy.sqrt(numpy.square(stddev_rosi_primary) + numpy.square(stddev_mcray_primary)))
err_primary_rosi = numpy.fmax(stddev_rosi_primary, stddev_mcray_primary-delta_primary) + 0.5*delta_primary
err_primary_mcray = numpy.fmax(stddev_mcray_primary, stddev_rosi_primary-delta_primary) + 0.5*delta_primary
err_primary_upper = numpy.zeros_like(err_primary_rosi)
err_primary_lower = numpy.zeros_like(err_primary_rosi)
for i in range(len(err_primary_upper)):
if means_rosi_primary[i] > means_primary[i]: # ROSI is upper bound
err_primary_upper[i] = err_primary_rosi[i]
err_primary_lower[i] = err_primary_mcray[i]
else: # McRay is upper bound
err_primary_upper[i] = err_primary_mcray[i]
err_primary_lower[i] = err_primary_rosi[i]
self.means_rosi_total = means_rosi_total
self.means_rosi_primary = means_rosi_primary
self.means_mcray_total = means_mcray_total
self.means_mcray_primary = means_mcray_primary
self.means_mc_total = means_total
self.means_mc_primary = means_primary
self.error_mc_total_upper = err_total_upper
self.error_mc_total_lower = err_total_lower
self.error_mc_primary_upper = err_primary_upper
self.error_mc_primary_lower = err_primary_lower
dataIO.close()
self.ratios_mc_total = ratios(self.means_mc_total)
self.ratios_mc_primary = ratios(self.means_mc_primary)
# calculate maximum uncertainties of MC ratios:
for v in range(1, len(self.ratios_mc_primary)+1):
c, uncertainty_total_upper = divide_and_error(
muA = self.means_mc_total[v-1],
muB = self.means_mc_total[v],
errA = self.error_mc_total_upper[v],
errB = self.error_mc_total_upper[v-1]
)
c, uncertainty_primary_upper = divide_and_error(
muA = self.means_mc_primary[v-1],
muB = self.means_mc_primary[v],
errA = self.error_mc_primary_upper[v],
errB = self.error_mc_primary_upper[v-1]
)
c, uncertainty_total_lower = divide_and_error(
muA = self.means_mc_total[v-1],
muB = self.means_mc_total[v],
errA = self.error_mc_total_lower[v],
errB = self.error_mc_total_lower[v-1]
)
c, uncertainty_primary_lower = divide_and_error(
muA = self.means_mc_primary[v-1],
muB = self.means_mc_primary[v],
errA = self.error_mc_primary_lower[v],
errB = self.error_mc_primary_lower[v-1]
)
self.error_ratios_mc_total_upper.append(uncertainty_total_upper)
self.error_ratios_mc_primary_upper.append(uncertainty_primary_upper)
self.error_ratios_mc_total_lower.append(uncertainty_total_lower)
self.error_ratios_mc_primary_lower.append(uncertainty_primary_lower)
class Test2D_SW_2(generalTest):
""" CTSimU test 2D-SW-1: detector models / scintillators.
CTSimU test 2D-SW-2: spectral filtering. """
def __init__(self, resultFileDirectory=".", name=None, rawOutput=False):
generalTest.__init__(
self,
testName="2D-SW-2",
name=name,
resultFileDirectory=resultFileDirectory,
rawOutput=rawOutput)
self.results = []
self.shrink = 35
self.leftOffset = 510 - self.shrink
self.nPixels = 20 + 2*self.shrink
# Absolute step definitions. Will be shrunk to accept tolerance border,
# but these absolute definitions are needed for grey value rescaling and clipping.
self.steps = [
ImageROI(self.leftOffset, 819, self.leftOffset+self.nPixels, 910),
ImageROI(self.leftOffset, 728, self.leftOffset+self.nPixels, 819),
ImageROI(self.leftOffset, 637, self.leftOffset+self.nPixels, 728),
ImageROI(self.leftOffset, 546, self.leftOffset+self.nPixels, 637),
ImageROI(self.leftOffset, 455, self.leftOffset+self.nPixels, 546),
ImageROI(self.leftOffset, 364, self.leftOffset+self.nPixels, 455),
ImageROI(self.leftOffset, 273, self.leftOffset+self.nPixels, 364),
ImageROI(self.leftOffset, 182, self.leftOffset+self.nPixels, 273),
ImageROI(self.leftOffset, 91, self.leftOffset+self.nPixels, 182),
ImageROI(self.leftOffset, 0, self.leftOffset+self.nPixels, 91) # free beam
]
def prepare(self):
""" Preparations before the test will be run with the images from the pipeline. """
self.prepared = True
def prepareRun(self, i):
if i < len(self.subtests):
results = Test2D_SW_2_results()
if self.subtests[i] == "Al":
results.loadReference("01_Al_noFilter_200kV-poly_Ideal")
results.longName = "Al wedge, no filter"
elif self.subtests[i] == "AlCu":
results.loadReference("02_Al_CuFilter_200kV-poly_Ideal")
results.longName = "Al wedge, 2 mm Cu filter"
elif self.subtests[i] == "Fe":
results.loadReference("03_Fe_noFilter_200kV-poly_Ideal")
results.longName = "Fe wedge, no filter"
elif self.subtests[i] == "FeCu":
results.loadReference("04_Fe_CuFilter_200kV-poly_Ideal")
results.longName = "Fe wedge, 2 mm Cu filter"
else:
raise Exception("{key} is not a valid subtest identifier for test scenario {test}".format(key=self.subtests[i], test=self.testName))
self.name = self.testName
self.results.append(results)
else:
if len(self.subtests) == 0:
raise Exception("Please provide keywords that identify which metadata file belongs to which subtest. Test {testname} accepts the following keywords: 'Al', 'AlCu', 'Fe' and 'FeCu'.".format(testname=self.testName))
else:
raise Exception("Number of provided image metadata files exceeds number of test runs ({expected}).".format(expected=len(self.subtests)))
def run(self, image):
self.prepare()
self.prepareRun(self.currentRun)
i = self.currentRun
subtestName = self.subtests[i]
# Grey value summary
statsText = "# Evaluation of Test {name}, {subname}:\n".format(name=self.name, subname=subtestName)
statsText += "# {longDesc}\n".format(longDesc=self.results[i].longName)
statsText += "# \n"
statsText += "# ROI mean grey value per step\n"
statsText += "# step\tx0\ty0\tx1\ty1\twidth [px]\theight [px]\tarea [px]\tmean [GV]\n"
step = 0
for roi in self.steps:
step += 1
smallerROI = copy.deepcopy(roi)
smallerROI.grow(-self.shrink)
stats = image.stats(smallerROI)
statsText += "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{:.3f}\n".format(step, smallerROI.x0, smallerROI.y0, smallerROI.x1, smallerROI.y1, stats["width"], stats["height"], stats["area"], stats["mean"])
self.results[i].means.append(stats["mean"])
self.results[i].ratios = ratios(self.results[i].means)
statsFileName = "{dir}/{name}_{subname}_grey_values.txt".format(dir=self.resultFileDirectory, name=self.name, subname=subtestName)
with open(statsFileName, 'w') as statsFile:
statsFile.write(statsText)
statsFile.close()
# Ratio summary
ratioText = "# Evaluation of Test {name}, {subname}:\n".format(name=self.name, subname=subtestName)
ratioText += "# {longDesc}\n".format(longDesc=self.results[i].longName)
ratioText += "# \n"
ratioText += "# Grey value ratios\n"
ratioText += "# step A\tstep B\tratio (A/B)\treference ratio (primary)\terror_lower (ref. primary)\terror_upper (ref. primary)\trel. deviation (primary)\treference ratio (total)\terror_lower (ref. total)\terror_upper (ref. total)\trel. deviation (total)\n"
for r in range(len(self.results[i].ratios)):
ratioText += "{A}\t{B}\t{ratio:.5f}\t{refPrimary:.5f}\t{errorPrimaryLower:.5f}\t{errorPrimaryUpper:.5f}\t{devFacPrimary:.5f}\t{reftotal:.5f}\t{errorTotalLower:.5f}\t{errorTotalUpper:.5f}\t{devFacTotal:.5f}\n".format(
A = (r+2),
B = (r+1),
ratio = self.results[i].ratios[r],
refPrimary = self.results[i].ratios_mc_primary[r],
errorPrimaryLower = self.results[i].error_ratios_mc_primary_lower[r],
errorPrimaryUpper = self.results[i].error_ratios_mc_primary_upper[r],
devFacPrimary = (self.results[i].ratios[r] / self.results[i].ratios_mc_primary[r] - 1),
reftotal = self.results[i].ratios_mc_total[r],
errorTotalLower = self.results[i].error_ratios_mc_total_lower[r],
errorTotalUpper = self.results[i].error_ratios_mc_total_upper[r],
devFacTotal = (self.results[i].ratios[r] / self.results[i].ratios_mc_total[r] - 1)
)
ratioFileName = "{dir}/{name}_{subname}_ratios.txt".format(dir=self.resultFileDirectory, name=self.name, subname=subtestName)
with open(ratioFileName, 'w') as ratiosFile:
ratiosFile.write(ratioText)
ratiosFile.close()
self.plotResults()
self.currentRun += 1
return image
def followUp(self):
pass
def plotResults(self):
i = self.currentRun
subtestName = self.subtests[i]
xValues = numpy.linspace(0, len(self.results[i].ratios)-1, len(self.results[i].ratios), endpoint=True)
xLabels = ("2 by 1", "3 by 2", "4 by 3", "5 by 4", "6 by 5", "7 by 6", "8 by 7", "9 by 8", "10 by 9")
try:
import matplotlib
import matplotlib.pyplot
from matplotlib.ticker import (MultipleLocator, FormatStrFormatter, AutoMinorLocator)
matplotlib.use("agg")
for mode in ("primary", "total"):
fig, ax = matplotlib.pyplot.subplots(nrows=1, ncols=1, figsize=(8, 5))
# Grey Value Profile:
if mode == "primary":
modeDescription = "primary radiation"
ax.errorbar(xValues, self.results[i].ratios_mc_primary, xerr=None, yerr=[self.results[i].error_ratios_mc_primary_lower, self.results[i].error_ratios_mc_primary_upper], linewidth=0, elinewidth=2.0, ecolor='#fe6100')
ax.plot(xValues, self.results[i].ratios_mc_primary, '_', markersize=11.0, label="Monte-Carlo reference", color='#fe6100')
else:
modeDescription = "total radiation (scatter+primary)"
ax.errorbar(xValues, self.results[i].ratios_mc_total, xerr=None, yerr=[self.results[i].error_ratios_mc_total_lower, self.results[i].error_ratios_mc_total_upper], linewidth=0, elinewidth=2.0, ecolor='#fe6100')
ax.plot(xValues, self.results[i].ratios_mc_total, '_', markersize=11.0, label="Monte-Carlo reference", color='#fe6100')
ax.plot(xValues, self.results[i].ratios, 'o', markersize=5.0, label="measured", color='#1f77b4')
ax.set_xlabel("step pair division")
ax.set_ylabel("grey value ratio")
ax.set_title("2D-SW-2, {sub}, {details}".format(sub=self.results[i].longName, details=modeDescription), loc="left", fontsize=10)
ax.set_xticks(xValues)
ax.xaxis.set_ticklabels(xLabels)
ax.grid(visible=True, which='major', axis='both', color='#d9d9d9', linestyle='dashed')
ax.grid(visible=True, which='minor', axis='both', color='#e7e7e7', linestyle='dotted')
ax.legend(loc='lower left')
fig.tight_layout(pad=2.5)
plotFilename = "{dir}/{name}_{subname}_ratios_{mode}.png".format(dir=self.resultFileDirectory, name=self.name, subname=subtestName, mode=mode)
matplotlib.pyplot.savefig(plotFilename)
fig.clf()
matplotlib.pyplot.close('all')
except Exception as e:
log(f"Warning: Error plotting results for test {self.name}, {subtestName} using matplotlib: {e}")
Classes
class Test2D_SW_2 (resultFileDirectory='.', name=None, rawOutput=False)
-
CTSimU test 2D-SW-1: detector models / scintillators. CTSimU test 2D-SW-2: spectral filtering.
Expand source code
class Test2D_SW_2(generalTest): """ CTSimU test 2D-SW-1: detector models / scintillators. CTSimU test 2D-SW-2: spectral filtering. """ def __init__(self, resultFileDirectory=".", name=None, rawOutput=False): generalTest.__init__( self, testName="2D-SW-2", name=name, resultFileDirectory=resultFileDirectory, rawOutput=rawOutput) self.results = [] self.shrink = 35 self.leftOffset = 510 - self.shrink self.nPixels = 20 + 2*self.shrink # Absolute step definitions. Will be shrunk to accept tolerance border, # but these absolute definitions are needed for grey value rescaling and clipping. self.steps = [ ImageROI(self.leftOffset, 819, self.leftOffset+self.nPixels, 910), ImageROI(self.leftOffset, 728, self.leftOffset+self.nPixels, 819), ImageROI(self.leftOffset, 637, self.leftOffset+self.nPixels, 728), ImageROI(self.leftOffset, 546, self.leftOffset+self.nPixels, 637), ImageROI(self.leftOffset, 455, self.leftOffset+self.nPixels, 546), ImageROI(self.leftOffset, 364, self.leftOffset+self.nPixels, 455), ImageROI(self.leftOffset, 273, self.leftOffset+self.nPixels, 364), ImageROI(self.leftOffset, 182, self.leftOffset+self.nPixels, 273), ImageROI(self.leftOffset, 91, self.leftOffset+self.nPixels, 182), ImageROI(self.leftOffset, 0, self.leftOffset+self.nPixels, 91) # free beam ] def prepare(self): """ Preparations before the test will be run with the images from the pipeline. """ self.prepared = True def prepareRun(self, i): if i < len(self.subtests): results = Test2D_SW_2_results() if self.subtests[i] == "Al": results.loadReference("01_Al_noFilter_200kV-poly_Ideal") results.longName = "Al wedge, no filter" elif self.subtests[i] == "AlCu": results.loadReference("02_Al_CuFilter_200kV-poly_Ideal") results.longName = "Al wedge, 2 mm Cu filter" elif self.subtests[i] == "Fe": results.loadReference("03_Fe_noFilter_200kV-poly_Ideal") results.longName = "Fe wedge, no filter" elif self.subtests[i] == "FeCu": results.loadReference("04_Fe_CuFilter_200kV-poly_Ideal") results.longName = "Fe wedge, 2 mm Cu filter" else: raise Exception("{key} is not a valid subtest identifier for test scenario {test}".format(key=self.subtests[i], test=self.testName)) self.name = self.testName self.results.append(results) else: if len(self.subtests) == 0: raise Exception("Please provide keywords that identify which metadata file belongs to which subtest. Test {testname} accepts the following keywords: 'Al', 'AlCu', 'Fe' and 'FeCu'.".format(testname=self.testName)) else: raise Exception("Number of provided image metadata files exceeds number of test runs ({expected}).".format(expected=len(self.subtests))) def run(self, image): self.prepare() self.prepareRun(self.currentRun) i = self.currentRun subtestName = self.subtests[i] # Grey value summary statsText = "# Evaluation of Test {name}, {subname}:\n".format(name=self.name, subname=subtestName) statsText += "# {longDesc}\n".format(longDesc=self.results[i].longName) statsText += "# \n" statsText += "# ROI mean grey value per step\n" statsText += "# step\tx0\ty0\tx1\ty1\twidth [px]\theight [px]\tarea [px]\tmean [GV]\n" step = 0 for roi in self.steps: step += 1 smallerROI = copy.deepcopy(roi) smallerROI.grow(-self.shrink) stats = image.stats(smallerROI) statsText += "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{:.3f}\n".format(step, smallerROI.x0, smallerROI.y0, smallerROI.x1, smallerROI.y1, stats["width"], stats["height"], stats["area"], stats["mean"]) self.results[i].means.append(stats["mean"]) self.results[i].ratios = ratios(self.results[i].means) statsFileName = "{dir}/{name}_{subname}_grey_values.txt".format(dir=self.resultFileDirectory, name=self.name, subname=subtestName) with open(statsFileName, 'w') as statsFile: statsFile.write(statsText) statsFile.close() # Ratio summary ratioText = "# Evaluation of Test {name}, {subname}:\n".format(name=self.name, subname=subtestName) ratioText += "# {longDesc}\n".format(longDesc=self.results[i].longName) ratioText += "# \n" ratioText += "# Grey value ratios\n" ratioText += "# step A\tstep B\tratio (A/B)\treference ratio (primary)\terror_lower (ref. primary)\terror_upper (ref. primary)\trel. deviation (primary)\treference ratio (total)\terror_lower (ref. total)\terror_upper (ref. total)\trel. deviation (total)\n" for r in range(len(self.results[i].ratios)): ratioText += "{A}\t{B}\t{ratio:.5f}\t{refPrimary:.5f}\t{errorPrimaryLower:.5f}\t{errorPrimaryUpper:.5f}\t{devFacPrimary:.5f}\t{reftotal:.5f}\t{errorTotalLower:.5f}\t{errorTotalUpper:.5f}\t{devFacTotal:.5f}\n".format( A = (r+2), B = (r+1), ratio = self.results[i].ratios[r], refPrimary = self.results[i].ratios_mc_primary[r], errorPrimaryLower = self.results[i].error_ratios_mc_primary_lower[r], errorPrimaryUpper = self.results[i].error_ratios_mc_primary_upper[r], devFacPrimary = (self.results[i].ratios[r] / self.results[i].ratios_mc_primary[r] - 1), reftotal = self.results[i].ratios_mc_total[r], errorTotalLower = self.results[i].error_ratios_mc_total_lower[r], errorTotalUpper = self.results[i].error_ratios_mc_total_upper[r], devFacTotal = (self.results[i].ratios[r] / self.results[i].ratios_mc_total[r] - 1) ) ratioFileName = "{dir}/{name}_{subname}_ratios.txt".format(dir=self.resultFileDirectory, name=self.name, subname=subtestName) with open(ratioFileName, 'w') as ratiosFile: ratiosFile.write(ratioText) ratiosFile.close() self.plotResults() self.currentRun += 1 return image def followUp(self): pass def plotResults(self): i = self.currentRun subtestName = self.subtests[i] xValues = numpy.linspace(0, len(self.results[i].ratios)-1, len(self.results[i].ratios), endpoint=True) xLabels = ("2 by 1", "3 by 2", "4 by 3", "5 by 4", "6 by 5", "7 by 6", "8 by 7", "9 by 8", "10 by 9") try: import matplotlib import matplotlib.pyplot from matplotlib.ticker import (MultipleLocator, FormatStrFormatter, AutoMinorLocator) matplotlib.use("agg") for mode in ("primary", "total"): fig, ax = matplotlib.pyplot.subplots(nrows=1, ncols=1, figsize=(8, 5)) # Grey Value Profile: if mode == "primary": modeDescription = "primary radiation" ax.errorbar(xValues, self.results[i].ratios_mc_primary, xerr=None, yerr=[self.results[i].error_ratios_mc_primary_lower, self.results[i].error_ratios_mc_primary_upper], linewidth=0, elinewidth=2.0, ecolor='#fe6100') ax.plot(xValues, self.results[i].ratios_mc_primary, '_', markersize=11.0, label="Monte-Carlo reference", color='#fe6100') else: modeDescription = "total radiation (scatter+primary)" ax.errorbar(xValues, self.results[i].ratios_mc_total, xerr=None, yerr=[self.results[i].error_ratios_mc_total_lower, self.results[i].error_ratios_mc_total_upper], linewidth=0, elinewidth=2.0, ecolor='#fe6100') ax.plot(xValues, self.results[i].ratios_mc_total, '_', markersize=11.0, label="Monte-Carlo reference", color='#fe6100') ax.plot(xValues, self.results[i].ratios, 'o', markersize=5.0, label="measured", color='#1f77b4') ax.set_xlabel("step pair division") ax.set_ylabel("grey value ratio") ax.set_title("2D-SW-2, {sub}, {details}".format(sub=self.results[i].longName, details=modeDescription), loc="left", fontsize=10) ax.set_xticks(xValues) ax.xaxis.set_ticklabels(xLabels) ax.grid(visible=True, which='major', axis='both', color='#d9d9d9', linestyle='dashed') ax.grid(visible=True, which='minor', axis='both', color='#e7e7e7', linestyle='dotted') ax.legend(loc='lower left') fig.tight_layout(pad=2.5) plotFilename = "{dir}/{name}_{subname}_ratios_{mode}.png".format(dir=self.resultFileDirectory, name=self.name, subname=subtestName, mode=mode) matplotlib.pyplot.savefig(plotFilename) fig.clf() matplotlib.pyplot.close('all') except Exception as e: log(f"Warning: Error plotting results for test {self.name}, {subtestName} using matplotlib: {e}")
Ancestors
Methods
def prepare(self)
-
Preparations before the test will be run with the images from the pipeline.
Expand source code
def prepare(self): """ Preparations before the test will be run with the images from the pipeline. """ self.prepared = True
def prepareRun(self, i)
-
Expand source code
def prepareRun(self, i): if i < len(self.subtests): results = Test2D_SW_2_results() if self.subtests[i] == "Al": results.loadReference("01_Al_noFilter_200kV-poly_Ideal") results.longName = "Al wedge, no filter" elif self.subtests[i] == "AlCu": results.loadReference("02_Al_CuFilter_200kV-poly_Ideal") results.longName = "Al wedge, 2 mm Cu filter" elif self.subtests[i] == "Fe": results.loadReference("03_Fe_noFilter_200kV-poly_Ideal") results.longName = "Fe wedge, no filter" elif self.subtests[i] == "FeCu": results.loadReference("04_Fe_CuFilter_200kV-poly_Ideal") results.longName = "Fe wedge, 2 mm Cu filter" else: raise Exception("{key} is not a valid subtest identifier for test scenario {test}".format(key=self.subtests[i], test=self.testName)) self.name = self.testName self.results.append(results) else: if len(self.subtests) == 0: raise Exception("Please provide keywords that identify which metadata file belongs to which subtest. Test {testname} accepts the following keywords: 'Al', 'AlCu', 'Fe' and 'FeCu'.".format(testname=self.testName)) else: raise Exception("Number of provided image metadata files exceeds number of test runs ({expected}).".format(expected=len(self.subtests)))
Inherited members
class Test2D_SW_2_results
-
Results for one sub test of the filtering scenario.
Expand source code
class Test2D_SW_2_results: """ Results for one sub test of the filtering scenario. """ def __init__(self): self.longName = "" # Grey value means and ratios (per wedge step): self.means = [] # Mean value for each step self.ratios = None # Grey value ratios between the steps # Grey values from Monte-Carlo simuation: self.means_rosi_total = None # accounting for scatter radiation self.means_rosi_primary = None # accounting only for primary radiation self.means_mcray_total = None # accounting for scatter radiation self.means_mcray_primary = None # accounting only for primary radiation # Reference values for Monte-Carlo simulation (calculated in loadReference()) self.means_mc_total = None # accounting for scatter radiation self.means_mc_primary = None # accounting only for primary radiation self.error_mc_total_upper = None self.error_mc_total_lower = None self.error_mc_primary_upper = None self.error_mc_primary_lower = None # reference grey value ratios self.ratios_mc_total = None self.ratios_mc_primary = None self.error_ratios_mc_total_upper = [] self.error_ratios_mc_total_lower = [] self.error_ratios_mc_primary_upper = [] self.error_ratios_mc_primary_lower = [] def loadReference(self, name): dataText = pkgutil.get_data(__name__, "data/2D-SW-2_scenario{name}.txt".format(name=name)).decode() dataIO = io.StringIO(dataText) allData = numpy.loadtxt(dataIO, delimiter='\t') # ignore free beam # --- total radiation means_rosi_total = allData[:,1] means_mcray_total = allData[:,4] means_total = (means_rosi_total + means_mcray_total) * 0.5 delta_total = numpy.absolute(means_rosi_total - means_mcray_total) stddev_rosi_total = allData[:,2] stddev_mcray_total = allData[:,5] #err_total = 0.5*(delta_total + numpy.sqrt(numpy.square(stddev_rosi_total) + numpy.square(stddev_mcray_total))) err_total_rosi = numpy.fmax(stddev_rosi_total, stddev_mcray_total-delta_total) + 0.5*delta_total err_total_mcray = numpy.fmax(stddev_mcray_total, stddev_rosi_total-delta_total) + 0.5*delta_total err_total_upper = numpy.zeros_like(err_total_rosi) err_total_lower = numpy.zeros_like(err_total_rosi) for i in range(len(err_total_upper)): if means_rosi_total[i] > means_total[i]: # ROSI is upper bound err_total_upper[i] = err_total_rosi[i] err_total_lower[i] = err_total_mcray[i] else: # McRay is upper bound err_total_upper[i] = err_total_mcray[i] err_total_lower[i] = err_total_rosi[i] # --- primary radiation means_rosi_primary = allData[:,9] means_mcray_primary = allData[:,12] means_primary = (means_rosi_primary + means_mcray_primary) * 0.5 delta_primary = numpy.absolute(means_rosi_primary - means_mcray_primary) stddev_rosi_primary = allData[:,10] stddev_mcray_primary = allData[:,13] #err_primary = 0.5*(delta_primary + numpy.sqrt(numpy.square(stddev_rosi_primary) + numpy.square(stddev_mcray_primary))) err_primary_rosi = numpy.fmax(stddev_rosi_primary, stddev_mcray_primary-delta_primary) + 0.5*delta_primary err_primary_mcray = numpy.fmax(stddev_mcray_primary, stddev_rosi_primary-delta_primary) + 0.5*delta_primary err_primary_upper = numpy.zeros_like(err_primary_rosi) err_primary_lower = numpy.zeros_like(err_primary_rosi) for i in range(len(err_primary_upper)): if means_rosi_primary[i] > means_primary[i]: # ROSI is upper bound err_primary_upper[i] = err_primary_rosi[i] err_primary_lower[i] = err_primary_mcray[i] else: # McRay is upper bound err_primary_upper[i] = err_primary_mcray[i] err_primary_lower[i] = err_primary_rosi[i] self.means_rosi_total = means_rosi_total self.means_rosi_primary = means_rosi_primary self.means_mcray_total = means_mcray_total self.means_mcray_primary = means_mcray_primary self.means_mc_total = means_total self.means_mc_primary = means_primary self.error_mc_total_upper = err_total_upper self.error_mc_total_lower = err_total_lower self.error_mc_primary_upper = err_primary_upper self.error_mc_primary_lower = err_primary_lower dataIO.close() self.ratios_mc_total = ratios(self.means_mc_total) self.ratios_mc_primary = ratios(self.means_mc_primary) # calculate maximum uncertainties of MC ratios: for v in range(1, len(self.ratios_mc_primary)+1): c, uncertainty_total_upper = divide_and_error( muA = self.means_mc_total[v-1], muB = self.means_mc_total[v], errA = self.error_mc_total_upper[v], errB = self.error_mc_total_upper[v-1] ) c, uncertainty_primary_upper = divide_and_error( muA = self.means_mc_primary[v-1], muB = self.means_mc_primary[v], errA = self.error_mc_primary_upper[v], errB = self.error_mc_primary_upper[v-1] ) c, uncertainty_total_lower = divide_and_error( muA = self.means_mc_total[v-1], muB = self.means_mc_total[v], errA = self.error_mc_total_lower[v], errB = self.error_mc_total_lower[v-1] ) c, uncertainty_primary_lower = divide_and_error( muA = self.means_mc_primary[v-1], muB = self.means_mc_primary[v], errA = self.error_mc_primary_lower[v], errB = self.error_mc_primary_lower[v-1] ) self.error_ratios_mc_total_upper.append(uncertainty_total_upper) self.error_ratios_mc_primary_upper.append(uncertainty_primary_upper) self.error_ratios_mc_total_lower.append(uncertainty_total_lower) self.error_ratios_mc_primary_lower.append(uncertainty_primary_lower)
Methods
def loadReference(self, name)
-
Expand source code
def loadReference(self, name): dataText = pkgutil.get_data(__name__, "data/2D-SW-2_scenario{name}.txt".format(name=name)).decode() dataIO = io.StringIO(dataText) allData = numpy.loadtxt(dataIO, delimiter='\t') # ignore free beam # --- total radiation means_rosi_total = allData[:,1] means_mcray_total = allData[:,4] means_total = (means_rosi_total + means_mcray_total) * 0.5 delta_total = numpy.absolute(means_rosi_total - means_mcray_total) stddev_rosi_total = allData[:,2] stddev_mcray_total = allData[:,5] #err_total = 0.5*(delta_total + numpy.sqrt(numpy.square(stddev_rosi_total) + numpy.square(stddev_mcray_total))) err_total_rosi = numpy.fmax(stddev_rosi_total, stddev_mcray_total-delta_total) + 0.5*delta_total err_total_mcray = numpy.fmax(stddev_mcray_total, stddev_rosi_total-delta_total) + 0.5*delta_total err_total_upper = numpy.zeros_like(err_total_rosi) err_total_lower = numpy.zeros_like(err_total_rosi) for i in range(len(err_total_upper)): if means_rosi_total[i] > means_total[i]: # ROSI is upper bound err_total_upper[i] = err_total_rosi[i] err_total_lower[i] = err_total_mcray[i] else: # McRay is upper bound err_total_upper[i] = err_total_mcray[i] err_total_lower[i] = err_total_rosi[i] # --- primary radiation means_rosi_primary = allData[:,9] means_mcray_primary = allData[:,12] means_primary = (means_rosi_primary + means_mcray_primary) * 0.5 delta_primary = numpy.absolute(means_rosi_primary - means_mcray_primary) stddev_rosi_primary = allData[:,10] stddev_mcray_primary = allData[:,13] #err_primary = 0.5*(delta_primary + numpy.sqrt(numpy.square(stddev_rosi_primary) + numpy.square(stddev_mcray_primary))) err_primary_rosi = numpy.fmax(stddev_rosi_primary, stddev_mcray_primary-delta_primary) + 0.5*delta_primary err_primary_mcray = numpy.fmax(stddev_mcray_primary, stddev_rosi_primary-delta_primary) + 0.5*delta_primary err_primary_upper = numpy.zeros_like(err_primary_rosi) err_primary_lower = numpy.zeros_like(err_primary_rosi) for i in range(len(err_primary_upper)): if means_rosi_primary[i] > means_primary[i]: # ROSI is upper bound err_primary_upper[i] = err_primary_rosi[i] err_primary_lower[i] = err_primary_mcray[i] else: # McRay is upper bound err_primary_upper[i] = err_primary_mcray[i] err_primary_lower[i] = err_primary_rosi[i] self.means_rosi_total = means_rosi_total self.means_rosi_primary = means_rosi_primary self.means_mcray_total = means_mcray_total self.means_mcray_primary = means_mcray_primary self.means_mc_total = means_total self.means_mc_primary = means_primary self.error_mc_total_upper = err_total_upper self.error_mc_total_lower = err_total_lower self.error_mc_primary_upper = err_primary_upper self.error_mc_primary_lower = err_primary_lower dataIO.close() self.ratios_mc_total = ratios(self.means_mc_total) self.ratios_mc_primary = ratios(self.means_mc_primary) # calculate maximum uncertainties of MC ratios: for v in range(1, len(self.ratios_mc_primary)+1): c, uncertainty_total_upper = divide_and_error( muA = self.means_mc_total[v-1], muB = self.means_mc_total[v], errA = self.error_mc_total_upper[v], errB = self.error_mc_total_upper[v-1] ) c, uncertainty_primary_upper = divide_and_error( muA = self.means_mc_primary[v-1], muB = self.means_mc_primary[v], errA = self.error_mc_primary_upper[v], errB = self.error_mc_primary_upper[v-1] ) c, uncertainty_total_lower = divide_and_error( muA = self.means_mc_total[v-1], muB = self.means_mc_total[v], errA = self.error_mc_total_lower[v], errB = self.error_mc_total_lower[v-1] ) c, uncertainty_primary_lower = divide_and_error( muA = self.means_mc_primary[v-1], muB = self.means_mc_primary[v], errA = self.error_mc_primary_lower[v], errB = self.error_mc_primary_lower[v-1] ) self.error_ratios_mc_total_upper.append(uncertainty_total_upper) self.error_ratios_mc_primary_upper.append(uncertainty_primary_upper) self.error_ratios_mc_total_lower.append(uncertainty_total_lower) self.error_ratios_mc_primary_lower.append(uncertainty_primary_lower)