Module ctsimu.evaluation.test2D_FB_2

Test 2D-FB-2: Laws of distance and radiation incidence

This test scenario checks if the intensity profile is correctly rendered under the assumption of an isotropic point source. The inverse square law and the law of radiation incidence must be obeyed:

I\sim \cos(\alpha)/r^2

The test compares the central horizontal row of pixels at index y=250 with the analytical intensity profile.

To run this test evaluation, you can simply pass the name of the test and the metadata file which describes the projection that you want to evaluate to the Toolbox:

from ctsimu.toolbox import Toolbox
Toolbox("2D-FB-2", "2D-FB-2_metadata.json")
Expand source code
# -*- coding: UTF-8 -*-
"""# Test 2D-FB-2: Laws of distance and radiation incidence

.. include:: ./test2D_FB_2.md
"""

from ..test import *
from ..helpers import *
from ..scenario import Scenario

class Test2D_FB_2(generalTest):
    """ CTSimU test 2D-FB-2: 1/r^2 law, cos(alpha) intensity on pixel. """
    def __init__(self, resultFileDirectory=".", name=None, rawOutput=False):
        generalTest.__init__(
            self,
            testName="2D-FB-2",
            name=name,
            nExpectedRuns=1,
            resultFileDirectory=resultFileDirectory,
            rawOutput=rawOutput)
        
        self.geometry = None
        self.analyticalIntensityProfileImage = None  # stores the image after preparation

        # Horizontal profile data:
        self.lineNr = 0
        self.profile_analytical = None
        self.profile_measured   = None

        # Maximum absolute grey value difference between analytical image and mesured image: 
        self.maxDiff = 0

        # RMS of GV differences:
        self.rmsGVDiff = 0


    def prepare(self):
        """ Preparations before the test will be run with the images from the pipeline. """
        if not isinstance(self.pipe, Pipeline):
            self.prepared = False
            raise Exception("Step must be part of a processing pipeline before it can prepare. Current pipeline: {}".format(self.pipe))

        if not self.prepared:
            self.jsonScenarioFile = "2D-FB-2_2021-03-24v06r00dp.json"

            if(self.jsonScenarioFile is not None):
                self.scenario = Scenario(json_dict=json_from_pkg(pkg_scenario(self.jsonScenarioFile)))
                self.geometry = self.scenario.current_geometry()
                self.geometry.update()
                self.analyticalIntensityProfileImage = self.geometry.create_detector_flat_field_analytical()

                # Raise normalized image to maximum grey value of 60000,
                # as demanded by test 2D-FB-2:
                self.analyticalIntensityProfileImage.renormalize(newMin=0, newMax=60000.0, currentMin=0)

                self.prepared = True
            else:
                raise Exception("Test 2D-FB-2: Please provide a JSON scenario description.")


    def run(self, image):
        self.prepare()
        self.currentRun += 1

        self.lineNr = int(round(self.geometry.brightest_spot_detector.y()))
        log("Getting horizontal profile for detector row {l}.".format(l=self.lineNr))

        # Horizontal profile of the analytical image along the central line:
        self.profile_analytical = self.analyticalIntensityProfileImage.horizontalProfile(self.lineNr)

        # Horizontal profile of the image to be evaluated:
        self.profile_measured = image.horizontalProfile(self.lineNr)

        return image


    def followUp(self):
        log("Writing evaluation results...")
        nPixels = len(self.profile_analytical)
        if len(self.profile_analytical) == len(self.profile_measured):
            csvText = "# x [px]\tAnalytical GV\tMeasured GV\tDifference\tRel. Deviation [%]\n"
            squaresSum = 0
            self.maxDiff = 0
            for i in range(nPixels):
                gv0 = self.profile_analytical[i]
                gv1 = self.profile_measured[i]
                delta = gv0 - gv1
                relativeDeviation = 0.0
                if gv0 != 0:
                    relativeDeviation = (100.0*delta)/gv0

                squaresSum += delta*delta

                if abs(delta) > self.maxDiff:
                    self.maxDiff = abs(delta)

                csvText += "{i}\t{gvAnalytical:.3f}\t{gvMeasured:.3f}\t{delta:.3f}\t{relDev:.3f}\n".format(i=i, gvAnalytical=gv0, gvMeasured=gv1, delta=delta, relDev=relativeDeviation)

            self.rmsGVDiff = math.sqrt(squaresSum / nPixels)

            # Write evaluation text file:
            header = "# Evaluation of Test {name}:\n# Max Absolute GV Difference: {maxAbsDiff:.5f}\n# RMS GV Difference:  {rms:.5f}\n# \n# Horizontal profile along y={line}:\n".format(name=self.name, maxAbsDiff=self.maxDiff, rms=self.rmsGVDiff, line=self.lineNr)
            csvText = header + csvText

            csvFileName = "{dir}/{name}_summary.txt".format(dir=self.resultFileDirectory, name=self.name)
            with open(csvFileName, 'w') as csvFile:
                csvFile.write(csvText)
                csvFile.close()


            # Write analytical image:
            if self.rawOutput:
                self.analyticalIntensityProfileImage.saveRAW("{dir}/{name}_analytical.raw".format(dir=self.resultFileDirectory, name=self.name), dataType="float32", addInfo=True)
            else: # TIFF
                self.analyticalIntensityProfileImage.save("{dir}/{name}_analytical.tif".format(dir=self.resultFileDirectory, name=self.name), dataType="float32")

            self.plotResults()

            log("Evaluation data for test {name} written to {dir}.".format(name=self.name, dir=self.resultFileDirectory))

        else:
            raise Exception("Profile width mismatch between analytical result ({}) and measured result ({}).".format(len(profile_analytical), len(profile_measured)))

    def plotResults(self):
        try:
            log("Plotting evaluation results...")
            import matplotlib
            import matplotlib.pyplot
            from matplotlib.ticker import (MultipleLocator, FormatStrFormatter, AutoMinorLocator)

            xValues = numpy.linspace(0, len(self.profile_analytical), len(self.profile_analytical), endpoint=False)
            absDev = self.profile_analytical - self.profile_measured
            relDev = 100.0*absDev / self.profile_analytical

            matplotlib.use("agg")

            fig, (axUpper, axLower) = matplotlib.pyplot.subplots(nrows=2, ncols=1, figsize=(9, 9))
            
            # Grey Value Profile:
            axUpper.plot(xValues, self.profile_analytical, linewidth=8.0, label="Analytical", color='#ffaa00')
            axUpper.plot(xValues, self.profile_measured,   linewidth=2.0, label="Measured", color='#1f77b4')
            axUpper.set_xlabel("Horizontal distance in px")
            axUpper.set_ylabel("Grey value")
            axUpper.set_title("Grey value line profile along y = {line} px".format(line=self.lineNr))
            axUpper.xaxis.set_major_locator(MultipleLocator(100))
            axUpper.xaxis.set_major_formatter(FormatStrFormatter('%d'))
            axUpper.xaxis.set_minor_locator(MultipleLocator(50))
            axUpper.grid(visible=True, which='major', axis='both', color='#d9d9d9', linestyle='dashed')
            axUpper.grid(visible=True, which='minor', axis='both', color='#e7e7e7', linestyle='dotted')
            axUpper.legend()

            # Absolute Deviation:
            line_absolute = axLower.plot(xValues, absDev, linewidth=1.25, label="Absolute", color='#ffaa00')
            axLower.set_xlabel("Horizontal distance in px")
            axLower.set_ylabel("Absolute deviation in grey values")
            axLower.set_title("Deviation = Analytical - Measured")
            axLower.xaxis.set_major_locator(MultipleLocator(100))
            axLower.xaxis.set_major_formatter(FormatStrFormatter('%d'))
            axLower.xaxis.set_minor_locator(MultipleLocator(50))
            axLower.grid(visible=True, which='major', axis='both', color='#d9d9d9', linestyle='dashed')
            axLower.grid(visible=True, which='minor', axis='both', color='#e7e7e7', linestyle='dotted')
            
            # Relative Deviation:
            axLower2 = axLower.twinx()
            axLower2.axhline(0, linestyle='-', linewidth=1.25, color='#000000')
            line_relative = axLower2.plot(xValues, relDev, linewidth=1.25, label="Relative", color='#1f77b4')
            axLower2.set_ylabel("Relative deviation in %")
            maxDev = max(abs(relDev.min()), abs(relDev.max()))
            axLower2.set_ylim([-1.5*maxDev, 1.5*maxDev])

            # Add all relevant lines to legend:
            lines_all = line_absolute + line_relative
            labels = [l.get_label() for l in lines_all]
            axLower.legend(lines_all, labels, loc=0)      

            fig.tight_layout(pad=5.0)

            plotFilename = "{dir}/{name}_results.png".format(dir=self.resultFileDirectory, name=self.name)
            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_FB_2 (resultFileDirectory='.', name=None, rawOutput=False)

CTSimU test 2D-FB-2: 1/r^2 law, cos(alpha) intensity on pixel.

Expand source code
class Test2D_FB_2(generalTest):
    """ CTSimU test 2D-FB-2: 1/r^2 law, cos(alpha) intensity on pixel. """
    def __init__(self, resultFileDirectory=".", name=None, rawOutput=False):
        generalTest.__init__(
            self,
            testName="2D-FB-2",
            name=name,
            nExpectedRuns=1,
            resultFileDirectory=resultFileDirectory,
            rawOutput=rawOutput)
        
        self.geometry = None
        self.analyticalIntensityProfileImage = None  # stores the image after preparation

        # Horizontal profile data:
        self.lineNr = 0
        self.profile_analytical = None
        self.profile_measured   = None

        # Maximum absolute grey value difference between analytical image and mesured image: 
        self.maxDiff = 0

        # RMS of GV differences:
        self.rmsGVDiff = 0


    def prepare(self):
        """ Preparations before the test will be run with the images from the pipeline. """
        if not isinstance(self.pipe, Pipeline):
            self.prepared = False
            raise Exception("Step must be part of a processing pipeline before it can prepare. Current pipeline: {}".format(self.pipe))

        if not self.prepared:
            self.jsonScenarioFile = "2D-FB-2_2021-03-24v06r00dp.json"

            if(self.jsonScenarioFile is not None):
                self.scenario = Scenario(json_dict=json_from_pkg(pkg_scenario(self.jsonScenarioFile)))
                self.geometry = self.scenario.current_geometry()
                self.geometry.update()
                self.analyticalIntensityProfileImage = self.geometry.create_detector_flat_field_analytical()

                # Raise normalized image to maximum grey value of 60000,
                # as demanded by test 2D-FB-2:
                self.analyticalIntensityProfileImage.renormalize(newMin=0, newMax=60000.0, currentMin=0)

                self.prepared = True
            else:
                raise Exception("Test 2D-FB-2: Please provide a JSON scenario description.")


    def run(self, image):
        self.prepare()
        self.currentRun += 1

        self.lineNr = int(round(self.geometry.brightest_spot_detector.y()))
        log("Getting horizontal profile for detector row {l}.".format(l=self.lineNr))

        # Horizontal profile of the analytical image along the central line:
        self.profile_analytical = self.analyticalIntensityProfileImage.horizontalProfile(self.lineNr)

        # Horizontal profile of the image to be evaluated:
        self.profile_measured = image.horizontalProfile(self.lineNr)

        return image


    def followUp(self):
        log("Writing evaluation results...")
        nPixels = len(self.profile_analytical)
        if len(self.profile_analytical) == len(self.profile_measured):
            csvText = "# x [px]\tAnalytical GV\tMeasured GV\tDifference\tRel. Deviation [%]\n"
            squaresSum = 0
            self.maxDiff = 0
            for i in range(nPixels):
                gv0 = self.profile_analytical[i]
                gv1 = self.profile_measured[i]
                delta = gv0 - gv1
                relativeDeviation = 0.0
                if gv0 != 0:
                    relativeDeviation = (100.0*delta)/gv0

                squaresSum += delta*delta

                if abs(delta) > self.maxDiff:
                    self.maxDiff = abs(delta)

                csvText += "{i}\t{gvAnalytical:.3f}\t{gvMeasured:.3f}\t{delta:.3f}\t{relDev:.3f}\n".format(i=i, gvAnalytical=gv0, gvMeasured=gv1, delta=delta, relDev=relativeDeviation)

            self.rmsGVDiff = math.sqrt(squaresSum / nPixels)

            # Write evaluation text file:
            header = "# Evaluation of Test {name}:\n# Max Absolute GV Difference: {maxAbsDiff:.5f}\n# RMS GV Difference:  {rms:.5f}\n# \n# Horizontal profile along y={line}:\n".format(name=self.name, maxAbsDiff=self.maxDiff, rms=self.rmsGVDiff, line=self.lineNr)
            csvText = header + csvText

            csvFileName = "{dir}/{name}_summary.txt".format(dir=self.resultFileDirectory, name=self.name)
            with open(csvFileName, 'w') as csvFile:
                csvFile.write(csvText)
                csvFile.close()


            # Write analytical image:
            if self.rawOutput:
                self.analyticalIntensityProfileImage.saveRAW("{dir}/{name}_analytical.raw".format(dir=self.resultFileDirectory, name=self.name), dataType="float32", addInfo=True)
            else: # TIFF
                self.analyticalIntensityProfileImage.save("{dir}/{name}_analytical.tif".format(dir=self.resultFileDirectory, name=self.name), dataType="float32")

            self.plotResults()

            log("Evaluation data for test {name} written to {dir}.".format(name=self.name, dir=self.resultFileDirectory))

        else:
            raise Exception("Profile width mismatch between analytical result ({}) and measured result ({}).".format(len(profile_analytical), len(profile_measured)))

    def plotResults(self):
        try:
            log("Plotting evaluation results...")
            import matplotlib
            import matplotlib.pyplot
            from matplotlib.ticker import (MultipleLocator, FormatStrFormatter, AutoMinorLocator)

            xValues = numpy.linspace(0, len(self.profile_analytical), len(self.profile_analytical), endpoint=False)
            absDev = self.profile_analytical - self.profile_measured
            relDev = 100.0*absDev / self.profile_analytical

            matplotlib.use("agg")

            fig, (axUpper, axLower) = matplotlib.pyplot.subplots(nrows=2, ncols=1, figsize=(9, 9))
            
            # Grey Value Profile:
            axUpper.plot(xValues, self.profile_analytical, linewidth=8.0, label="Analytical", color='#ffaa00')
            axUpper.plot(xValues, self.profile_measured,   linewidth=2.0, label="Measured", color='#1f77b4')
            axUpper.set_xlabel("Horizontal distance in px")
            axUpper.set_ylabel("Grey value")
            axUpper.set_title("Grey value line profile along y = {line} px".format(line=self.lineNr))
            axUpper.xaxis.set_major_locator(MultipleLocator(100))
            axUpper.xaxis.set_major_formatter(FormatStrFormatter('%d'))
            axUpper.xaxis.set_minor_locator(MultipleLocator(50))
            axUpper.grid(visible=True, which='major', axis='both', color='#d9d9d9', linestyle='dashed')
            axUpper.grid(visible=True, which='minor', axis='both', color='#e7e7e7', linestyle='dotted')
            axUpper.legend()

            # Absolute Deviation:
            line_absolute = axLower.plot(xValues, absDev, linewidth=1.25, label="Absolute", color='#ffaa00')
            axLower.set_xlabel("Horizontal distance in px")
            axLower.set_ylabel("Absolute deviation in grey values")
            axLower.set_title("Deviation = Analytical - Measured")
            axLower.xaxis.set_major_locator(MultipleLocator(100))
            axLower.xaxis.set_major_formatter(FormatStrFormatter('%d'))
            axLower.xaxis.set_minor_locator(MultipleLocator(50))
            axLower.grid(visible=True, which='major', axis='both', color='#d9d9d9', linestyle='dashed')
            axLower.grid(visible=True, which='minor', axis='both', color='#e7e7e7', linestyle='dotted')
            
            # Relative Deviation:
            axLower2 = axLower.twinx()
            axLower2.axhline(0, linestyle='-', linewidth=1.25, color='#000000')
            line_relative = axLower2.plot(xValues, relDev, linewidth=1.25, label="Relative", color='#1f77b4')
            axLower2.set_ylabel("Relative deviation in %")
            maxDev = max(abs(relDev.min()), abs(relDev.max()))
            axLower2.set_ylim([-1.5*maxDev, 1.5*maxDev])

            # Add all relevant lines to legend:
            lines_all = line_absolute + line_relative
            labels = [l.get_label() for l in lines_all]
            axLower.legend(lines_all, labels, loc=0)      

            fig.tight_layout(pad=5.0)

            plotFilename = "{dir}/{name}_results.png".format(dir=self.resultFileDirectory, name=self.name)
            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. """
    if not isinstance(self.pipe, Pipeline):
        self.prepared = False
        raise Exception("Step must be part of a processing pipeline before it can prepare. Current pipeline: {}".format(self.pipe))

    if not self.prepared:
        self.jsonScenarioFile = "2D-FB-2_2021-03-24v06r00dp.json"

        if(self.jsonScenarioFile is not None):
            self.scenario = Scenario(json_dict=json_from_pkg(pkg_scenario(self.jsonScenarioFile)))
            self.geometry = self.scenario.current_geometry()
            self.geometry.update()
            self.analyticalIntensityProfileImage = self.geometry.create_detector_flat_field_analytical()

            # Raise normalized image to maximum grey value of 60000,
            # as demanded by test 2D-FB-2:
            self.analyticalIntensityProfileImage.renormalize(newMin=0, newMax=60000.0, currentMin=0)

            self.prepared = True
        else:
            raise Exception("Test 2D-FB-2: Please provide a JSON scenario description.")

Inherited members