Module ctsimu.scenario.detector

A CTSimU detector: position, orientation, size, and other parameters.

Expand source code
# -*- coding: UTF-8 -*-
"""
A CTSimU detector: position, orientation, size, and other parameters.
"""

from ..helpers import *
from ..geometry import *
from .part import Part
from .parameter import Parameter
from .group import Group, Array

class Detector(Part):
        """CTSimU detector."""
        def __init__(self, _root=None):
                Part.__init__(self, name="detector", _root=_root)

                # Detector parameters
                self.set(key="model",            value=None,    native_unit="string", simple=True)
                self.set(key="manufacturer",     value=None,    native_unit="string", simple=True)
                self.set(key="type",             value="ideal", native_unit="string", simple=True, valid_values=[None, "ideal", "real"])
                self.set(key="columns",          value=1000,    native_unit="px")
                self.set(key="rows",             value=1000,    native_unit="px")

                self.new_subgroup("pixel_pitch")
                self.pixel_pitch.set(key="u",    value=0.1,     native_unit="mm")
                self.pixel_pitch.set(key="v",    value=0.1,     native_unit="mm")

                self.set(key="bit_depth",        value=16,      native_unit=None)
                self.set(key="integration_time", value=1.0,     native_unit="s")
                self.set(key="dead_time",        value=1.0,     native_unit="s")
                self.set(key="image_lag",        value=0.0,     native_unit=None)
                self.set(key="gain",             value=None,    native_unit=None)

                # Properties for gray value reproduction:
                self.new_subgroup("gray_value")
                self.gray_value.add_alternative_name("grey_value")
                self.gray_value.set(key="imin",   value=0,     native_unit=None)
                self.gray_value.set(key="imax",   value=60000, native_unit=None)
                self.gray_value.set(key="factor", value=None,  native_unit="1/J")
                self.gray_value.set(key="offset", value=None,  native_unit=None)
                self.gray_value.set(key="intensity_characteristics_file",  value=None, native_unit="string")
                self.gray_value.set(key="efficiency_characteristics_file", value=None, native_unit="string")

                # Noise:
                self.new_subgroup("noise")
                self.noise.set(key="snr_at_imax",                value=None, native_unit=None)
                self.noise.set(key="noise_characteristics_file", value=None, native_unit="string")

                # Unsharpness:
                self.new_subgroup("unsharpness")
                self.unsharpness.add_alternative_name("sharpness")
                self.unsharpness.set(key="basic_spatial_resolution", value=None, native_unit="mm")
                self.unsharpness.set(key="mtf", value=None, native_unit="string")

                # Bad pixel map:
                self.new_subgroup("bad_pixel_map")
                self.bad_pixel_map.set(key="file",       value=None, native_unit="string")
                # dim_x and dim_y not necessary. Must match detector columns/rows.
                #self.bad_pixel_map.set(key="dim_x",      value=None, simple=True)
                #self.bad_pixel_map.set(key="dim_y",      value=None, simple=True)
                self.bad_pixel_map.set(key="type",       value="int16",  native_unit="string", simple=True)
                self.bad_pixel_map.set(key="endian",     value="little", native_unit="string", simple=True)
                self.bad_pixel_map.set(key="headersize", value=0, simple=True)

                # Scintillator
                self.new_subgroup("scintillator")
                self.scintillator.set(key="material_id", value=None, native_unit="string", simple=True)
                self.scintillator.set(key="thickness",   value=None, native_unit="mm")

                # Window
                self.new_subgroup("window")
                self.window.new_subgroup("front", array=True)
                self.window.front.set(key="material_id", value=None, native_unit="string", simple=True)
                self.window.front.set(key="thickness",   value=None, native_unit="mm")
                self.window.new_subgroup("rear", array=True)
                self.window.rear.set(key="material_id", value=None, native_unit="string", simple=True)
                self.window.rear.set(key="thickness",   value=None, native_unit="mm")

                # Filters
                self.new_subgroup("filters")
                self.filters.new_subgroup("front", array=True)
                self.filters.front.set(key="material_id", value=None, native_unit="string", simple=True)
                self.filters.front.set(key="thickness",   value=None, native_unit="mm")
                self.filters.new_subgroup("rear", array=True)
                self.filters.rear.set(key="material_id", value=None, native_unit="string", simple=True)
                self.filters.rear.set(key="thickness",   value=None, native_unit="mm")

        def check(self):
                # Check if the detector type is valid:
                if not (self.get("type") in valid_detector_types):
                        raise ValueError(f"Not a valid detector type: \'{self.get('type')}\'. Should be any of {valid_detector_types}.")
                        return False

                return True

        def physical_width(self) -> float:
                """Get detector's physical width in mm.

                Returns
                -------
                physical_width : float
                """
                return float(self.get("pitch_u") * int(self.get("columns")))

        def physical_height(self) -> float:
                """Get detector's physical height in mm.

                Returns
                -------
                physical_height : float
                """
                return float(self.get("pitch_v") * int(self.get("rows")))

        def pixel_area(self) -> float:
                """Get area of a single pixel in mm².

                Returns
                -------
                area : float
                        Pixel area in mm².
                """
                return self.get("pitch_u") * self.get("pitch_v")

        def pixel_area_m2(self) -> float:
                """Get area of a single pixel in m².

                Returns
                -------
                area : float
                        Pixel area in m².
                """
                return self.pixel_area() * 1.0e-6

        def max_gray_value(self) -> int:
                """The maximum gray value that can be stored using
                the detector bit depth, assuming gray values are stored as
                unsigned integers.

                Returns
                -------
                max_gray_value : int
                        2^bitdepth - 1
                """
                return (2**self.get("bit_depth")) - 1

        def set_from_json(self, json_scenario:dict):
                """Import the detector definition and geometry from the JSON object.
                The JSON object should contain the complete content
                of the scenario definition file
                (at least the geometry and detector sections).

                Parameters
                ----------
                json_scenario : dict
                        A complete CTSimU scenario object, as imported from a JSON structure.
                """
                self.reset()

                # Extract the detector's geometry:
                geo = json_extract(json_scenario, ["geometry", "detector"])
                self.set_geometry(json_geometry_object=geo, proper_cs="local")

                # Detector properties:
                detprops = json_extract(json_scenario, ["detector"])
                Group.set_from_json(self, detprops)

Classes

class Detector

CTSimU detector.

The part's name can be set upon initialization.

Parameters

name : str
Name for the part.
Expand source code
class Detector(Part):
        """CTSimU detector."""
        def __init__(self, _root=None):
                Part.__init__(self, name="detector", _root=_root)

                # Detector parameters
                self.set(key="model",            value=None,    native_unit="string", simple=True)
                self.set(key="manufacturer",     value=None,    native_unit="string", simple=True)
                self.set(key="type",             value="ideal", native_unit="string", simple=True, valid_values=[None, "ideal", "real"])
                self.set(key="columns",          value=1000,    native_unit="px")
                self.set(key="rows",             value=1000,    native_unit="px")

                self.new_subgroup("pixel_pitch")
                self.pixel_pitch.set(key="u",    value=0.1,     native_unit="mm")
                self.pixel_pitch.set(key="v",    value=0.1,     native_unit="mm")

                self.set(key="bit_depth",        value=16,      native_unit=None)
                self.set(key="integration_time", value=1.0,     native_unit="s")
                self.set(key="dead_time",        value=1.0,     native_unit="s")
                self.set(key="image_lag",        value=0.0,     native_unit=None)
                self.set(key="gain",             value=None,    native_unit=None)

                # Properties for gray value reproduction:
                self.new_subgroup("gray_value")
                self.gray_value.add_alternative_name("grey_value")
                self.gray_value.set(key="imin",   value=0,     native_unit=None)
                self.gray_value.set(key="imax",   value=60000, native_unit=None)
                self.gray_value.set(key="factor", value=None,  native_unit="1/J")
                self.gray_value.set(key="offset", value=None,  native_unit=None)
                self.gray_value.set(key="intensity_characteristics_file",  value=None, native_unit="string")
                self.gray_value.set(key="efficiency_characteristics_file", value=None, native_unit="string")

                # Noise:
                self.new_subgroup("noise")
                self.noise.set(key="snr_at_imax",                value=None, native_unit=None)
                self.noise.set(key="noise_characteristics_file", value=None, native_unit="string")

                # Unsharpness:
                self.new_subgroup("unsharpness")
                self.unsharpness.add_alternative_name("sharpness")
                self.unsharpness.set(key="basic_spatial_resolution", value=None, native_unit="mm")
                self.unsharpness.set(key="mtf", value=None, native_unit="string")

                # Bad pixel map:
                self.new_subgroup("bad_pixel_map")
                self.bad_pixel_map.set(key="file",       value=None, native_unit="string")
                # dim_x and dim_y not necessary. Must match detector columns/rows.
                #self.bad_pixel_map.set(key="dim_x",      value=None, simple=True)
                #self.bad_pixel_map.set(key="dim_y",      value=None, simple=True)
                self.bad_pixel_map.set(key="type",       value="int16",  native_unit="string", simple=True)
                self.bad_pixel_map.set(key="endian",     value="little", native_unit="string", simple=True)
                self.bad_pixel_map.set(key="headersize", value=0, simple=True)

                # Scintillator
                self.new_subgroup("scintillator")
                self.scintillator.set(key="material_id", value=None, native_unit="string", simple=True)
                self.scintillator.set(key="thickness",   value=None, native_unit="mm")

                # Window
                self.new_subgroup("window")
                self.window.new_subgroup("front", array=True)
                self.window.front.set(key="material_id", value=None, native_unit="string", simple=True)
                self.window.front.set(key="thickness",   value=None, native_unit="mm")
                self.window.new_subgroup("rear", array=True)
                self.window.rear.set(key="material_id", value=None, native_unit="string", simple=True)
                self.window.rear.set(key="thickness",   value=None, native_unit="mm")

                # Filters
                self.new_subgroup("filters")
                self.filters.new_subgroup("front", array=True)
                self.filters.front.set(key="material_id", value=None, native_unit="string", simple=True)
                self.filters.front.set(key="thickness",   value=None, native_unit="mm")
                self.filters.new_subgroup("rear", array=True)
                self.filters.rear.set(key="material_id", value=None, native_unit="string", simple=True)
                self.filters.rear.set(key="thickness",   value=None, native_unit="mm")

        def check(self):
                # Check if the detector type is valid:
                if not (self.get("type") in valid_detector_types):
                        raise ValueError(f"Not a valid detector type: \'{self.get('type')}\'. Should be any of {valid_detector_types}.")
                        return False

                return True

        def physical_width(self) -> float:
                """Get detector's physical width in mm.

                Returns
                -------
                physical_width : float
                """
                return float(self.get("pitch_u") * int(self.get("columns")))

        def physical_height(self) -> float:
                """Get detector's physical height in mm.

                Returns
                -------
                physical_height : float
                """
                return float(self.get("pitch_v") * int(self.get("rows")))

        def pixel_area(self) -> float:
                """Get area of a single pixel in mm².

                Returns
                -------
                area : float
                        Pixel area in mm².
                """
                return self.get("pitch_u") * self.get("pitch_v")

        def pixel_area_m2(self) -> float:
                """Get area of a single pixel in m².

                Returns
                -------
                area : float
                        Pixel area in m².
                """
                return self.pixel_area() * 1.0e-6

        def max_gray_value(self) -> int:
                """The maximum gray value that can be stored using
                the detector bit depth, assuming gray values are stored as
                unsigned integers.

                Returns
                -------
                max_gray_value : int
                        2^bitdepth - 1
                """
                return (2**self.get("bit_depth")) - 1

        def set_from_json(self, json_scenario:dict):
                """Import the detector definition and geometry from the JSON object.
                The JSON object should contain the complete content
                of the scenario definition file
                (at least the geometry and detector sections).

                Parameters
                ----------
                json_scenario : dict
                        A complete CTSimU scenario object, as imported from a JSON structure.
                """
                self.reset()

                # Extract the detector's geometry:
                geo = json_extract(json_scenario, ["geometry", "detector"])
                self.set_geometry(json_geometry_object=geo, proper_cs="local")

                # Detector properties:
                detprops = json_extract(json_scenario, ["detector"])
                Group.set_from_json(self, detprops)

Ancestors

Methods

def check(self)
Expand source code
def check(self):
        # Check if the detector type is valid:
        if not (self.get("type") in valid_detector_types):
                raise ValueError(f"Not a valid detector type: \'{self.get('type')}\'. Should be any of {valid_detector_types}.")
                return False

        return True
def max_gray_value(self) ‑> int

The maximum gray value that can be stored using the detector bit depth, assuming gray values are stored as unsigned integers.

Returns

max_gray_value : int
2^bitdepth - 1
Expand source code
def max_gray_value(self) -> int:
        """The maximum gray value that can be stored using
        the detector bit depth, assuming gray values are stored as
        unsigned integers.

        Returns
        -------
        max_gray_value : int
                2^bitdepth - 1
        """
        return (2**self.get("bit_depth")) - 1
def physical_height(self) ‑> float

Get detector's physical height in mm.

Returns

physical_height : float
 
Expand source code
def physical_height(self) -> float:
        """Get detector's physical height in mm.

        Returns
        -------
        physical_height : float
        """
        return float(self.get("pitch_v") * int(self.get("rows")))
def physical_width(self) ‑> float

Get detector's physical width in mm.

Returns

physical_width : float
 
Expand source code
def physical_width(self) -> float:
        """Get detector's physical width in mm.

        Returns
        -------
        physical_width : float
        """
        return float(self.get("pitch_u") * int(self.get("columns")))
def pixel_area(self) ‑> float

Get area of a single pixel in mm².

Returns

area : float
Pixel area in mm².
Expand source code
def pixel_area(self) -> float:
        """Get area of a single pixel in mm².

        Returns
        -------
        area : float
                Pixel area in mm².
        """
        return self.get("pitch_u") * self.get("pitch_v")
def pixel_area_m2(self) ‑> float

Get area of a single pixel in m².

Returns

area : float
Pixel area in m².
Expand source code
def pixel_area_m2(self) -> float:
        """Get area of a single pixel in m².

        Returns
        -------
        area : float
                Pixel area in m².
        """
        return self.pixel_area() * 1.0e-6
def set_from_json(self, json_scenario: dict)

Import the detector definition and geometry from the JSON object. The JSON object should contain the complete content of the scenario definition file (at least the geometry and detector sections).

Parameters

json_scenario : dict
A complete CTSimU scenario object, as imported from a JSON structure.
Expand source code
def set_from_json(self, json_scenario:dict):
        """Import the detector definition and geometry from the JSON object.
        The JSON object should contain the complete content
        of the scenario definition file
        (at least the geometry and detector sections).

        Parameters
        ----------
        json_scenario : dict
                A complete CTSimU scenario object, as imported from a JSON structure.
        """
        self.reset()

        # Extract the detector's geometry:
        geo = json_extract(json_scenario, ["geometry", "detector"])
        self.set_geometry(json_geometry_object=geo, proper_cs="local")

        # Detector properties:
        detprops = json_extract(json_scenario, ["detector"])
        Group.set_from_json(self, detprops)

Inherited members