From 910227cf876bd5997508a2895584a2c065e80782 Mon Sep 17 00:00:00 2001 From: luigimasturzo <55702507+luigimasturzo@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:39:48 +0200 Subject: [PATCH 1/8] Added ElectronFlash simulation files --- .../linacs/ElectronFlash/EF_function.py | 607 ++++++++++++++ .../ElectronFlash/simulation_standard.py | 132 +++ .../ElectronFlash/utils/GateMaterials.db | 790 ++++++++++++++++++ .../ElectronFlash/utils/spectra/9Mev.txt | 80 ++ .../ElectronFlash/utils/spectra/9Mev_old.txt | 80 ++ 5 files changed, 1689 insertions(+) create mode 100644 opengate/contrib/linacs/ElectronFlash/EF_function.py create mode 100644 opengate/contrib/linacs/ElectronFlash/simulation_standard.py create mode 100644 opengate/contrib/linacs/ElectronFlash/utils/GateMaterials.db create mode 100644 opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev.txt create mode 100644 opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev_old.txt diff --git a/opengate/contrib/linacs/ElectronFlash/EF_function.py b/opengate/contrib/linacs/ElectronFlash/EF_function.py new file mode 100644 index 000000000..349478443 --- /dev/null +++ b/opengate/contrib/linacs/ElectronFlash/EF_function.py @@ -0,0 +1,607 @@ +import opengate as gate +import numpy as np +import os, sys, logging +from scipy.spatial.transform import Rotation as R +import SimpleITK as sitk + + + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +cm = gate.g4_units.cm +mm = gate.g4_units.mm +MeV = gate.g4_units.MeV +deg = gate.g4_units.deg + + +material_colors = { + "Titanium" : [0.2, 0.2, 0.2, 0.6], + "Tecapeek" : [1.0, 0.6, 0.2, 0.6], + "Air" : [1.0, 0.6, 0.2, 0.6], + "AluminiumEGS": [0.8, 0.8, 0.8, 0.6], + "Tungsten" : [0.8, 0.8, 0.8, 0.6], + "PMMA" : [0.0, 0.6, 0.0, 0.5], + "Water" : [0.0, 0.0, 0.5, 0.5], + } + + +def add_tubs(sim, name, mother, rmin, rmax, height, material, translation, material_colors = None): + """ + This function creates and adds a cylindrical (Tubs) volume to the geometry. + + Args: + sim: The simulation object where the volume will be added. + name (str): Name of the volume. + mother (str): The name of the mother volume. + rmin (float): Inner radius of the tube in millimeters. + rmax (float): Outer radius of the tube in millimeters. + height (float): Full height of the tube in millimeters. + material (str): Material assigned to the volume. + translation (tuple): A 3-element tuple representing the (x, y, z) translation of the volume. + material_colors (dict): A dictionary mapping material names to RGB color strings or tuples. + + Returns: + The created volume object. + + """ + vol = sim.add_volume("Tubs", name) + vol.mother = mother + vol.rmin = rmin * mm + vol.rmax = rmax * mm + vol.dz = height * mm / 2 + vol.material = material + vol.translation = translation + if material_colors and material in material_colors: + vol.color = material_colors[material] + return vol + + +def add_cons(sim, name, mother, rmin1, rmax1, rmin2, rmax2, height, material, translation, material_colors = None): + """ + This function creates and adds a conical (Cons) volume to the geometry. + + Args: + sim: The simulation object where the volume will be added. + name (str): Name of the volume. + mother (str): The name of the mother volume. + rmin1 (float): Inner radius at the -Z end in millimeters. + rmax1 (float): Outer radius at the -Z end in millimeters. + rmin2 (float): Inner radius at the +Z end in millimeters. + rmax2 (float): Outer radius at the +Z end in millimeters. + height (float): Full height of the cone in millimeters. + material (str): Material assigned to the volume. + translation (tuple): A 3-element tuple representing the (x, y, z) translation of the volume. + material_colors (dict, optional): A dictionary mapping material names to RGB color strings or tuples. + + Returns: + The created volume object. + """ + vol = sim.add_volume("Cons", name) + vol.mother = mother + vol.rmin1 = rmin1 * mm + vol.rmax1 = rmax1 * mm + vol.rmin2 = rmin2 * mm + vol.rmax2 = rmax2 * mm + vol.dz = height / 2 + vol.material = material + vol.translation = translation + vol.sphi = 0 * deg + vol.dphi = 360 * deg + if material_colors and material in material_colors: + vol.color = material_colors[material] + return vol + +def add_box(sim, name, mother, lx, ly, lz, material, translation, material_colors = None): + """ + This function creates and adds a rectangular (Box) volume to the geometry. + + Args: + sim: The simulation object where the volume will be added. + name (str): Name of the volume. + mother (str): The name of the mother volume. + lx (float): Length of the box along the X-axis in millimeters. + ly (float): Length of the box along the Y-axis in millimeters. + lz (float): Length of the box along the Z-axis in millimeters. + material (str): Material assigned to the volume. + translation (tuple): A 3-element tuple representing the (x, y, z) translation of the volume. + material_colors (dict, optional): A dictionary mapping material names to RGB color strings or tuples. + + Returns: + The created volume object. + """ + vol = sim.add_volume("Box", name) + vol.mother = mother + vol.size = [lx, ly, lz] + vol.material = material + vol.translation = translation + if material_colors and material in material_colors: + vol.color = material_colors[material] + return vol + + +def get_object_zdimension(components, center_z): + """ + Computes the Z-dimension range (start and end Z coordinates) of the whole structure (made up of components). + + Args: + components (list): A list of component tuples. + center_z (float): Global Z-axis offset to apply to all components. + + Returns: + tuple: (start_z, end_z) in millimeters, representing the total Z-extent. + """ + get_z_bounds = lambda comp: (comp[-1][2] - comp[-3] / 2 + center_z, comp[-1][2] + comp[-3] / 2+center_z) + + start_z, _ = get_z_bounds(components[0]) + _, end_z = get_z_bounds(components[-1]) + return start_z, end_z + +def build_ElectronFlash(sim,center_x=0, center_y=0, center_z=0, material_colors=None): + """ + This function builds the full ElectronFlash linear accelerator (linac) geometry, starting from the titanium window up to the beginning of the BLD. + + Args: + sim: The simulation object where volumes will be added. + center_x (float): Offset in millimeters along the X-axis for the titanium window. + center_y (float): Offset in millimeters along the Y-axis for the titanium window. + center_z (float): Offset in millimeters along the Z-axis for the titanium window. + material_colors (dict, optional): A dictionary mapping material names to RGB color strings or tuples. + + Returns: + float: The Z-coordinate (in mm) of the end of the linac structure, useful when other components has to be added. + """ + if material_colors is None: + material_colors = {} + + def offset(mother, pos): + if mother == "world": + return [ + pos[0] + center_x, + pos[1] + center_y, + pos[2] + center_z + ] + return pos + + logger.info(f"Building ElectronFlash linac structure with center offset: X={center_x} mm, Y={center_y} mm, Z={center_z} mm") + + components = [ + # (func, name, mother, args..., material, translation) + (add_tubs,sim, "tit_window", "world", 0, 18.2, 0.055, "Titanium", [0, 0, 0.0275*mm]), + (add_tubs,sim, "cyl1", "world", 0, 34.6, 12.8, "Tecapeek", [0, 0, 6.455*mm]), + (add_cons,sim, "hollow_cone", "cyl1", 0, 7, 0, 19, 12.8, "Air", [0, 0, 0]), + (add_tubs,sim, "st_0", "world", 15, 34.6, 5, "Tecapeek", [0, 0, 15.355*mm]), + (add_tubs,sim, "st_1", "world", 15, 22, 14, "Tecapeek", [0, 0, 24.855*mm]), + (add_tubs,sim, "st_2", "world", 20, 24, 132, "Tecapeek", [0, 0, 97.855*mm]), + (add_tubs,sim, "st_3", "world", 31, 37, 35, "Tecapeek", [0, 0, 156.755*mm]), + (add_tubs,sim, "st_4", "world", 22.5, 37, 73.5, "Tecapeek", [0, 0, 211.005*mm]), + (add_tubs,sim, "st_5", "world", 37, 130.8, 20, "AluminiumEGS", [0, 0, 211.005*mm]), + (add_tubs,sim, "cyl6", "world", 0, 48, 69, "Tecapeek", [0, 0, 282.255*mm]), + (add_cons,sim, "hollow_cone1", "cyl6", 0, 22.5, 0, 31.5, 69, "Air", [0, 0, 0]), + (add_tubs,sim, "st_6", "world", 48, 72, 16, "Tecapeek", [0, 0, 255.755*mm]), + (add_tubs,sim, "st_7", "world", 48, 72, 29, "Tecapeek", [0, 0, 302.255*mm]), + + ] + + for comp in components: + func = comp[0] + sim = comp[1] + name = comp[2] + mother = comp[3] + *args, material, translation = comp[4:] + + func( + sim, + name, + mother, + *args, + material, + offset(mother, translation), + material_colors=material_colors + ) + start_z, end_z = get_object_zdimension(components, center_z) + + logger.info(f"Linac structure range: start at {start_z:.3f} mm, end at {end_z:.3f} mm") + return end_z + +def build_passive_collimation(sim, string_name, center_x=0, center_y=0, center_z=0, material_colors=None): + """ + Builds a passive collimation structure. + + Args: + sim: The simulation object where volumes are added. + string_name (str): Identifier for the collimator type. Allowed values are: + - 'app100' + - 'app40' + - 'app70' + - 'shaper40' + - 'mb_40_holes_11' + - 'mb_40_slit_11' + center_x (float, optional): X-axis offset (in mm) applied to all components. Defaults to 0. + center_y (float, optional): Y-axis offset (in mm) applied to all components. Defaults to 0. + center_z (float, optional): Z-axis offset (in mm) applied to all components. Defaults to 0. + material_colors (dict, optional): Dictionary mapping material names to colors. + + Raises: + ValueError: If `string_name` is not one of the allowed applicator types. + + Returns: + float: The Z-coordinate (in mm) of the end position of the constructed collimator structure. + """ + if material_colors is None: + material_colors = {} + + allowed_names = ['app100', 'app40', 'app70', 'shaper40', 'mb_40_holes_11', 'mb_40_slit_11'] + if string_name not in allowed_names: + raise ValueError(f"[ERROR] Unsupported applicator type: '{string_name}'. Allowed applicators are: {allowed_names}") + + def offset(mother, pos): + if mother == "world": + return [ + pos[0] + center_x, + pos[1] + center_y, + pos[2] + center_z + ] + return pos + + if string_name == 'app100': + components = [ + (add_tubs, sim, "cyl7", "world", 0, 72, 42.8, "PMMA", [0, 0, 21.4]), + (add_tubs, sim, "hollow_cyl", "cyl7", 0, 31.5, 10, "Air", [0, 0, -16.4*mm]), + (add_cons, sim, "hollow_cone2","cyl7", 0, 31.5, 0, 50, 32.8, "Air", [0, 0, 5*mm]), + (add_tubs, sim, "st_8", "world", 55, 72, 14.8, "PMMA", [0, 0, 50.2*mm]), + (add_tubs, sim, "app100", "world", 50, 55, 742, "PMMA", [0, 0, 413.8*mm]), + ] + elif string_name == 'app40': + components = [ + (add_tubs, sim, "cyl7", "world", 0, 72, 42.8, "PMMA", [0, 0, 21.4]), + (add_cons, sim, "hollow_cone2","cyl7", 0, 31.5, 0, 20, 42.8, "Air", [0, 0, 0]), + (add_tubs, sim, "st_8", "world", 25, 72, 10, "PMMA", [0, 0, 47.8*mm]), + (add_tubs, sim, "app40", "world", 20, 25, 357, "PMMA", [0, 0, 231.3*mm]), + ] + elif string_name == 'shaper40': + components = [ + (add_tubs, sim, "cyl_s", "world", 25.2, 26, 50, "PMMA", [0, 0, -28]), + (add_tubs, sim, "cyl_p", "world", 25.2, 75, 5, "PMMA", [0, 0, -0.5]), + ] + leaf_defs = [ + ("leaf1", [12.5, 0, 3.5]), + ("leaf2", [-12.5, 0, 3.5]), + ("leaf3", [0, 12.5, 8.5]), + ("leaf4", [0, -12.5, 8.5]), + ] + leaf_components = [ + (add_box, sim, name, "world", + 25 if name in ['leaf1', 'leaf2'] else 50, + 50 if name in ['leaf1', 'leaf2'] else 25, + 3, "Tungsten", pos) + for name, pos in leaf_defs + ] + final_components = components + leaf_components + elif string_name == 'mb_40_holes_11': + positions = [[x, y, 0] for x in [-4, -2, 0, 2, 4] for y in [-4, -2, 0, 2, 4]] + + components = [ + (add_tubs, sim, "cyl_mb1", "world", 25.2, 40, 25, "Tecapeek", [0, 0, -12.5]), #338.155 + (add_box , sim, "slab1", "world", 5 , 50, 2.5, "Tecapeek", [22.5, 0, 3.75]), + (add_box , sim, "slab2", "world", 5 , 50, 2.5, "Tecapeek", [-22.5, 0, 3.75]), + (add_box , sim, "slab3", "world", 40 , 5, 2.5, "Tecapeek", [0, -22.5, 3.75]), + ] + elif string_name == 'mb_40_slit_11': + positions = [[x, 0, 0] for x in [-4, -2, 0, 2, 4]] + + components = [ + (add_tubs, sim, "cyl_mb1", "world", 25.2, 40, 25, "Tecapeek", [0, 0, -12.5]), #338.155 + #(add_box , sim, "plate1", "world", 50 , 50, 2.5, "Tungsten", [0, 0, 1.25]), + #(add_box , sim, "plate2", "world", 40 , 40, 2.5, "Tungsten", [0, 0, 3.75]), + (add_box , sim, "slab1", "world", 5 , 50, 2.5, "Tecapeek", [22.5, 0, 3.75]), + (add_box , sim, "slab2", "world", 5 , 50, 2.5, "Tecapeek", [-22.5, 0, 3.75]), + (add_box , sim, "slab3", "world", 40 , 5, 2.5, "Tecapeek", [0, -22.5, 3.75]), + ] + + for comp in components: + func = comp[0] + sim = comp[1] + name = comp[2] + mother = comp[3] + *args, material, translation = comp[4:] + + func( + sim, + name, + mother, + *args, + material, + offset(mother, translation), + material_colors=material_colors + ) + + if string_name == 'shaper40': + leaf_objects = [] + for name, pos in leaf_defs: + obj = add_box(sim, name, "world", 25 if "leaf1" in name or "leaf2" in name else 50, + 50 if "leaf1" in name or "leaf2" in name else 25, + 3, "Tungsten", offset("world", pos), material_colors=material_colors) + leaf_objects.append(obj) + + if string_name == 'mb_40_holes_11': + plate_1 = add_box( sim, "plate1", "world", 50 , 50, 2.5, "Tungsten", offset("world", [0, 0, 1.25]) ) + plate_2 = add_box( sim, "plate2", "world", 40 , 40, 2.5, "Tungsten", offset("world", [0, 0, 3.75]) ) + + hole1_1 = add_box(sim, "hole1_1", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[0]) + hole1_2 = add_box(sim, "hole1_2", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[1]) + hole1_3 = add_box(sim, "hole1_3", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[2]) + hole1_4 = add_box(sim, "hole1_4", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[3]) + hole1_5 = add_box(sim, "hole1_5", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[4]) + hole1_6 = add_box(sim, "hole1_6", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[5]) + hole1_7 = add_box(sim, "hole1_7", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[6]) + hole1_8 = add_box(sim, "hole1_8", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[7]) + hole1_9 = add_box(sim, "hole1_9", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[8]) + hole1_10 = add_box(sim, "hole1_10", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[9]) + hole1_11 = add_box(sim, "hole1_11", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[10]) + hole1_12 = add_box(sim, "hole1_12", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[11]) + hole1_13 = add_box(sim, "hole1_13", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[12]) + hole1_14 = add_box(sim, "hole1_14", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[13]) + hole1_15 = add_box(sim, "hole1_15", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[14]) + hole1_16 = add_box(sim, "hole1_16", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[15]) + hole1_17 = add_box(sim, "hole1_17", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[16]) + hole1_18 = add_box(sim, "hole1_18", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[17]) + hole1_19 = add_box(sim, "hole1_19", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[18]) + hole1_20 = add_box(sim, "hole1_20", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[19]) + hole1_21 = add_box(sim, "hole1_21", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[20]) + hole1_22 = add_box(sim, "hole1_22", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[21]) + hole1_23 = add_box(sim, "hole1_23", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[22]) + hole1_24 = add_box(sim, "hole1_24", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[23]) + hole1_25 = add_box(sim, "hole1_25", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[24]) + hole2_1 = add_box(sim, "hole2_1", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[0]) + hole2_2 = add_box(sim, "hole2_2", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[1]) + hole2_3 = add_box(sim, "hole2_3", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[2]) + hole2_4 = add_box(sim, "hole2_4", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[3]) + hole2_5 = add_box(sim, "hole2_5", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[4]) + hole2_6 = add_box(sim, "hole2_6", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[5]) + hole2_7 = add_box(sim, "hole2_7", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[6]) + hole2_8 = add_box(sim, "hole2_8", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[7]) + hole2_9 = add_box(sim, "hole2_9", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[8]) + hole2_10 = add_box(sim, "hole2_10", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[9]) + hole2_11 = add_box(sim, "hole2_11", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[10]) + hole2_12 = add_box(sim, "hole2_12", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[11]) + hole2_13 = add_box(sim, "hole2_13", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[12]) + hole2_14 = add_box(sim, "hole2_14", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[13]) + hole2_15 = add_box(sim, "hole2_15", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[14]) + hole2_16 = add_box(sim, "hole2_16", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[15]) + hole2_17 = add_box(sim, "hole2_17", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[16]) + hole2_18 = add_box(sim, "hole2_18", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[17]) + hole2_19 = add_box(sim, "hole2_19", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[18]) + hole2_20 = add_box(sim, "hole2_20", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[19]) + hole2_21 = add_box(sim, "hole2_21", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[20]) + hole2_22 = add_box(sim, "hole2_22", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[21]) + hole2_23 = add_box(sim, "hole2_23", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[22]) + hole2_24 = add_box(sim, "hole2_24", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[23]) + hole2_25 = add_box(sim, "hole2_25", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[24]) + + + + elif string_name == 'mb_40_slit_11': + plate_1 = add_box( sim, "plate1", "world", 50 , 50, 2.5, "Tungsten", offset("world", [0, 0, 1.25]) ) + plate_2 = add_box( sim, "plate2", "world", 40 , 40, 2.5, "Tungsten", offset("world", [0, 0, 3.75]) ) + hole1_1 = add_box(sim, "hole1_1", plate_1, 1*mm, 10*mm, 2.5*mm, "Air", positions[0]) + hole1_2 = add_box(sim, "hole1_2", plate_1, 1*mm, 10*mm, 2.5*mm, "Air", positions[1]) + hole1_3 = add_box(sim, "hole1_3", plate_1, 1*mm, 10*mm, 2.5*mm, "Air", positions[2]) + hole1_4 = add_box(sim, "hole1_4", plate_1, 1*mm, 10*mm, 2.5*mm, "Air", positions[3]) + hole1_5 = add_box(sim, "hole1_5", plate_1, 1*mm, 10*mm, 2.5*mm, "Air", positions[4]) + hole2_1 = add_box(sim, "hole2_1", plate_2, 1*mm, 10*mm, 2.5*mm, "Air", positions[0]) + hole2_2 = add_box(sim, "hole2_2", plate_2, 1*mm, 10*mm, 2.5*mm, "Air", positions[1]) + hole2_3 = add_box(sim, "hole2_3", plate_2, 1*mm, 10*mm, 2.5*mm, "Air", positions[2]) + hole2_4 = add_box(sim, "hole2_4", plate_2, 1*mm, 10*mm, 2.5*mm, "Air", positions[3]) + hole2_5 = add_box(sim, "hole2_5", plate_2, 1*mm, 10*mm, 2.5*mm, "Air", positions[4]) + + + if string_name == 'shaper40': + start_z, end_z = get_object_zdimension(final_components, center_z) + logger.info(f"Passive collimator - '{string_name}' - range: start at {start_z:.3f} mm, end at {end_z:.3f} mm") + return end_z, leaf_objects + + elif string_name == ('mb_40_holes_11' or 'mb_40_slit_11'): + start_z, end_z = get_object_zdimension(components, center_z) + logger.info(f"Passive collimator - '{string_name}' - range: start at {start_z:.3f} mm, end at {end_z:.3f} mm") + return end_z + + else: + start_z, end_z = get_object_zdimension(components, center_z) + logger.info(f"Passive collimator - '{string_name}' - range: start at {start_z:.3f} mm, end at {end_z:.3f} mm") + return end_z + + + +def build_dosephantombox(sim, _string_name, material, center_x=0, center_y=0, center_z=0, + dimension_x=None, dimension_y=None, dimension_z=None, material_colors=None): + """ + Creates and adds a box-shaped volume to the simulation representing a typical dose phantom. + + Args: + sim: The simulation object where volumes are added. + _string_name (str): Unique identifier name for the box volume. + material (str): The material name assigned to the box volume. + center_x (float, optional): X-coordinate (in mm) of the box center. Defaults to 0. + center_y (float, optional): Y-coordinate (in mm) of the box center. Defaults to 0. + center_z (float, optional): Z-coordinate (in mm) of the box center. Defaults to 0. + dimension_x (float): Size of the box along the X-axis (in mm). Must be specified. + dimension_y (float): Size of the box along the Y-axis (in mm). Must be specified. + dimension_z (float): Size of the box along the Z-axis (in mm). Must be specified. + material_colors (dict, optional): Dictionary mapping material names to color values. + + Raises: + ValueError: If any of the dimensions (dimension_x, dimension_y, dimension_z) are None. + + Returns: + Volume: The created box volume object added to the simulation. + """ + if None in (dimension_x, dimension_y, dimension_z): + raise ValueError("[ERROR] All dimensions (dimension_x, dimension_y, dimension_z) must be specified.") + + if material_colors is None: + material_colors = {} + + box = sim.add_volume("Box", _string_name) + box.mother = "world" + box.size = [dimension_x, dimension_y, dimension_z] + box.material = material + box.translation = [center_x, center_y, center_z] + + if material in material_colors: + box.color = material_colors[material] + + logger.info(f"Box - '{_string_name}' - of material '{material}' built at: " + f"({center_x:.3f}, {center_y:.3f}, {center_z:.3f}) mm with size: " + f"{dimension_x:.3f} x {dimension_y:.3f} x {dimension_z:.3f} mm.") + + return box + +def add_source(sim, number_of_events): + """ + Adds an electron source to the simulation with a discrete energy spectrum loaded from a file. + + Args: + sim: The simulation object to which the source is added. + number_of_events (int): Number of events to simulate from the source. + + Returns: + source: The created source object. + """ + spectrum_path = "utils/spectra/9Mev.txt" + source = sim.add_source("GenericSource", "source") + source.particle = "e-" + source.energy.type = "spectrum_discrete" + source.energy.spectrum_energies, source.energy.spectrum_weights = np.loadtxt(spectrum_path, unpack=True) + source.position.type = "disc" + source.position.radius = 3 * mm + source.position.sigma_r = 0.8 * mm + source.position.centre = [0, 0, 0] + + source.direction.type = 'momentum' + source.direction.momentum = [0, 0, 1] + + source.n = number_of_events + + logger.info(f"Added source with spectrum from '{spectrum_path}', " + f"number of events: {number_of_events}, " + f"position radius: {source.position.radius} mm.") + + return source + +def set_shaper_aperture(leaf1, leaf2, leaf3, leaf4, aperture_x_mm, aperture_y_mm): + """ + Adjusts leaf positions to create a square/rectangular aperture. + + Args: + leaf1, leaf2: Leaves that define the X direction. + leaf3, leaf4: Leaves that define the Y direction. + aperture_x_mm (float): Desired aperture in X direction (must be <= 40 mm). + aperture_y_mm (float): Desired aperture in Y direction (must be <= 40 mm). + + Raises: + ValueError: If aperture_x_mm or aperture_y_mm exceeds 40 mm. + """ + if aperture_x_mm > 40 or aperture_y_mm > 40: + raise ValueError(f"[ERROR] Requested aperture exceeds maximum of 40 mm: " + f"X = {aperture_x_mm:.2f}, Y = {aperture_y_mm:.2f}") + + half_x = aperture_x_mm / 2 + half_y = aperture_y_mm / 2 + + # Update X leaves (left/right) + leaf1.translation[0] = half_x + leaf1.size[0] / 2 # right + leaf2.translation[0] = -half_x - leaf2.size[0] / 2 # left + + # Update Y leaves (top/bottom) + leaf3.translation[1] = half_y + leaf3.size[1] / 2 # top + leaf4.translation[1] = -half_y - leaf4.size[1] / 2 # bottom + + + +def rotate_leaves_around_z(leaf1, leaf2, leaf3, leaf4, angle_deg): + rot = R.from_euler('z', angle_deg, degrees=True) + rot_matrix = rot.as_matrix() # shape (3,3) + + for leaf in [leaf1, leaf2, leaf3, leaf4]: + x, y, z = leaf.translation + pos_rotated = rot.apply([x, y, z]) + leaf.translation = pos_rotated.tolist() + leaf.rotation = rot_matrix + +def obtain_pdd_from_image(path, mean_size=10): + """ + Loads a dose image and computes the normalized Percent Depth Dose (PDD) + from a central square ROI. + + Parameters: + path (str or Path): Path to the .mhd dose file. + + Returns: + pdd (np.ndarray): Normalized 1D PDD profile along depth (Z-axis). + """ + image = sitk.ReadImage(str(path)) + dose_array = sitk.GetArrayFromImage(image) + + y_center, x_center = int(dose_array.shape[1]/2), int(dose_array.shape[2]/2) + half = mean_size // 2 + roi_y = slice(y_center - half, y_center + half) + roi_x = slice(x_center - half, x_center + half) + + pdd = np.mean(dose_array[:, roi_y, roi_x], axis=(1, 2)) + pdd /= np.max(pdd) + + return pdd + +def obtain_profile_from_image(path, mean_size=10): + """ + Loads a dose image and computes the normalized Percent Depth Dose (PDD) + from a central square ROI. + + Parameters: + path (str or Path): Path to the .mhd dose file. + + Returns: + pdd (np.ndarray): Normalized 1D PDD profile along depth (Z-axis). + """ + image = sitk.ReadImage(str(path)) + dose_array = sitk.GetArrayFromImage(image) + + profile = np.mean(dose_array[0:7, 120:130,:], axis = (0,1)) + profile /= np.max(profile) + + return profile + +def evaluate_pdd_similarity(reference_pdd, test_pdd, tolerance=0.03): + """ + Compares two PDD curves using Mean Absolute Error (MAE). + + Parameters: + reference_pdd (np.1darray): Reference PDD curve (normalized). + test_pdd (np.1darray) : Test PDD curve (normalized). + tolerance (float) : MAE tolerance threshold. + + Returns: + passed (bool): True if MAE < tolerance. + """ + mae = np.mean(np.abs(reference_pdd - test_pdd)) + passed = mae < tolerance + + return passed, mae + +def evaluate_profile_similarity(reference_profile, test_profile, tolerance=0.2): + """ + Compares two PDD curves using Mean Absolute Error (MAE). + + Parameters: + reference_pdd (np.1darray): Reference PDD curve (normalized). + test_pdd (np.1darray) : Test PDD curve (normalized). + tolerance (float) : MAE tolerance threshold. + + Returns: + passed (bool): True if MAE < tolerance. + """ + mae = np.mean(np.abs(reference_profile - test_profile)) + passed = mae < tolerance + + return passed, mae \ No newline at end of file diff --git a/opengate/contrib/linacs/ElectronFlash/simulation_standard.py b/opengate/contrib/linacs/ElectronFlash/simulation_standard.py new file mode 100644 index 000000000..8efcd0cc9 --- /dev/null +++ b/opengate/contrib/linacs/ElectronFlash/simulation_standard.py @@ -0,0 +1,132 @@ +import opengate as gate +import numpy as np +import EF_function as fun +import os, sys, logging + + + + +if __name__ == "__main__": + #===================================================== + # INITIALISATION + #===================================================== + + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + gate_materials_path = "utils/GateMaterials.db" + number_of_total_events = 1_000_000 + + + sim = gate.Simulation() + sim.verbose_level = gate.logger.RUN + sim.running_verbose_level = gate.logger.RUN + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.visu_type = "qt" + sim.random_engine = "MersenneTwister" + sim.random_seed = "auto" + sim.output_dir = "output" + sim.number_of_threads = 4 + sim.progress_bar = True + sim.volume_manager.add_material_database(gate_materials_path) + if sim.visu: + sim.number_of_threads = 1 + number_of_total_events = 1 + number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 + + + + #===================================================== + # GEOMETRY + #===================================================== + + mm = fun.mm + + ###### Build Linac from titanium window up to BLD + EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) + + + ###### Build different BLDs (app100, app40) + App100_end = fun.build_passive_collimation(sim, "app100", center_z=EF_end, material_colors=fun.material_colors) + #App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) + + ###### If the proper applicator is selected (app40), you can add the MB template or the beam shaper device + #MB_end = fun.build_passive_collimation(sim, "mb_40_holes_11", center_z=App40_end, material_colors=fun.material_colors) + + #shaper40_end, (leaf1, leaf2, leaf3, leaf4) = fun.build_passive_collimation(sim, "shaper40", center_z=App40_end, material_colors=fun.material_colors) + #fun.set_shaper_aperture(leaf1, leaf2, leaf3, leaf4, aperture_x_mm = 20, aperture_y_mm = 20) + #fun.rotate_leaves_around_z(leaf1, leaf2, leaf3, leaf4, angle_deg = 45) + + + #===================================================== + # PHANTOMS + #===================================================== + + ### Build a water phantom for openfield dose deposition - OK + dim_x, dim_y, dim_z = 250*mm, 250*mm, 60*mm + dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=App100_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) + + ### Build a water phantom for MB applications - OK + #dim_x, dim_y, dim_z = 100*mm, 100*mm, 30*mm + #dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=MB_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) + + ### Build a plane for phase space test + #dim_x, dim_y, dim_z = 150*mm, 150*mm, 0.01*mm + #phsp_plane = fun.build_dosephantombox(sim, "Phasespace_plane", "Air", center_z=MB_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) + + + + #===================================================== + # ACTORS + #===================================================== + + ## DoseActor for PDD/profiles (openfield and shaper) + dose = sim.add_actor("DoseActor", "dose") + dose.attached_to = "Waterbox" + dose.output_filename = "dose.mhd" + dose.hit_type = "random" + dose.size = [120, 120, 30] # Number of voxels + dose.spacing = [1 * mm, 1 * mm, 2 * mm] # Voxel size + dose.dose.active = True + dose.dose_uncertainty.active = False + dose.edep_uncertainty.active = False + + ## DoseActor for MB - OK + #dose = sim.add_actor("DoseActor", "dose") + #dose.attached_to = "Waterbox" + #dose.output_filename = "dose.mhd" + #dose.hit_type = "random" + #dose.size = [250, 250, 30] # Number of voxels + #dose.spacing = [0.1 * mm, 0.1 * mm, 1 * mm] # Voxel size + #dose.dose.active = True + #dose.dose_uncertainty.active = False + #dose.edep_uncertainty.active = False + + ### PHSP actor for tests - OK + #phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + #phsp.attached_to = phsp_plane.name + #phsp.attributes = ["KineticEnergy","PreDirection","EventPosition",] + #phsp.output_filename = "phsp.root" + + + #===================================================== + # PHYSICS + #===================================================== + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" + sim.physics_manager.set_production_cut("world", "all", 2*mm) + + + #===================================================== + # SOURCE + #===================================================== + source = fun.add_source(sim, number_of_events) + + + + #===================================================== + # START BEAMS + #===================================================== + sim.run() \ No newline at end of file diff --git a/opengate/contrib/linacs/ElectronFlash/utils/GateMaterials.db b/opengate/contrib/linacs/ElectronFlash/utils/GateMaterials.db new file mode 100644 index 000000000..24c246a47 --- /dev/null +++ b/opengate/contrib/linacs/ElectronFlash/utils/GateMaterials.db @@ -0,0 +1,790 @@ +[Elements] +Hydrogen: S= H ; Z= 1. ; A= 1.01 g/mole +Helium: S= He ; Z= 2. ; A= 4.003 g/mole +Lithium: S= Li ; Z= 3. ; A= 6.941 g/mole +Lithium6: S= Li6 ; Z= 3. ; A= 6.015 g/mole +Lithium7: S= Li7 ; Z= 3. ; A= 7.016 g/mole +Beryllium: S= Be ; Z= 4. ; A= 9.012 g/mole +Boron: S= B ; Z= 5. ; A= 10.811 g/mole +Boron10: S= B10 ; Z= 5. ; A= 10.013 g/mole +Carbon: S= C ; Z= 6. ; A= 12.01 g/mole +Nitrogen: S= N ; Z= 7. ; A= 14.01 g/mole +Oxygen: S= O ; Z= 8. ; A= 16.00 g/mole +Fluorine: S= F ; Z= 9. ; A= 18.998 g/mole +Neon: S= Ne ; Z= 10. ; A= 20.180 g/mole +Sodium: S= Na ; Z= 11. ; A= 22.99 g/mole +Magnesium: S= Mg ; Z= 12. ; A= 24.305 g/mole +Aluminium: S= Al ; Z= 13. ; A= 26.981539 g/mole +Silicon: S= Si ; Z= 14. ; A= 28.09 g/mole +Phosphor: S= P ; Z= 15. ; A= 30.97 g/mole +Sulfur: S= S ; Z= 16. ; A= 32.066 g/mole +Chlorine: S= Cl ; Z= 17. ; A= 35.45 g/mole +Argon: S= Ar ; Z= 18. ; A= 39.95 g/mole +Potassium: S= K ; Z= 19. ; A= 39.098 g/mole +Calcium: S= Ca ; Z= 20. ; A= 40.08 g/mole +Scandium: S= Sc ; Z= 21. ; A= 44.956 g/mole +Titanium: S= Ti ; Z= 22. ; A= 47.867 g/mole +Vanadium: S= V ; Z= 23. ; A= 50.942 g/mole +Chromium: S= Cr ; Z= 24. ; A= 51.996 g/mole +Manganese: S= Mn ; Z= 25. ; A= 54.938 g/mole +Iron: S= Fe ; Z= 26. ; A= 55.845 g/mole +Cobalt: S= Co ; Z= 27. ; A= 58.933 g/mole +Nickel: S= Ni ; Z= 28. ; A= 58.693 g/mole +Copper: S= Cu ; Z= 29. ; A= 63.39 g/mole +Zinc: S= Zn ; Z= 30. ; A= 65.39 g/mole +Gallium: S= Ga ; Z= 31. ; A= 69.723 g/mole +Germanium: S= Ge ; Z= 32. ; A= 72.61 g/mole +Arsenic: S= As ; Z= 33. ; A= 74.9 g/mole +Bromine: S= Br ; Z= 35. ; A= 79.90 g/mole +Rubidium: S= Rb ; Z= 37. ; A= 85.4678 g/mole +Strontium: S= Sr ; Z= 38. ; A= 87.62 g/mole +Yttrium: S= Y ; Z= 39. ; A= 88.91 g/mole +Zirconium: S= Zr ; Z= 40. ; A= 91.224 g/mole +Molybdenum: S= Mo ; Z= 42. ; A= 95.95 g/mole +Technetium: S= Tc ; Z= 43. ; A= 99.0 g/mole +Palladium: S= Pd ; Z= 46. ; A= 106.4 g/mole +Silver: S= Ag ; Z= 47. ; A= 107.868 g/mole +Cadmium: S= Cd ; Z= 48. ; A= 112.41 g/mole +Indium: S= In ; Z= 49. ; A= 114.8 g/mole +Tin: S= Sn ; Z= 50. ; A= 118.71 g/mole +Antimony: S= Sb ; Z= 51. ; A= 121.76 g/mole +Tellurium: S= Te ; Z= 52. ; A= 127.6 g/mole +Iodine: S= I ; Z= 53. ; A= 126.90 g/mole +Cesium: S= Cs ; Z= 55. ; A= 132.905 g/mole +Lanthanum: S= La ; Z= 57. ; A= 138.91 g/mole +Cerium: S= Ce ; Z= 58. ; A= 140.116 g/mole +Samarium: S= Sm ; Z= 62. ; A= 150.36 g/mole +Gadolinium: S= Gd ; Z= 64. ; A= 157.25 g/mole +Lutetium: S= Lu ; Z= 71. ; A= 174.97 g/mole +Tantalum: S= Ta ; Z= 73. ; A= 180.95 g/mole +Tungsten: S= W ; Z= 74. ; A= 183.84 g/mole +Gold: S= Au ; Z= 79. ; A= 196.966 g/mole +Thallium: S= Tl ; Z= 81. ; A= 204.37 g/mole +Lead: S= Pb ; Z= 82. ; A= 207.20 g/mole +Bismuth: S= Bi ; Z= 83. ; A= 208.98 g/mole +Uranium: S= U ; Z= 92. ; A= 238.03 g/mole + +[Materials] +Vacuum: d=0.000001 mg/cm3 ; n=1 + +el: name=Hydrogen ; n=1 + +Air: d=1.29 mg/cm3 ; n=4 ; state=gas + +el: name=Nitrogen ; f=0.755268 + +el: name=Oxygen ; f=0.231781 + +el: name=Argon ; f=0.012827 + +el: name=Carbon ; f=0.000124 + + +Tecapeek: d=1.32 g/cm3 ; n=3; state=solid + +el: name=Hydrogen; f = 0.0476 + +el: name=Carbon ; f = 0.7619 + +el: name=Oxygen ; f = 0.1905 + +Nickel: d=8.908 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Gold: d=19.3 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Carbon: d=2.1 g/cm3 ; n=1; state=solid + +el: name=auto ; n=1 + +Copper: d=8.96 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Aluminium: d=2.69890 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Titanium: d=4.54 g/cm3; n=1; state=solid + +el: name=auto ; n=1 + +Beryllium: d=1.85 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +AluminiumEGS: d=2.702 g/cm3 ; n=1 ; state=solid + +el: name=Aluminium ; n=1 + +Uranium: d=18.90 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Silicon: d=2.33 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Zinc: d=7.1 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Germanium: d=5.32 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Strontium: d=2.64 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Yttrium: d=4.47 g/cm3 ; n=1 + +el: name=auto ; n=1 + +Molybdenum: d=10.28 g/cm3 ; n=1 + +el: name=auto ; n=1 + +Gadolinium: d=7.9 g/cm3 ; n=1 + +el: name=auto ; n=1 + +Lutetium: d=9.84 g/cm3 ; n=1 + +el: name=auto ; n=1 + +Tantalum: d=16.65 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Tungsten: d=19.3 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Lead: d=11.4 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Bismuth: d=9.75 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +NaI: d=3.67 g/cm3; n=2; state=solid + +el: name=Sodium ; n=1 + +el: name=Iodine ; n=1 + +NaITl: d=3.67 g/cm3; n=3; state=solid + +el: name=Sodium ; f=0.152 + +el: name=Iodine ; f=0.838 + +el: name=Thallium ; f=0.010 + +CsI: d=4.51 g/cm3; n=2; state=solid + +el: name=Cesium ; n=1 + +el: name=Iodine ; n=1 + +CsITl: d=4.51 g/cm3; n=3; state=solid + +el: name=Cesium ; f=0.511 + +el: name=Iodine ; f=0.488 + +el: name=Thallium ; f=7.86e-04 + +CsINa: d=4.51 g/cm3; n=3; state=solid + +el: name=Cesium ; f=0.511 + +el: name=Iodine ; f=0.488 + +el: name=Sodium ; f=1e-04 + +PWO: d=8.28 g/cm3; n=3 ; state=solid + +el: name=Lead ; n=1 + +el: name=Tungsten ; n=1 + +el: name=Oxygen ; n=4 + +BGO: d=7.13 g/cm3; n= 3 ; state=solid + +el: name=Bismuth ; n=4 + +el: name=Germanium ; n=3 + +el: name=Oxygen ; n=12 + +LSO: d=7.4 g/cm3; n=3 ; state=solid + +el: name=Lutetium ; n=2 + +el: name=Silicon ; n=1 + +el: name=Oxygen ; n=5 + +Plexiglass: d=1.19 g/cm3; n=3; state=solid + +el: name=Hydrogen ; f=0.080538 + +el: name=Carbon ; f=0.599848 + +el: name=Oxygen ; f=0.319614 + +GSO: d=6.7 g/cm3; n=3 ; state=solid + +el: name=Gadolinium ; n=2 + +el: name=Silicon ; n=1 + +el: name=Oxygen ; n=5 + +LuAP: d=8.34 g/cm3; n=3 ; state=solid + +el: name=Lutetium ; n=1 + +el: name=Aluminium ; n=1 + +el: name=Oxygen ; n=3 + +YAP: d=5.55 g/cm3; n=3 ; state=solid + +el: name=Yttrium ; n=1 + +el: name=Aluminium ; n=1 + +el: name=Oxygen ; n=3 + +Lucite: d=1.19 g/cm3; n=3; + +el: name=Hydrogen ; f=0.080538 + +el: name=Carbon ; f=0.599848 + +el: name=Oxygen ; f=0.319614 + +CarbonFiber: d=1.75 g/cm3; n=2; state=Solid + +el: name=Carbon ; f=0.977 + +el: name=Oxygen ; f=0.023 + +Water: d=1.00 g/cm3; n=2 ; state=liquid + +el: name=Hydrogen ; n=2 + +el: name=Oxygen ; n=1 + +Quartz: d=2.2 g/cm3; n=2 ; state=solid + +el: name=Silicon ; n=1 + +el: name=Oxygen ; n=2 + +Fe3O4: d=5.17 g/cm3; n=2; state=Solid + +el: name=Iron ; n=3 + +el: name=Oxygen ; n=4 + +Mylar: d=1.4 g/cm3; n=3; state=solid + +el: name=Hydrogen ; f=0.041958 + +el: name=Carbon ; f=0.625017 + +el: name=Oxygen ; f=0.333025 + +Kapton: d=1.42 g/cm3; n=4; state=solid + +el: name=Hydrogen ; f=0.026362 + +el: name=Carbon ; f=0.691133 + +el: name=Nitrogen ; f=0.073270 + +el: name=Oxygen ; f=0.209235 + +Nomex: d=0.95 g/cm3; n=4; state=solid + +el: name=Hydrogen ; n=10 + +el: name=Carbon ; n=14 + +el: name=Nitrogen ; n=2 + +el: name=Oxygen ; n=2 + +NitrogenGas: d=1.29 mg/cm3 ; n=1 ; state=gas + +el: name=Nitrogen ; f=1 + +Cerrotru: d=8.72 g/cm3; n=2 ; state=solid + +el: name=Bismuth ; f=0.58 + +el: name=Tin ; f=0.42 + +Breast: d=1.020 g/cm3 ; n = 8 + +el: name=Oxygen ; f=0.5270 + +el: name=Carbon ; f=0.3320 + +el: name=Hydrogen ; f=0.1060 + +el: name=Nitrogen ; f=0.0300 + +el: name=Sulfur ; f=0.0020 + +el: name=Sodium ; f=0.0010 + +el: name=Phosphor ; f=0.0010 + +el: name=Chlorine ; f=0.0010 + +Glass: d=2.5 g/cm3; n=4; state=solid + +el: name=Sodium ; f=0.1020 + +el: name=Calcium ; f=0.0510 + +el: name=Silicon ; f=0.2480 + +el: name=Oxygen ; f=0.5990 + +Glass_Pyrex: d=2.23 g/cm3; n=6; state=solid + +el: name=Boron ; f=0.040066 + +el: name=Oxygen ; f=0.539559 + +el: name=Sodium ; f=0.028191 + +el: name=Aluminium ; f=0.011644 + +el: name=Silicon ; f=0.377220 + +el: name=Potassium ; f=0.003321 + +Scinti-C9H10: d=1.032 g/cm3 ; n=2 + +el: name=Carbon ; n=9 + +el: name=Hydrogen ; n=10 + +LuYAP-70: d=7.1 g/cm3 ; n=4 + +el: name=Lutetium ; n=7 + +el: name=Yttrium ; n=3 + +el: name=Aluminium ; n=10 + +el: name=Oxygen ; n=30 + +LuYAP-80: d=7.5 g/cm3 ; n=4 + +el: name=Lutetium ; n=8 + +el: name=Yttrium ; n=2 + +el: name=Aluminium ; n=10 + +el: name=Oxygen ; n=30 + +Plastic: d=1.18 g/cm3 ; n=3; state=solid + +el: name=Carbon ; n=5 + +el: name=Hydrogen ; n=8 + +el: name=Oxygen ; n=2 + +Paraffine: d=0.8 g/cm3 ; n=3; state=solid + +el: name=Carbon ; n=5 + +el: name=Hydrogen ; n=8 + +el: name=Oxygen ; n=2 + +EJ230: d=1.023 g/cm3 ; n=2; state=solid + +el: name=Carbon ; n=10 + +el: name=Hydrogen ; n=11 + +HDPE: d=0.954 g/cm3 ; n=2; state=solid + +el: name=Carbon ; n=2 + +el: name=Hydrogen ; n=4 + +Biomimic: d=1.05 g/cm3 ; n=3; state=solid + +el: name=Carbon ; n=5 + +el: name=Hydrogen ; n=8 + +el: name=Oxygen ; n=2 + +FITC: d=1.0 g/cm3 ; n=1 + +el: name=Carbon ; n=1 + +RhB: d=1.0 g/cm3 ; n=1 + +el: name=Carbon ; n=1 + +CZT: d=5.68 g/cm3 ; n=3; state=solid + +el: name=Cadmium ; n=9 + +el: name=Zinc ; n=1 + +el: name=Tellurium ; n=10 + +Lung: d=0.26 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.103 + +el: name=Carbon ; f=0.105 + +el: name=Nitrogen ; f=0.031 + +el: name=Oxygen ; f=0.749 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.003 + +el: name=Potassium ; f=0.002 + +Polyethylene: d=0.96 g/cm3 ; n=2 + +el: name=Hydrogen ; n=2 + +el: name=Carbon ; n=1 + +PVC: d=1.65 g/cm3 ; n=3 ; state=solid + +el: name=Hydrogen ; n=3 + +el: name=Carbon ; n=2 + +el: name=Chlorine ; n=1 + +SS304: d=7.92 g/cm3 ; n=4 ; state=solid + +el: name=Iron ; f=0.695 + +el: name=Chromium ; f=0.190 + +el: name=Nickel ; f=0.095 + +el: name=Manganese ; f=0.020 + +PTFE: d= 2.18 g/cm3 ; n=2 ; state=solid + +el: name=Carbon ; n=1 + +el: name=Fluorine ; n=2 + +LYSO: d=7.36 g/cm3; n=4 ; state=solid + +el: name=Lutetium ; f=0.714467891 + +el: name=Yttrium ; f=0.04033805 + +el: name=Silicon ; f=0.063714272 + +el: name=Oxygen ; f=0.181479788 + +LYSO-Ce-Hilger: d=7.10 g/cm3; n=5 ; state=solid + +el: name=Lutetium ; f=0.713838658203075 + +el: name=Yttrium ; f=0.040302477781781 + +el: name=Silicon ; f=0.063721807284236 + +el: name=Oxygen ; f=0.181501252152072 + +el: name=Cerium ; f=0.000635804578835201 + +LaBr3: d=5.06 g/cm3 ; n=2 + +el: name=Bromine ; n=3 + +el: name=Lanthanum ; n=1 + +Millipore: d=1.0 g/cm3 ; n=3 + +el: name=Hydrogen ; n=10 + +el: name=Carbon ; n=6 + +el: name=Oxygen ; n=5 + +PCB: d=1.20 g/cm3 ; n=3 + +el: name=Hydrogen ; n=8 + +el: name=Carbon ; n=5 + +el: name=Oxygen ; n=2 + +Body: d=1.00 g/cm3 ; n=2 + +el: name=Hydrogen ; f=0.112 + +el: name=Oxygen ; f=0.888 + +A150_Tissue_Plastic: d=1.127 g/cm3 ; n=6 + +el: name=Hydrogen ; f=0.101330 + +el: name=Carbon ; f=0.775498 + +el: name=Nitrogen ; f=0.035057 + +el: name=Oxygen ; f=0.052315 + +el: name=Fluorine ; f=0.017423 + +el: name=Calcium ; f=0.018377 + +Muscle: d=1.05 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.102 + +el: name=Carbon ; f=0.143 + +el: name=Nitrogen ; f=0.034 + +el: name=Oxygen ; f=0.71 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.001 + +el: name=Potassium ; f=0.004 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +LungMoby: d=0.30 g/cm3 ; n=6 + +el: name=Hydrogen ; f=0.099 + +el: name=Carbon ; f=0.100 + +el: name=Nitrogen ; f=0.028 + +el: name=Oxygen ; f=0.740 + +el: name=Phosphor ; f=0.001 + +el: name=Calcium ; f=0.032 + +SpineBone: d=1.42 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.063 + +el: name=Carbon ; f=0.261 + +el: name=Nitrogen ; f=0.039 + +el: name=Oxygen ; f=0.436 + +el: name=Sodium ; f=0.001 + +el: name=Magnesium ; f=0.001 + +el: name=Phosphor ; f=0.061 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.001 + +el: name=Potassium ; f=0.001 + +el: name=Calcium ; f=0.133 + +RibBone: d=1.92 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.034 + +el: name=Carbon ; f=0.155 + +el: name=Nitrogen ; f=0.042 + +el: name=Oxygen ; f=0.435 + +el: name=Sodium ; f=0.001 + +el: name=Magnesium ; f=0.002 + +el: name=Phosphor ; f=0.103 + +el: name=Sulfur ; f=0.003 + +el: name=Calcium ; f=0.225 + +el: name=Scandium ; f=0.0 + +el: name=Titanium ; f=0.0 + +Epidermis: d=0.92 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.120 + +el: name=Carbon ; f=0.640 + +el: name=Nitrogen ; f=0.008 + +el: name=Oxygen ; f=0.229 + +el: name=Phosphor ; f=0.002 + +el: name=Calcium ; f=0.001 + +el: name=Scandium ; f=0.0 + +el: name=Titanium ; f=0.0 + +el: name=Vanadium ; f=0.0 + +el: name=Chromium ; f=0.0 + +el: name=Manganese ; f=0.0 + +Hypodermis: d=0.92 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.120 + +el: name=Carbon ; f=0.640 + +el: name=Nitrogen ; f=0.008 + +el: name=Oxygen ; f=0.229 + +el: name=Phosphor ; f=0.002 + +el: name=Calcium ; f=0.001 + +el: name=Scandium ; f=0.0 + +el: name=Titanium ; f=0.0 + +el: name=Vanadium ; f=0.0 + +el: name=Chromium ; f=0.0 + +el: name=Manganese ; f=0.0 + +Blood: d=1.06 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.102 + +el: name=Carbon ; f=0.11 + +el: name=Nitrogen ; f=0.033 + +el: name=Oxygen ; f=0.745 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.001 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.003 + +el: name=Potassium ; f=0.002 + +el: name=Iron ; f=0.001 + +el: name=Cobalt ; f=0.0 + +Heart: d=1.05 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.104 + +el: name=Carbon ; f=0.139 + +el: name=Nitrogen ; f=0.029 + +el: name=Oxygen ; f=0.718 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.003 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Kidney: d=1.05 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.103 + +el: name=Carbon ; f=0.132 + +el: name=Nitrogen ; f=0.03 + +el: name=Oxygen ; f=0.724 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.002 + +el: name=Calcium ; f=0.001 + +el: name=Scandium ; f=0.0 + +Liver: d=1.06 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.102 + +el: name=Carbon ; f=0.139 + +el: name=Nitrogen ; f=0.03 + +el: name=Oxygen ; f=0.716 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.003 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.003 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Lymph: d=1.03 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.108 + +el: name=Carbon ; f=0.041 + +el: name=Nitrogen ; f=0.011 + +el: name=Oxygen ; f=0.832 + +el: name=Sodium ; f=0.003 + +el: name=Sulfur ; f=0.001 + +el: name=Chlorine ; f=0.004 + +el: name=Argon ; f=0.0 + +el: name=Potassium ; f=0.0 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Pancreas: d=1.04 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.106 + +el: name=Carbon ; f=0.169 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.694 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.001 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.002 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Intestine: d=1.03 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.106 + +el: name=Carbon ; f=0.115 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.751 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.001 + +el: name=Sulfur ; f=0.001 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.001 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Skull: d=1.61 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.05 + +el: name=Carbon ; f=0.212 + +el: name=Nitrogen ; f=0.04 + +el: name=Oxygen ; f=0.435 + +el: name=Sodium ; f=0.001 + +el: name=Magnesium ; f=0.002 + +el: name=Phosphor ; f=0.081 + +el: name=Sulfur ; f=0.003 + +el: name=Calcium ; f=0.176 + +el: name=Scandium ; f=0.0 + +el: name=Titanium ; f=0.0 + +Cartilage: d=1.10 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.096 + +el: name=Carbon ; f=0.099 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.744 + +el: name=Sodium ; f=0.005 + +el: name=Phosphor ; f=0.022 + +el: name=Sulfur ; f=0.009 + +el: name=Chlorine ; f=0.003 + +el: name=Argon ; f=0.0 + +el: name=Potassium ; f=0.0 + +el: name=Calcium ; f=0.0 + +Brain: d=1.04 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.107 + +el: name=Carbon ; f=0.145 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.712 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.004 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.003 + +el: name=Potassium ; f=0.003 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Spleen: d=1.06 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.103 + +el: name=Carbon ; f=0.113 + +el: name=Nitrogen ; f=0.032 + +el: name=Oxygen ; f=0.741 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.003 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.003 + +el: name=Calcium ; f=0.0 + +el: name=Scandium ; f=0.0 + +Testis: d=1.04 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.106000 + +el: name=Carbon ; f=0.099000 + +el: name=Nitrogen ; f=0.020000 + +el: name=Oxygen ; f=0.766000 + +el: name=Sodium ; f=0.002000 + +el: name=Phosphor ; f=0.001000 + +el: name=Sulfur ; f=0.002000 + +el: name=Chlorine ; f=0.002000 + +el: name=Potassium ; f=0.002000 + +AirBodyInterface: d=0.296 g/cm3 ; n=15 + +el: name=Hydrogen ; f=0.10134 + +el: name=Carbon ; f=0.10238 + +el: name=Nitrogen ; f=0.02866 + +el: name=Oxygen ; f=0.75752 + +el: name=Sodium ; f=0.00184 + +el: name=Magnesium ; f=0.00007 + +el: name=Silicon ; f=0.00006 + +el: name=Phosphor ; f=0.00080 + +el: name=Sulfur ; f=0.00225 + +el: name=Chlorine ; f=0.00266 + +el: name=Potassium ; f=0.00194 + +el: name=Calcium ; f=0.00009 + +el: name=Iron ; f=0.00037 + +el: name=Zinc ; f=0.00001 + +el: name=Rubidium ; f=0.00001 + +Bones: d=1.4 g/cm3 ; n=18 + +el: name=Hydrogen ; f=0.07337 + +el: name=Carbon ; f=0.25475 + +el: name=Nitrogen ; f=0.03057 + +el: name=Oxygen ; f=0.47893 + +el: name=Fluorine ; f=0.00025 + +el: name=Sodium ; f=0.00326 + +el: name=Magnesium ; f=0.00112 + +el: name=Silicon ; f=0.00002 + +el: name=Phosphor ; f=0.05095 + +el: name=Sulfur ; f=0.00173 + +el: name=Chlorine ; f=0.00143 + +el: name=Potassium ; f=0.00153 + +el: name=Calcium ; f=0.10190 + +el: name=Iron ; f=0.00008 + +el: name=Zinc ; f=0.00005 + +el: name=Rubidium ; f=0.00002 + +el: name=Strontium ; f=0.00003 + +el: name=Lead ; f=0.00001 + +PE: d=0.93 g/cm3 ; n=2 + +el: name=Hydrogen ; n=2 + +el: name=Carbon ; n=1 + +CdTe: d=5.85 g/cm3 ; n=2; state=solid + +el: name=Cadmium ; f=0.468358 + +el: name=Tellurium ; f=0.531642 + +PMMA: d=1.19 g/cm3 ; n=3 ; state=solid + +el: name=Hydrogen ; f=0.080538 + +el: name=Carbon ; f=0.599848 + +el: name=Oxygen ; f=0.319614 + +Epoxy: d=1.0 g/cm3; n=3; state=solid + +el: name=Carbon ; n=1 + +el: name=Hydrogen ; n=1 + +el: name=Oxygen ; n=1 + +Carbide: d=15.8 g/cm3; n=2 ; state=solid + +el: name=Tungsten ; n=1 + +el: name=Carbon ; n=1 + +ABS: d=1.10 g/cm3; n=3 ; state=solid + +el: name=Hydrogen ; n=17 + +el: name=Carbon ; n=15 + +el: name=Nitrogen ; n=1 + +FR4_epoxy: d=1.85 g/cm3; n=4; state=solid + +el: name=Carbon ; f=0.6419 + +el: name=Hydrogen ; f=0.0643 + +el: name=Oxygen ; f=0.2036 + +el: name=Chlorine ; f=0.0902 + +FR4: d=1.85 g/cm3; n=11; state=solid + +el: name=Silicon ; f=0.15 + +el: name=Oxygen ; f=0.36 + +el: name=Calcium ; f=0.08 + +el: name=Aluminium ; f=0.04 + +el: name=Magnesium ; f=0.01 + +el: name=Boron ; f=0.01 + +el: name=Potassium ; f=0.01 + +el: name=Carbon ; f=0.27 + +el: name=Hydrogen ; f=0.03 + +el: name=Nitrogen ; f=0.03 + +el: name=Sodium ; f=0.01 + +FR4_Copper: d=2.2998 g/cm3; n=4; state=solid + +el: name=Gold ; f=0.01 + +el: name=Nickel ; f=0.02 + +el: name=Copper ; f=0.21 + +mat: name=FR4 ; f=0.76 + +FR4_Copper_epoxy: d=2.2998 g/cm3; n=7; state=solid + +el: name=Gold ; f=0.0131 + +el: name=Nickel ; f=0.0182 + +el: name=Copper ; f=0.2134 + +el: name=Carbon ; f=0.4848 + +el: name=Hydrogen ; f=0.0486 + +el: name=Oxygen ; f=0.1538 + +el: name=Chlorine ; f=0.0681 + +Gypsum: d=2.32 g/cm3 ; n=4 ; state=solid + +el: name=Hydrogen ; f=0.023416 + +el: name=Oxygen ; f=0.557572 + +el: name=Sulfur ; f=0.186215 + +el: name=Calcium ; f=0.232797 + +Concrete: d=2.35 g/cm3 ; n=10 ; state=solid + +el: name=Hydrogen ; f=0.0527 + +el: name=Oxygen ; f=0.4746 + +el: name=Sodium ; f=0.0162 + +el: name=Magnesium ; f=0.0024 + +el: name=Aluminium ; f=0.0433 + +el: name=Silicon ; f=0.3008 + +el: name=Sulfur ; f=0.0013 + +el: name=Potassium ; f=0.0182 + +el: name=Calcium ; f=0.0787 + +el: name=Iron ; f=0.0118 + +Concrete2: d=3.50 g/cm3 ; n=10 ; state=solid + +el: name=Hydrogen ; f=0.010000 + +el: name=Carbon ; f=0.001000 + +el: name=Oxygen ; f=0.529107 + +el: name=Sodium ; f=0.016000 + +el: name=Magnesium ; f=0.002000 + +el: name=Aluminium ; f=0.033872 + +el: name=Silicon ; f=0.337021 + +el: name=Potassium ; f=0.013000 + +el: name=Calcium ; f=0.044000 + +el: name=Iron ; f=0.014000 + +MRGEL: d=1.04 g/cm3; n=5 ; state=solid + +el: name=Hydrogen ; f=0.104161105 + +el: name=Carbon ; f=0.095211486 + +el: name=Nitrogen ; f=0.016995 + +el: name=Oxygen ; f=0.783004584 + +el: name=Sulfur ; f=0.000627825 + +Pyrex66: d=1.478 g/cm3 ; n=6 + +el: name=Oxygen ; f=0.5386 + +el: name=Silicon ; f=0.3768 + +el: name=Sodium ; f=0.0297 + +el: name=Phosphor ; f=0.0042 + +el: name=Boron ; f=0.0401 + +el: name=Aluminium ; f=0.0106 + +Steel: d=7.86 g/cm3; n=3; state=solid + +el: name=Carbon ; f=0.002 + +el: name=Manganese ; f=0.005 + +el: name=Iron ; f=0.993 + +GlassFiber: d=2.6 g/cm3; n=3; state=solid + +el: name=Hydrogen ; f=0.080538 + +el: name=Carbon ; f=0.599848 + +el: name=Oxygen ; f=0.319614 + +Polystyrene: d=0.05 g/cm3 ; n=2 ; state=solid + +el: name=Hydrogen ; n=8 + +el: name=Carbon ; n=8 + +LeadSb: d=11.16 g/cm3; n=2; state=solid + +el: name=Lead ; f=0.95 + +el: name=Antimony ; f=0.05 + +TiO2: d=4.23 g/cm3; n=2; state=solid + +el: name=Titanium ; f=0.5993 + +el: name=Oxygen ; f=0.4007 + +TiO: d=4.24 g/cm3; n=2; state=solid + +el: name=Titanium ; n=1 + +el: name=Oxygen ; n=1 + +Water_Catphan_LowD: d=0.85 g/cm3; n=2 ; state=liquid + +el: name=Hydrogen ; n=2 + +el: name=Oxygen ; n=1 \ No newline at end of file diff --git a/opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev.txt b/opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev.txt new file mode 100644 index 000000000..7bf5eab93 --- /dev/null +++ b/opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev.txt @@ -0,0 +1,80 @@ +0.100000 0.010140 +0.200000 0.010610 +0.400000 0.010170 +0.500000 0.010660 +0.600000 0.010670 +0.800000 0.012820 +0.900000 0.012130 +1.000000 0.014240 +1.200000 0.014720 +1.300000 0.014780 +1.400000 0.015790 +1.600000 0.016450 +1.700000 0.015990 +1.800000 0.016830 +2.000000 0.017250 +2.100000 0.018060 +2.200000 0.018160 +2.400000 0.018720 +2.500000 0.018940 +2.600000 0.019470 +2.800000 0.020100 +2.900000 0.020890 +3.000000 0.021930 +3.200000 0.022350 +3.300000 0.022880 +3.400000 0.024370 +3.600000 0.024890 +3.700000 0.026160 +3.800000 0.025970 +4.000000 0.037760 +4.100000 0.038230 +4.200000 0.049750 +4.400000 0.070680 +4.500000 0.051660 +4.600000 0.052850 +4.800000 0.054530 +4.900000 0.055060 +5.000000 0.067310 +5.200000 0.048420 +5.300000 0.039540 +5.400000 0.041320 +5.600000 0.043570 +5.700000 0.044770 +5.800000 0.047590 +6.000000 0.049810 +6.100000 0.051730 +6.200000 0.054590 +6.400000 0.056450 +6.500000 0.059620 +6.600000 0.062980 +6.800000 0.065880 +6.900000 0.068480 +7.000000 0.071810 +7.200000 0.077100 +7.300000 0.081210 +7.400000 0.084450 +7.600000 0.091070 +7.700000 0.094850 +7.800000 0.100830 +8.000000 0.107010 +8.100000 0.122463 +8.200000 0.130944 +8.400000 0.140327 +8.500000 0.145794 +8.600000 0.155859 +8.800000 0.165231 +8.900000 0.174790 +9.100000 0.184833 +9.300000 0.197934 +9.400000 0.206921 +9.500000 0.217404 +9.700000 0.230736 +9.800000 0.241120 +9.900000 0.251592 +10.100000 0.269731 +10.300000 0.301059 +10.500000 0.322168 +10.600000 0.346918 +10.700000 0.372229 +10.800000 1.000000 diff --git a/opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev_old.txt b/opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev_old.txt new file mode 100644 index 000000000..7bf5eab93 --- /dev/null +++ b/opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev_old.txt @@ -0,0 +1,80 @@ +0.100000 0.010140 +0.200000 0.010610 +0.400000 0.010170 +0.500000 0.010660 +0.600000 0.010670 +0.800000 0.012820 +0.900000 0.012130 +1.000000 0.014240 +1.200000 0.014720 +1.300000 0.014780 +1.400000 0.015790 +1.600000 0.016450 +1.700000 0.015990 +1.800000 0.016830 +2.000000 0.017250 +2.100000 0.018060 +2.200000 0.018160 +2.400000 0.018720 +2.500000 0.018940 +2.600000 0.019470 +2.800000 0.020100 +2.900000 0.020890 +3.000000 0.021930 +3.200000 0.022350 +3.300000 0.022880 +3.400000 0.024370 +3.600000 0.024890 +3.700000 0.026160 +3.800000 0.025970 +4.000000 0.037760 +4.100000 0.038230 +4.200000 0.049750 +4.400000 0.070680 +4.500000 0.051660 +4.600000 0.052850 +4.800000 0.054530 +4.900000 0.055060 +5.000000 0.067310 +5.200000 0.048420 +5.300000 0.039540 +5.400000 0.041320 +5.600000 0.043570 +5.700000 0.044770 +5.800000 0.047590 +6.000000 0.049810 +6.100000 0.051730 +6.200000 0.054590 +6.400000 0.056450 +6.500000 0.059620 +6.600000 0.062980 +6.800000 0.065880 +6.900000 0.068480 +7.000000 0.071810 +7.200000 0.077100 +7.300000 0.081210 +7.400000 0.084450 +7.600000 0.091070 +7.700000 0.094850 +7.800000 0.100830 +8.000000 0.107010 +8.100000 0.122463 +8.200000 0.130944 +8.400000 0.140327 +8.500000 0.145794 +8.600000 0.155859 +8.800000 0.165231 +8.900000 0.174790 +9.100000 0.184833 +9.300000 0.197934 +9.400000 0.206921 +9.500000 0.217404 +9.700000 0.230736 +9.800000 0.241120 +9.900000 0.251592 +10.100000 0.269731 +10.300000 0.301059 +10.500000 0.322168 +10.600000 0.346918 +10.700000 0.372229 +10.800000 1.000000 From c961cbdd66537271fd5122760e2223c81b9263d3 Mon Sep 17 00:00:00 2001 From: luigimasturzo <55702507+luigimasturzo@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:57:42 +0200 Subject: [PATCH 2/8] Update GateMaterials.db Added 3 different elements/compounds for ElectronFlash simulation --- opengate/contrib/GateMaterials.db | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/opengate/contrib/GateMaterials.db b/opengate/contrib/GateMaterials.db index b4660006d..54246e4aa 100644 --- a/opengate/contrib/GateMaterials.db +++ b/opengate/contrib/GateMaterials.db @@ -68,6 +68,21 @@ Uranium: S= U ; Z= 92. ; A= 238.03 g/mole Vacuum: d=0.000001 mg/cm3 ; n=1 +el: name=Hydrogen ; n=1 +Air: d=1.29 mg/cm3 ; n=4 ; state=gas + +el: name=Nitrogen ; f=0.755268 + +el: name=Oxygen ; f=0.231781 + +el: name=Argon ; f=0.012827 + +el: name=Carbon ; f=0.000124 + + +Tecapeek: d=1.32 g/cm3 ; n=3; state=solid + +el: name=Hydrogen; f = 0.0476 + +el: name=Carbon ; f = 0.7619 + +el: name=Oxygen ; f = 0.1905 + +AluminiumEGS: d=2.702 g/cm3 ; n=1 ; state=solid + +el: name=Aluminium ; n=1 + Nickel: d=8.908 g/cm3 ; n=1 ; state=solid +el: name=auto ; n=1 From 025ac18f593f2ca27094a7b473e3d7b55cb71fab Mon Sep 17 00:00:00 2001 From: luigimasturzo <55702507+luigimasturzo@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:59:39 +0200 Subject: [PATCH 3/8] Update simulation_standard.py --- .../contrib/linacs/ElectronFlash/simulation_standard.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/opengate/contrib/linacs/ElectronFlash/simulation_standard.py b/opengate/contrib/linacs/ElectronFlash/simulation_standard.py index 8efcd0cc9..df4db84d2 100644 --- a/opengate/contrib/linacs/ElectronFlash/simulation_standard.py +++ b/opengate/contrib/linacs/ElectronFlash/simulation_standard.py @@ -13,8 +13,8 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) + - gate_materials_path = "utils/GateMaterials.db" number_of_total_events = 1_000_000 @@ -36,7 +36,7 @@ number_of_total_events = 1 number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 - + sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") #===================================================== # GEOMETRY @@ -129,4 +129,4 @@ #===================================================== # START BEAMS #===================================================== - sim.run() \ No newline at end of file + sim.run() From 6445b70dfc00811258284670865ad69391e689d9 Mon Sep 17 00:00:00 2001 From: luigimasturzo <55702507+luigimasturzo@users.noreply.github.com> Date: Fri, 5 Sep 2025 10:19:51 +0200 Subject: [PATCH 4/8] Add files via upload Added tests files WITHOUT the reference data --- .../tests/src/test095_ElectronFlash_linac.py | 111 ++++++++++++++++ .../tests/src/test096_ElectronFlash_linac.py | 123 ++++++++++++++++++ .../tests/src/test097_ElectronFlash_linac.py | 118 +++++++++++++++++ .../tests/src/test098_ElectronFlash_linac.py | 114 ++++++++++++++++ .../test099_ElectronFlash_linac_geometry.py | 38 ++++++ .../tests/src/test100_ElectronFlash_linac.py | 114 ++++++++++++++++ .../tests/src/test101_ElectronFlash_linac.py | 115 ++++++++++++++++ .../tests/src/test102_ElectronFlash_linac.py | 117 +++++++++++++++++ .../tests/src/test103_ElectronFlash_linac.py | 116 +++++++++++++++++ 9 files changed, 966 insertions(+) create mode 100644 opengate/tests/src/test095_ElectronFlash_linac.py create mode 100644 opengate/tests/src/test096_ElectronFlash_linac.py create mode 100644 opengate/tests/src/test097_ElectronFlash_linac.py create mode 100644 opengate/tests/src/test098_ElectronFlash_linac.py create mode 100644 opengate/tests/src/test099_ElectronFlash_linac_geometry.py create mode 100644 opengate/tests/src/test100_ElectronFlash_linac.py create mode 100644 opengate/tests/src/test101_ElectronFlash_linac.py create mode 100644 opengate/tests/src/test102_ElectronFlash_linac.py create mode 100644 opengate/tests/src/test103_ElectronFlash_linac.py diff --git a/opengate/tests/src/test095_ElectronFlash_linac.py b/opengate/tests/src/test095_ElectronFlash_linac.py new file mode 100644 index 000000000..5ed40b45c --- /dev/null +++ b/opengate/tests/src/test095_ElectronFlash_linac.py @@ -0,0 +1,111 @@ +import opengate as gate +import numpy as np +import function as fun +import os, sys, logging +from function import mm +from opengate.tests import utility + +# test_ElectronFlash_dose_app40.py + + +if __name__ == "__main__": + #===================================================== + # INITIALISATION + #===================================================== + + + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + number_of_total_events = 500_000 + + paths = utility.get_default_test_paths(__file__, output_folder="test095_ElectronFlash_linac") + + sim = gate.Simulation() + sim.verbose_level = gate.logger.RUN + sim.running_verbose_level = gate.logger.RUN + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.visu_type = "qt" + sim.random_engine = "MersenneTwister" + sim.random_seed = 18101996 + sim.output_dir = paths.output + sim.number_of_threads = 3 + sim.progress_bar = True + if sim.visu: + sim.number_of_threads = 1 + number_of_total_events = 1 + number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 + + sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") + + #===================================================== + # GEOMETRY + #===================================================== + + EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) + App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) + + #===================================================== + # PHANTOMS + #===================================================== + + dim_x, dim_y, dim_z = 250*mm, 250*mm, 60*mm + dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=App40_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) + + #===================================================== + # ACTORS + #===================================================== + + dose = sim.add_actor("DoseActor", "dose") + dose.attached_to = "Waterbox" + dose.output_filename = "dose_test_app40.mhd" + dose.hit_type = "random" + dose.size = [120, 120, 30] # Number of voxels + dose.spacing = [1 * mm, 1 * mm, 2 * mm] # Voxel size + dose.dose.active = True + dose.dose_uncertainty.active = False + dose.edep_uncertainty.active = False + + + #===================================================== + # PHYSICS + #===================================================== + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" + sim.physics_manager.set_production_cut("world", "all", 2*mm) + + + #===================================================== + # SOURCE + #===================================================== + + source = fun.add_source(sim, number_of_events) + + + #===================================================== + # START BEAMS + #===================================================== + + sim.run() + + + #===================================================== + # Perform test + #===================================================== + + path_reference_dose = paths.output_ref / "dose_reference_app40_dose.mhd" + path_test_dose = dose.dose.get_output_path() + + reference_pdd = fun.obtain_pdd_from_image(path_reference_dose) + test_pdd = fun.obtain_pdd_from_image(path_test_dose) + is_ok, mae = fun.evaluate_pdd_similarity(reference_pdd, test_pdd) + + if is_ok: + print("test_ElectronFlash_dose_app40.py : PDD comparison test passed.") + else: + print("test_ElectronFlash_dose_app40.py : PDD comparison test failed. MAE = {:.3f}".format(mae)) + + + \ No newline at end of file diff --git a/opengate/tests/src/test096_ElectronFlash_linac.py b/opengate/tests/src/test096_ElectronFlash_linac.py new file mode 100644 index 000000000..b59f54d11 --- /dev/null +++ b/opengate/tests/src/test096_ElectronFlash_linac.py @@ -0,0 +1,123 @@ +import opengate as gate +import numpy as np +import function as fun +import os, sys, logging +import SimpleITK as sitk +from function import mm +from opengate.tests import utility + +# test_ElectronFlash_dose_app100 + + +if __name__ == "__main__": + #===================================================== + # INITIALISATION + #===================================================== + + # test_ElectronFlash_dose_app40.py + + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + number_of_total_events = 500_000 + + paths = utility.get_default_test_paths(__file__, output_folder="test096_ElectronFlash_linac") + + sim = gate.Simulation() + sim.verbose_level = gate.logger.RUN + sim.running_verbose_level = gate.logger.RUN + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.visu_type = "qt" + sim.random_engine = "MersenneTwister" + sim.random_seed = 18101996 + sim.output_dir = paths.output + sim.number_of_threads = 3 + sim.progress_bar = True + if sim.visu: + sim.number_of_threads = 1 + number_of_total_events = 1 + number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 + + sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") + + + #===================================================== + # GEOMETRY + #===================================================== + + EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) + App100_end = fun.build_passive_collimation(sim, "app100", center_z=EF_end, material_colors=fun.material_colors) + + #===================================================== + # PHANTOMS + #===================================================== + + dim_x, dim_y, dim_z = 250*mm, 250*mm, 60*mm + dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=App100_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) + + #===================================================== + # ACTORS + #===================================================== + + dose = sim.add_actor("DoseActor", "dose") + dose.attached_to = "Waterbox" + dose.output_filename = "dose_test_app100.mhd" + dose.hit_type = "random" + dose.size = [120, 120, 30] # Number of voxels + dose.spacing = [1 * mm, 1 * mm, 2 * mm] # Voxel size + dose.dose.active = True + dose.dose_uncertainty.active = False + dose.edep_uncertainty.active = False + + + #===================================================== + # PHYSICS + #===================================================== + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" + sim.physics_manager.set_production_cut("world", "all", 2*mm) + + + #===================================================== + # SOURCE + #===================================================== + + source = fun.add_source(sim, number_of_events) + + + #===================================================== + # START BEAMS + #===================================================== + + sim.run() + + + #===================================================== + # Perform test + #===================================================== + + path_reference_dose = paths.output_ref / "dose_reference_app100_dose.mhd" + path_test_dose = dose.dose.get_output_path() + + reference_pdd = fun.obtain_pdd_from_image(path_reference_dose) + test_pdd = fun.obtain_pdd_from_image(path_test_dose) + is_ok, mae = fun.evaluate_pdd_similarity(reference_pdd, test_pdd) + + if is_ok: + print("test002_ElectronFlash_linac : PDD comparison test passed.") + else: + print("test002_ElectronFlash_linac : PDD comparison test failed. MAE = {:.3f}".format(mae)) + + + + + + + + + + + + \ No newline at end of file diff --git a/opengate/tests/src/test097_ElectronFlash_linac.py b/opengate/tests/src/test097_ElectronFlash_linac.py new file mode 100644 index 000000000..06ecb9a4b --- /dev/null +++ b/opengate/tests/src/test097_ElectronFlash_linac.py @@ -0,0 +1,118 @@ +import opengate as gate +import numpy as np +import function as fun +import os, sys, logging +from function import mm +from opengate.tests import utility +import SimpleITK as sitk + +# test MB slit + +if __name__ == "__main__": + #===================================================== + # INITIALISATION + #===================================================== + + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + + number_of_total_events = 500_000 + + paths = utility.get_default_test_paths(__file__, output_folder="test097_ElectronFlash_linac") + + sim = gate.Simulation() + sim.verbose_level = gate.logger.RUN + sim.running_verbose_level = gate.logger.RUN + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.visu_type = "qt" + sim.random_engine = "MersenneTwister" + sim.random_seed = 18101996 + sim.output_dir = paths.output + sim.number_of_threads = 3 + sim.progress_bar = True + + if sim.visu: + sim.number_of_threads = 1 + number_of_total_events = 1 + number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 + + sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") + + + #===================================================== + # GEOMETRY + #===================================================== + + EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) + App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) + MB_end = fun.build_passive_collimation(sim, "mb_40_slit_11", center_z=App40_end, material_colors=fun.material_colors) + + + #===================================================== + # PHANTOMS + #===================================================== + + dim_x, dim_y, dim_z = 100*mm, 100*mm, 20*mm + dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=MB_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) + + #===================================================== + # ACTORS + #===================================================== + + dose = sim.add_actor("DoseActor", "dose") + dose.attached_to = "Waterbox" + dose.output_filename = "dose_test_MBslit.mhd" + dose.hit_type = "random" + dose.size = [250, 250, 20] # Number of voxels + dose.spacing = [0.1 * mm, 0.1 * mm, 1 * mm] # Voxel size + dose.dose.active = True + dose.dose_uncertainty.active = False + dose.edep_uncertainty.active = False + + + #===================================================== + # PHYSICS + #===================================================== + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" + sim.physics_manager.set_production_cut("world", "all", 2*mm) + + + #===================================================== + # SOURCE + #===================================================== + + source = fun.add_source(sim, number_of_events) + + + #===================================================== + # START BEAMS + #===================================================== + + sim.run() + + + #===================================================== + # Perform test + #===================================================== + + path_reference_dose = paths.output_ref / "dose_reference_MBslit_dose.mhd" + path_test_dose = dose.dose.get_output_path() + + reference_profile = fun.obtain_pdd_from_image(path_reference_dose) + test_profile = fun.obtain_pdd_from_image(path_test_dose) + is_ok, mae = fun.evaluate_profile_similarity(reference_profile, test_profile) + + if is_ok: + print("test003_ElectronFlash_linac.py : Profile comparison test passed.") + else: + print("test003_ElectronFlash_linac.py : Profile comparison test failed. MAE = {:.3f}".format(mae)) + + + + + + \ No newline at end of file diff --git a/opengate/tests/src/test098_ElectronFlash_linac.py b/opengate/tests/src/test098_ElectronFlash_linac.py new file mode 100644 index 000000000..14ee00684 --- /dev/null +++ b/opengate/tests/src/test098_ElectronFlash_linac.py @@ -0,0 +1,114 @@ +import opengate as gate +import numpy as np +import function as fun +import os, sys, logging +from function import mm +from opengate.tests import utility + +if __name__ == "__main__": + #===================================================== + # INITIALISATION + #===================================================== + + + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + + number_of_total_events = 500_000 + + paths = utility.get_default_test_paths(__file__, output_folder="test098_ElectronFlash_linac") + + sim = gate.Simulation() + sim.verbose_level = gate.logger.RUN + sim.running_verbose_level = gate.logger.RUN + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.visu_type = "qt" + sim.random_engine = "MersenneTwister" + sim.random_seed = 18101996 + sim.output_dir = paths.output + sim.number_of_threads = 3 + sim.progress_bar = True + + if sim.visu: + sim.number_of_threads = 1 + number_of_total_events = 1 + number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 + + sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") + + + #===================================================== + # GEOMETRY + #===================================================== + + EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) + App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) + shaper40_end, (leaf1, leaf2, leaf3, leaf4) = fun.build_passive_collimation(sim, "shaper40", center_z=App40_end, material_colors=fun.material_colors) + fun.set_shaper_aperture(leaf1, leaf2, leaf3, leaf4, aperture_x_mm = 25, aperture_y_mm = 35) + fun.rotate_leaves_around_z(leaf1, leaf2, leaf3, leaf4, angle_deg = 45) + + #===================================================== + # PHANTOMS + #===================================================== + + dim_x, dim_y, dim_z = 250*mm, 250*mm, 60*mm + dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=shaper40_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) + + #===================================================== + # ACTORS + #===================================================== + + dose = sim.add_actor("DoseActor", "dose") + dose.attached_to = "Waterbox" + dose.output_filename = "dose_test_shaper40.mhd" + dose.hit_type = "random" + dose.size = [120, 120, 30] # Number of voxels + dose.spacing = [1 * mm, 1 * mm, 2 * mm] # Voxel size + dose.dose.active = True + dose.dose_uncertainty.active = False + dose.edep_uncertainty.active = False + + + #===================================================== + # PHYSICS + #===================================================== + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" + sim.physics_manager.set_production_cut("world", "all", 2*mm) + + + #===================================================== + # SOURCE + #===================================================== + + source = fun.add_source(sim, number_of_events) + + + #===================================================== + # START BEAMS + #===================================================== + + sim.run() + + #===================================================== + # Perform test + #===================================================== + + path_reference_dose = paths.output_ref / "dose_reference_shaper40_dose.mhd" + path_test_dose = dose.dose.get_output_path() + + reference_pdd = fun.obtain_pdd_from_image(path_reference_dose) + test_pdd = fun.obtain_pdd_from_image(path_test_dose) + is_ok, mae = fun.evaluate_pdd_similarity(reference_pdd, test_pdd) + + if is_ok: + print("test004_ElectronFlash_linac : PDD comparison test passed.") + else: + print("test004_ElectronFlash_linac : PDD comparison test failed. MAE = {:.3f}".format(mae)) + + + + \ No newline at end of file diff --git a/opengate/tests/src/test099_ElectronFlash_linac_geometry.py b/opengate/tests/src/test099_ElectronFlash_linac_geometry.py new file mode 100644 index 000000000..125acf7bd --- /dev/null +++ b/opengate/tests/src/test099_ElectronFlash_linac_geometry.py @@ -0,0 +1,38 @@ +import opengate as gate +import math +from scipy.spatial.transform import Rotation as R +import function as fun + + +def test_geometry_length(_geometry_string): + sim = gate.Simulation() + expected_lengths = { + "ElectronFlash" : 316.755, + "app100" : 784.8, + "app40" : 409.8, + "shaper40" : 63.0, + "mb_40_holes_11": 30.0, + "mb_40_slit_11" : 30.0 + } + if _geometry_string == "ElectronFlash": + total_length = fun.build_ElectronFlash(sim) + elif _geometry_string == "shaper40": + total_length, (_, _, _, _) = fun.build_passive_collimation(sim, _geometry_string, center_z=53) + elif _geometry_string in ("mb_40_holes_11", "mb_40_slit_11"): + total_length = fun.build_passive_collimation(sim, _geometry_string, center_z=25) + else: + total_length = fun.build_passive_collimation(sim, _geometry_string) + expected = expected_lengths.get(_geometry_string) + assert expected is not None, f"Unknown geometry: {_geometry_string}" + assert math.isclose(total_length, expected, rel_tol=1e-5), \ + f"Length mismatch for {_geometry_string}: expected {expected}, got {total_length}" + + + +test_geometry_length("ElectronFlash") +test_geometry_length("app100") +test_geometry_length("app40") +test_geometry_length("shaper40") +test_geometry_length("mb_40_holes_11") +test_geometry_length("mb_40_slit_11") + \ No newline at end of file diff --git a/opengate/tests/src/test100_ElectronFlash_linac.py b/opengate/tests/src/test100_ElectronFlash_linac.py new file mode 100644 index 000000000..ce28491da --- /dev/null +++ b/opengate/tests/src/test100_ElectronFlash_linac.py @@ -0,0 +1,114 @@ +import opengate as gate +import numpy as np +import function as fun +import os, sys, logging +from function import mm +from opengate.tests import utility + +if __name__ == "__main__": + #===================================================== + # INITIALISATION + #===================================================== + + + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + + number_of_total_events = 500_000 + + paths = utility.get_default_test_paths(__file__, output_folder="test100_ElectronFlash_linac") + + sim = gate.Simulation() + sim.verbose_level = gate.logger.RUN + sim.running_verbose_level = gate.logger.RUN + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.visu_type = "qt" + sim.random_engine = "MersenneTwister" + sim.random_seed = 18101996 + sim.output_dir = paths.output + sim.number_of_threads = 3 + sim.progress_bar = True + + if sim.visu: + sim.number_of_threads = 1 + number_of_total_events = 1 + number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 + + sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") + + + #===================================================== + # GEOMETRY + #===================================================== + + EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) + App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) + + #===================================================== + # PHANTOMS + #===================================================== + + dim_x, dim_y, dim_z = 150*mm, 150*mm, 0.01*mm + phsp_plane = fun.build_dosephantombox(sim, "Phasespace_plane", "Air", center_z=App40_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) + + #===================================================== + # ACTORS + #===================================================== + + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp.attached_to = phsp_plane.name + phsp.attributes = ["KineticEnergy","PreDirection","EventPosition",] + phsp.output_filename = "phsp_test_app40.root" + + + #===================================================== + # PHYSICS + #===================================================== + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" + sim.physics_manager.set_production_cut("world", "all", 2*mm) + + + #===================================================== + # SOURCE + #===================================================== + + source = fun.add_source(sim, number_of_events) + + + #===================================================== + # START BEAMS + #===================================================== + + sim.run() + + #===================================================== + # Perform test + #===================================================== + + path_reference_root_phsp = paths.output_ref / "phsp_reference_app40.root" + path_test_root_phsp = phsp.get_output_path() + + keys = ["KineticEnergy" ,"PreDirection_X","PreDirection_Y","EventPosition_X","EventPosition_Y"] + tols = [0.8, 0.8, 0.8, 0.8, 0.8] + br = "PhaseSpace;1" + is_ok = utility.compare_root3( + path_reference_root_phsp, + path_test_root_phsp, + br, + br, + keys, + keys, + tols, + None, + None, + paths.output / "test_EF_app40.png", + nb_bins=150, + hits_tol = 10**6 + ) + + + \ No newline at end of file diff --git a/opengate/tests/src/test101_ElectronFlash_linac.py b/opengate/tests/src/test101_ElectronFlash_linac.py new file mode 100644 index 000000000..37f7460a7 --- /dev/null +++ b/opengate/tests/src/test101_ElectronFlash_linac.py @@ -0,0 +1,115 @@ +import opengate as gate +import numpy as np +import function as fun +import os, sys, logging +from function import mm +from opengate.tests import utility + +if __name__ == "__main__": + #===================================================== + # INITIALISATION + #===================================================== + + + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + + number_of_total_events = 500_000 + + paths = utility.get_default_test_paths(__file__, output_folder="test101_ElectronFlash_linac") + + sim = gate.Simulation() + sim.verbose_level = gate.logger.RUN + sim.running_verbose_level = gate.logger.RUN + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.visu_type = "qt" + sim.random_engine = "MersenneTwister" + sim.random_seed = 18101996 + sim.output_dir = paths.output + sim.number_of_threads = 3 + sim.progress_bar = True + + if sim.visu: + sim.number_of_threads = 1 + number_of_total_events = 1 + number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 + + sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") + + + #===================================================== + # GEOMETRY + #===================================================== + + EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) + App100_end = fun.build_passive_collimation(sim, "app100", center_z=EF_end, material_colors=fun.material_colors) + + #===================================================== + # PHANTOMS + #===================================================== + + dim_x, dim_y, dim_z = 150*mm, 150*mm, 0.01*mm + phsp_plane = fun.build_dosephantombox(sim, "Phasespace_plane", "Air", center_z=App100_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) + + #===================================================== + # ACTORS + #===================================================== + + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp.attached_to = phsp_plane.name + phsp.attributes = ["KineticEnergy","PreDirection","EventPosition",] + phsp.output_filename = "phsp_test_app100.root" + + + #===================================================== + # PHYSICS + #===================================================== + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" + sim.physics_manager.set_production_cut("world", "all", 2*mm) + + + #===================================================== + # SOURCE + #===================================================== + + source = fun.add_source(sim, number_of_events) + + + #===================================================== + # START BEAMS + #===================================================== + + sim.run() + + + #===================================================== + # Perform test + #===================================================== + + path_reference_root_phsp = paths.output_ref / "phsp_reference_app100.root" + path_test_root_phsp = phsp.get_output_path() + + keys = ["KineticEnergy" ,"PreDirection_X","PreDirection_Y","EventPosition_X","EventPosition_Y"] + tols = [0.8, 0.8, 0.8, 0.8, 0.8] + br = "PhaseSpace;1" + is_ok = utility.compare_root3( + path_reference_root_phsp, + path_test_root_phsp, + br, + br, + keys, + keys, + tols, + None, + None, + paths.output / "test_EF_app100.png", + nb_bins=150, + hits_tol = 10**6 + ) + + + \ No newline at end of file diff --git a/opengate/tests/src/test102_ElectronFlash_linac.py b/opengate/tests/src/test102_ElectronFlash_linac.py new file mode 100644 index 000000000..5f2a5fc4e --- /dev/null +++ b/opengate/tests/src/test102_ElectronFlash_linac.py @@ -0,0 +1,117 @@ +import opengate as gate +import numpy as np +import function as fun +import os, sys, logging +from function import mm +from opengate.tests import utility + + + + +if __name__ == "__main__": + #===================================================== + # INITIALISATION + #===================================================== + + + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + + number_of_total_events = 500_000 + + paths = utility.get_default_test_paths(__file__, output_folder="test102_ElectronFlash_linac") + + sim = gate.Simulation() + sim.verbose_level = gate.logger.RUN + sim.running_verbose_level = gate.logger.RUN + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.visu_type = "qt" + sim.random_engine = "MersenneTwister" + sim.random_seed = 18101996 + sim.output_dir = paths.output + sim.number_of_threads = 3 + sim.progress_bar = True + + if sim.visu: + sim.number_of_threads = 1 + number_of_total_events = 1 + number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 + + sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") + + + #===================================================== + # GEOMETRY + #===================================================== + + ###### Build Linac from titanium window up to BLD + EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) + + + #===================================================== + # PHANTOMS + #===================================================== + + dim_x, dim_y, dim_z = 150*mm, 150*mm, 0.01*mm + phsp_plane = fun.build_dosephantombox(sim, "Phasespace_plane", "Air", center_z=EF_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) + + + #===================================================== + # ACTORS + #===================================================== + + + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp.attached_to = phsp_plane.name + phsp.attributes = ["KineticEnergy","PreDirection","EventPosition",] + phsp.output_filename = "phsp_test_nose.root" + + + #===================================================== + # PHYSICS + #===================================================== + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" + sim.physics_manager.set_production_cut("world", "all", 2*mm) + + + #===================================================== + # SOURCE + #===================================================== + + source = fun.add_source(sim, number_of_events) + + + #===================================================== + # START BEAMS + #===================================================== + + sim.run() + + #===================================================== + # Perform test + #===================================================== + + path_reference_root_phsp = paths.output_ref / "phsp_reference_nose.root" + path_test_root_phsp = phsp.get_output_path() + + keys = ["KineticEnergy" ,"PreDirection_X","PreDirection_Y","EventPosition_X","EventPosition_Y"] + tols = [0.8, 0.8, 0.8, 0.8, 0.8] + br = "PhaseSpace;1" + is_ok = utility.compare_root3( + path_reference_root_phsp, + path_test_root_phsp, + br, + br, + keys, + keys, + tols, + None, + None, + paths.output / "test_EF_nose.png", + nb_bins=150, + hits_tol = 10**6 + ) \ No newline at end of file diff --git a/opengate/tests/src/test103_ElectronFlash_linac.py b/opengate/tests/src/test103_ElectronFlash_linac.py new file mode 100644 index 000000000..f9d100112 --- /dev/null +++ b/opengate/tests/src/test103_ElectronFlash_linac.py @@ -0,0 +1,116 @@ +import opengate as gate +import numpy as np +import function as fun +import os, sys, logging +from function import mm +from opengate.tests import utility + +if __name__ == "__main__": + #===================================================== + # INITIALISATION + #===================================================== + + + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + + number_of_total_events = 500_000 + + paths = utility.get_default_test_paths(__file__, output_folder="test103_ElectronFlash_linac") + + sim = gate.Simulation() + sim.verbose_level = gate.logger.RUN + sim.running_verbose_level = gate.logger.RUN + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.visu_type = "qt" + sim.random_engine = "MersenneTwister" + sim.random_seed = 18101996 + sim.output_dir = paths.output + sim.number_of_threads = 3 + sim.progress_bar = True + + if sim.visu: + sim.number_of_threads = 1 + number_of_total_events = 1 + number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 + + sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") + + + #===================================================== + # GEOMETRY + #===================================================== + + EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) + App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) + shaper40_end, (leaf1, leaf2, leaf3, leaf4) = fun.build_passive_collimation(sim, "shaper40", center_z=App40_end, material_colors=fun.material_colors) + fun.set_shaper_aperture(leaf1, leaf2, leaf3, leaf4, aperture_x_mm = 25, aperture_y_mm = 35) + fun.rotate_leaves_around_z(leaf1, leaf2, leaf3, leaf4, angle_deg = 45) + #===================================================== + # PHANTOMS + #===================================================== + + dim_x, dim_y, dim_z = 150*mm, 150*mm, 0.01*mm + phsp_plane = fun.build_dosephantombox(sim, "Phasespace_plane", "Air", center_z=shaper40_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) + + #===================================================== + # ACTORS + #===================================================== + + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp.attached_to = phsp_plane.name + phsp.attributes = ["KineticEnergy","PreDirection","EventPosition",] + phsp.output_filename = "phsp_test_shaper40.root" + + + #===================================================== + # PHYSICS + #===================================================== + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" + sim.physics_manager.set_production_cut("world", "all", 2*mm) + + + #===================================================== + # SOURCE + #===================================================== + + source = fun.add_source(sim, number_of_events) + + + #===================================================== + # START BEAMS + #===================================================== + + sim.run() + + #===================================================== + # Perform test + #===================================================== + + path_reference_root_phsp = paths.output_ref / "phsp_reference_shaper40.root" + path_test_root_phsp = phsp.get_output_path() + + keys = ["KineticEnergy" ,"PreDirection_X","PreDirection_Y","EventPosition_X","EventPosition_Y"] + tols = [0.8, 0.8, 0.8, 0.8, 0.8] + br = "PhaseSpace;1" + is_ok = utility.compare_root3( + path_reference_root_phsp, + path_test_root_phsp, + br, + br, + keys, + keys, + tols, + None, + None, + paths.output / "test_EF_shaper40.png", + nb_bins=150, + hits_tol = 10**6 + ) + + + \ No newline at end of file From f126f9cf72cf5cc8d9f5a0ffa4065716382783ac Mon Sep 17 00:00:00 2001 From: Thomas BAUDIER Date: Thu, 20 Nov 2025 11:48:39 +0100 Subject: [PATCH 5/8] Use only one GateMaterials.db for ElectronFlash examples --- opengate/contrib/GateMaterials.db | 3 - .../ElectronFlash/utils/GateMaterials.db | 790 ------------------ 2 files changed, 793 deletions(-) delete mode 100644 opengate/contrib/linacs/ElectronFlash/utils/GateMaterials.db diff --git a/opengate/contrib/GateMaterials.db b/opengate/contrib/GateMaterials.db index 54246e4aa..70084cd89 100644 --- a/opengate/contrib/GateMaterials.db +++ b/opengate/contrib/GateMaterials.db @@ -80,9 +80,6 @@ Tecapeek: d=1.32 g/cm3 ; n=3; state=solid +el: name=Carbon ; f = 0.7619 +el: name=Oxygen ; f = 0.1905 -AluminiumEGS: d=2.702 g/cm3 ; n=1 ; state=solid - +el: name=Aluminium ; n=1 - Nickel: d=8.908 g/cm3 ; n=1 ; state=solid +el: name=auto ; n=1 diff --git a/opengate/contrib/linacs/ElectronFlash/utils/GateMaterials.db b/opengate/contrib/linacs/ElectronFlash/utils/GateMaterials.db deleted file mode 100644 index 24c246a47..000000000 --- a/opengate/contrib/linacs/ElectronFlash/utils/GateMaterials.db +++ /dev/null @@ -1,790 +0,0 @@ -[Elements] -Hydrogen: S= H ; Z= 1. ; A= 1.01 g/mole -Helium: S= He ; Z= 2. ; A= 4.003 g/mole -Lithium: S= Li ; Z= 3. ; A= 6.941 g/mole -Lithium6: S= Li6 ; Z= 3. ; A= 6.015 g/mole -Lithium7: S= Li7 ; Z= 3. ; A= 7.016 g/mole -Beryllium: S= Be ; Z= 4. ; A= 9.012 g/mole -Boron: S= B ; Z= 5. ; A= 10.811 g/mole -Boron10: S= B10 ; Z= 5. ; A= 10.013 g/mole -Carbon: S= C ; Z= 6. ; A= 12.01 g/mole -Nitrogen: S= N ; Z= 7. ; A= 14.01 g/mole -Oxygen: S= O ; Z= 8. ; A= 16.00 g/mole -Fluorine: S= F ; Z= 9. ; A= 18.998 g/mole -Neon: S= Ne ; Z= 10. ; A= 20.180 g/mole -Sodium: S= Na ; Z= 11. ; A= 22.99 g/mole -Magnesium: S= Mg ; Z= 12. ; A= 24.305 g/mole -Aluminium: S= Al ; Z= 13. ; A= 26.981539 g/mole -Silicon: S= Si ; Z= 14. ; A= 28.09 g/mole -Phosphor: S= P ; Z= 15. ; A= 30.97 g/mole -Sulfur: S= S ; Z= 16. ; A= 32.066 g/mole -Chlorine: S= Cl ; Z= 17. ; A= 35.45 g/mole -Argon: S= Ar ; Z= 18. ; A= 39.95 g/mole -Potassium: S= K ; Z= 19. ; A= 39.098 g/mole -Calcium: S= Ca ; Z= 20. ; A= 40.08 g/mole -Scandium: S= Sc ; Z= 21. ; A= 44.956 g/mole -Titanium: S= Ti ; Z= 22. ; A= 47.867 g/mole -Vanadium: S= V ; Z= 23. ; A= 50.942 g/mole -Chromium: S= Cr ; Z= 24. ; A= 51.996 g/mole -Manganese: S= Mn ; Z= 25. ; A= 54.938 g/mole -Iron: S= Fe ; Z= 26. ; A= 55.845 g/mole -Cobalt: S= Co ; Z= 27. ; A= 58.933 g/mole -Nickel: S= Ni ; Z= 28. ; A= 58.693 g/mole -Copper: S= Cu ; Z= 29. ; A= 63.39 g/mole -Zinc: S= Zn ; Z= 30. ; A= 65.39 g/mole -Gallium: S= Ga ; Z= 31. ; A= 69.723 g/mole -Germanium: S= Ge ; Z= 32. ; A= 72.61 g/mole -Arsenic: S= As ; Z= 33. ; A= 74.9 g/mole -Bromine: S= Br ; Z= 35. ; A= 79.90 g/mole -Rubidium: S= Rb ; Z= 37. ; A= 85.4678 g/mole -Strontium: S= Sr ; Z= 38. ; A= 87.62 g/mole -Yttrium: S= Y ; Z= 39. ; A= 88.91 g/mole -Zirconium: S= Zr ; Z= 40. ; A= 91.224 g/mole -Molybdenum: S= Mo ; Z= 42. ; A= 95.95 g/mole -Technetium: S= Tc ; Z= 43. ; A= 99.0 g/mole -Palladium: S= Pd ; Z= 46. ; A= 106.4 g/mole -Silver: S= Ag ; Z= 47. ; A= 107.868 g/mole -Cadmium: S= Cd ; Z= 48. ; A= 112.41 g/mole -Indium: S= In ; Z= 49. ; A= 114.8 g/mole -Tin: S= Sn ; Z= 50. ; A= 118.71 g/mole -Antimony: S= Sb ; Z= 51. ; A= 121.76 g/mole -Tellurium: S= Te ; Z= 52. ; A= 127.6 g/mole -Iodine: S= I ; Z= 53. ; A= 126.90 g/mole -Cesium: S= Cs ; Z= 55. ; A= 132.905 g/mole -Lanthanum: S= La ; Z= 57. ; A= 138.91 g/mole -Cerium: S= Ce ; Z= 58. ; A= 140.116 g/mole -Samarium: S= Sm ; Z= 62. ; A= 150.36 g/mole -Gadolinium: S= Gd ; Z= 64. ; A= 157.25 g/mole -Lutetium: S= Lu ; Z= 71. ; A= 174.97 g/mole -Tantalum: S= Ta ; Z= 73. ; A= 180.95 g/mole -Tungsten: S= W ; Z= 74. ; A= 183.84 g/mole -Gold: S= Au ; Z= 79. ; A= 196.966 g/mole -Thallium: S= Tl ; Z= 81. ; A= 204.37 g/mole -Lead: S= Pb ; Z= 82. ; A= 207.20 g/mole -Bismuth: S= Bi ; Z= 83. ; A= 208.98 g/mole -Uranium: S= U ; Z= 92. ; A= 238.03 g/mole - -[Materials] -Vacuum: d=0.000001 mg/cm3 ; n=1 - +el: name=Hydrogen ; n=1 - -Air: d=1.29 mg/cm3 ; n=4 ; state=gas - +el: name=Nitrogen ; f=0.755268 - +el: name=Oxygen ; f=0.231781 - +el: name=Argon ; f=0.012827 - +el: name=Carbon ; f=0.000124 - - -Tecapeek: d=1.32 g/cm3 ; n=3; state=solid - +el: name=Hydrogen; f = 0.0476 - +el: name=Carbon ; f = 0.7619 - +el: name=Oxygen ; f = 0.1905 - -Nickel: d=8.908 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Gold: d=19.3 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Carbon: d=2.1 g/cm3 ; n=1; state=solid - +el: name=auto ; n=1 - -Copper: d=8.96 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Aluminium: d=2.69890 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Titanium: d=4.54 g/cm3; n=1; state=solid - +el: name=auto ; n=1 - -Beryllium: d=1.85 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -AluminiumEGS: d=2.702 g/cm3 ; n=1 ; state=solid - +el: name=Aluminium ; n=1 - -Uranium: d=18.90 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Silicon: d=2.33 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Zinc: d=7.1 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Germanium: d=5.32 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Strontium: d=2.64 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Yttrium: d=4.47 g/cm3 ; n=1 - +el: name=auto ; n=1 - -Molybdenum: d=10.28 g/cm3 ; n=1 - +el: name=auto ; n=1 - -Gadolinium: d=7.9 g/cm3 ; n=1 - +el: name=auto ; n=1 - -Lutetium: d=9.84 g/cm3 ; n=1 - +el: name=auto ; n=1 - -Tantalum: d=16.65 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Tungsten: d=19.3 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Lead: d=11.4 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -Bismuth: d=9.75 g/cm3 ; n=1 ; state=solid - +el: name=auto ; n=1 - -NaI: d=3.67 g/cm3; n=2; state=solid - +el: name=Sodium ; n=1 - +el: name=Iodine ; n=1 - -NaITl: d=3.67 g/cm3; n=3; state=solid - +el: name=Sodium ; f=0.152 - +el: name=Iodine ; f=0.838 - +el: name=Thallium ; f=0.010 - -CsI: d=4.51 g/cm3; n=2; state=solid - +el: name=Cesium ; n=1 - +el: name=Iodine ; n=1 - -CsITl: d=4.51 g/cm3; n=3; state=solid - +el: name=Cesium ; f=0.511 - +el: name=Iodine ; f=0.488 - +el: name=Thallium ; f=7.86e-04 - -CsINa: d=4.51 g/cm3; n=3; state=solid - +el: name=Cesium ; f=0.511 - +el: name=Iodine ; f=0.488 - +el: name=Sodium ; f=1e-04 - -PWO: d=8.28 g/cm3; n=3 ; state=solid - +el: name=Lead ; n=1 - +el: name=Tungsten ; n=1 - +el: name=Oxygen ; n=4 - -BGO: d=7.13 g/cm3; n= 3 ; state=solid - +el: name=Bismuth ; n=4 - +el: name=Germanium ; n=3 - +el: name=Oxygen ; n=12 - -LSO: d=7.4 g/cm3; n=3 ; state=solid - +el: name=Lutetium ; n=2 - +el: name=Silicon ; n=1 - +el: name=Oxygen ; n=5 - -Plexiglass: d=1.19 g/cm3; n=3; state=solid - +el: name=Hydrogen ; f=0.080538 - +el: name=Carbon ; f=0.599848 - +el: name=Oxygen ; f=0.319614 - -GSO: d=6.7 g/cm3; n=3 ; state=solid - +el: name=Gadolinium ; n=2 - +el: name=Silicon ; n=1 - +el: name=Oxygen ; n=5 - -LuAP: d=8.34 g/cm3; n=3 ; state=solid - +el: name=Lutetium ; n=1 - +el: name=Aluminium ; n=1 - +el: name=Oxygen ; n=3 - -YAP: d=5.55 g/cm3; n=3 ; state=solid - +el: name=Yttrium ; n=1 - +el: name=Aluminium ; n=1 - +el: name=Oxygen ; n=3 - -Lucite: d=1.19 g/cm3; n=3; - +el: name=Hydrogen ; f=0.080538 - +el: name=Carbon ; f=0.599848 - +el: name=Oxygen ; f=0.319614 - -CarbonFiber: d=1.75 g/cm3; n=2; state=Solid - +el: name=Carbon ; f=0.977 - +el: name=Oxygen ; f=0.023 - -Water: d=1.00 g/cm3; n=2 ; state=liquid - +el: name=Hydrogen ; n=2 - +el: name=Oxygen ; n=1 - -Quartz: d=2.2 g/cm3; n=2 ; state=solid - +el: name=Silicon ; n=1 - +el: name=Oxygen ; n=2 - -Fe3O4: d=5.17 g/cm3; n=2; state=Solid - +el: name=Iron ; n=3 - +el: name=Oxygen ; n=4 - -Mylar: d=1.4 g/cm3; n=3; state=solid - +el: name=Hydrogen ; f=0.041958 - +el: name=Carbon ; f=0.625017 - +el: name=Oxygen ; f=0.333025 - -Kapton: d=1.42 g/cm3; n=4; state=solid - +el: name=Hydrogen ; f=0.026362 - +el: name=Carbon ; f=0.691133 - +el: name=Nitrogen ; f=0.073270 - +el: name=Oxygen ; f=0.209235 - -Nomex: d=0.95 g/cm3; n=4; state=solid - +el: name=Hydrogen ; n=10 - +el: name=Carbon ; n=14 - +el: name=Nitrogen ; n=2 - +el: name=Oxygen ; n=2 - -NitrogenGas: d=1.29 mg/cm3 ; n=1 ; state=gas - +el: name=Nitrogen ; f=1 - -Cerrotru: d=8.72 g/cm3; n=2 ; state=solid - +el: name=Bismuth ; f=0.58 - +el: name=Tin ; f=0.42 - -Breast: d=1.020 g/cm3 ; n = 8 - +el: name=Oxygen ; f=0.5270 - +el: name=Carbon ; f=0.3320 - +el: name=Hydrogen ; f=0.1060 - +el: name=Nitrogen ; f=0.0300 - +el: name=Sulfur ; f=0.0020 - +el: name=Sodium ; f=0.0010 - +el: name=Phosphor ; f=0.0010 - +el: name=Chlorine ; f=0.0010 - -Glass: d=2.5 g/cm3; n=4; state=solid - +el: name=Sodium ; f=0.1020 - +el: name=Calcium ; f=0.0510 - +el: name=Silicon ; f=0.2480 - +el: name=Oxygen ; f=0.5990 - -Glass_Pyrex: d=2.23 g/cm3; n=6; state=solid - +el: name=Boron ; f=0.040066 - +el: name=Oxygen ; f=0.539559 - +el: name=Sodium ; f=0.028191 - +el: name=Aluminium ; f=0.011644 - +el: name=Silicon ; f=0.377220 - +el: name=Potassium ; f=0.003321 - -Scinti-C9H10: d=1.032 g/cm3 ; n=2 - +el: name=Carbon ; n=9 - +el: name=Hydrogen ; n=10 - -LuYAP-70: d=7.1 g/cm3 ; n=4 - +el: name=Lutetium ; n=7 - +el: name=Yttrium ; n=3 - +el: name=Aluminium ; n=10 - +el: name=Oxygen ; n=30 - -LuYAP-80: d=7.5 g/cm3 ; n=4 - +el: name=Lutetium ; n=8 - +el: name=Yttrium ; n=2 - +el: name=Aluminium ; n=10 - +el: name=Oxygen ; n=30 - -Plastic: d=1.18 g/cm3 ; n=3; state=solid - +el: name=Carbon ; n=5 - +el: name=Hydrogen ; n=8 - +el: name=Oxygen ; n=2 - -Paraffine: d=0.8 g/cm3 ; n=3; state=solid - +el: name=Carbon ; n=5 - +el: name=Hydrogen ; n=8 - +el: name=Oxygen ; n=2 - -EJ230: d=1.023 g/cm3 ; n=2; state=solid - +el: name=Carbon ; n=10 - +el: name=Hydrogen ; n=11 - -HDPE: d=0.954 g/cm3 ; n=2; state=solid - +el: name=Carbon ; n=2 - +el: name=Hydrogen ; n=4 - -Biomimic: d=1.05 g/cm3 ; n=3; state=solid - +el: name=Carbon ; n=5 - +el: name=Hydrogen ; n=8 - +el: name=Oxygen ; n=2 - -FITC: d=1.0 g/cm3 ; n=1 - +el: name=Carbon ; n=1 - -RhB: d=1.0 g/cm3 ; n=1 - +el: name=Carbon ; n=1 - -CZT: d=5.68 g/cm3 ; n=3; state=solid - +el: name=Cadmium ; n=9 - +el: name=Zinc ; n=1 - +el: name=Tellurium ; n=10 - -Lung: d=0.26 g/cm3 ; n=9 - +el: name=Hydrogen ; f=0.103 - +el: name=Carbon ; f=0.105 - +el: name=Nitrogen ; f=0.031 - +el: name=Oxygen ; f=0.749 - +el: name=Sodium ; f=0.002 - +el: name=Phosphor ; f=0.002 - +el: name=Sulfur ; f=0.003 - +el: name=Chlorine ; f=0.003 - +el: name=Potassium ; f=0.002 - -Polyethylene: d=0.96 g/cm3 ; n=2 - +el: name=Hydrogen ; n=2 - +el: name=Carbon ; n=1 - -PVC: d=1.65 g/cm3 ; n=3 ; state=solid - +el: name=Hydrogen ; n=3 - +el: name=Carbon ; n=2 - +el: name=Chlorine ; n=1 - -SS304: d=7.92 g/cm3 ; n=4 ; state=solid - +el: name=Iron ; f=0.695 - +el: name=Chromium ; f=0.190 - +el: name=Nickel ; f=0.095 - +el: name=Manganese ; f=0.020 - -PTFE: d= 2.18 g/cm3 ; n=2 ; state=solid - +el: name=Carbon ; n=1 - +el: name=Fluorine ; n=2 - -LYSO: d=7.36 g/cm3; n=4 ; state=solid - +el: name=Lutetium ; f=0.714467891 - +el: name=Yttrium ; f=0.04033805 - +el: name=Silicon ; f=0.063714272 - +el: name=Oxygen ; f=0.181479788 - -LYSO-Ce-Hilger: d=7.10 g/cm3; n=5 ; state=solid - +el: name=Lutetium ; f=0.713838658203075 - +el: name=Yttrium ; f=0.040302477781781 - +el: name=Silicon ; f=0.063721807284236 - +el: name=Oxygen ; f=0.181501252152072 - +el: name=Cerium ; f=0.000635804578835201 - -LaBr3: d=5.06 g/cm3 ; n=2 - +el: name=Bromine ; n=3 - +el: name=Lanthanum ; n=1 - -Millipore: d=1.0 g/cm3 ; n=3 - +el: name=Hydrogen ; n=10 - +el: name=Carbon ; n=6 - +el: name=Oxygen ; n=5 - -PCB: d=1.20 g/cm3 ; n=3 - +el: name=Hydrogen ; n=8 - +el: name=Carbon ; n=5 - +el: name=Oxygen ; n=2 - -Body: d=1.00 g/cm3 ; n=2 - +el: name=Hydrogen ; f=0.112 - +el: name=Oxygen ; f=0.888 - -A150_Tissue_Plastic: d=1.127 g/cm3 ; n=6 - +el: name=Hydrogen ; f=0.101330 - +el: name=Carbon ; f=0.775498 - +el: name=Nitrogen ; f=0.035057 - +el: name=Oxygen ; f=0.052315 - +el: name=Fluorine ; f=0.017423 - +el: name=Calcium ; f=0.018377 - -Muscle: d=1.05 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.102 - +el: name=Carbon ; f=0.143 - +el: name=Nitrogen ; f=0.034 - +el: name=Oxygen ; f=0.71 - +el: name=Sodium ; f=0.001 - +el: name=Phosphor ; f=0.002 - +el: name=Sulfur ; f=0.003 - +el: name=Chlorine ; f=0.001 - +el: name=Potassium ; f=0.004 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -LungMoby: d=0.30 g/cm3 ; n=6 - +el: name=Hydrogen ; f=0.099 - +el: name=Carbon ; f=0.100 - +el: name=Nitrogen ; f=0.028 - +el: name=Oxygen ; f=0.740 - +el: name=Phosphor ; f=0.001 - +el: name=Calcium ; f=0.032 - -SpineBone: d=1.42 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.063 - +el: name=Carbon ; f=0.261 - +el: name=Nitrogen ; f=0.039 - +el: name=Oxygen ; f=0.436 - +el: name=Sodium ; f=0.001 - +el: name=Magnesium ; f=0.001 - +el: name=Phosphor ; f=0.061 - +el: name=Sulfur ; f=0.003 - +el: name=Chlorine ; f=0.001 - +el: name=Potassium ; f=0.001 - +el: name=Calcium ; f=0.133 - -RibBone: d=1.92 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.034 - +el: name=Carbon ; f=0.155 - +el: name=Nitrogen ; f=0.042 - +el: name=Oxygen ; f=0.435 - +el: name=Sodium ; f=0.001 - +el: name=Magnesium ; f=0.002 - +el: name=Phosphor ; f=0.103 - +el: name=Sulfur ; f=0.003 - +el: name=Calcium ; f=0.225 - +el: name=Scandium ; f=0.0 - +el: name=Titanium ; f=0.0 - -Epidermis: d=0.92 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.120 - +el: name=Carbon ; f=0.640 - +el: name=Nitrogen ; f=0.008 - +el: name=Oxygen ; f=0.229 - +el: name=Phosphor ; f=0.002 - +el: name=Calcium ; f=0.001 - +el: name=Scandium ; f=0.0 - +el: name=Titanium ; f=0.0 - +el: name=Vanadium ; f=0.0 - +el: name=Chromium ; f=0.0 - +el: name=Manganese ; f=0.0 - -Hypodermis: d=0.92 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.120 - +el: name=Carbon ; f=0.640 - +el: name=Nitrogen ; f=0.008 - +el: name=Oxygen ; f=0.229 - +el: name=Phosphor ; f=0.002 - +el: name=Calcium ; f=0.001 - +el: name=Scandium ; f=0.0 - +el: name=Titanium ; f=0.0 - +el: name=Vanadium ; f=0.0 - +el: name=Chromium ; f=0.0 - +el: name=Manganese ; f=0.0 - -Blood: d=1.06 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.102 - +el: name=Carbon ; f=0.11 - +el: name=Nitrogen ; f=0.033 - +el: name=Oxygen ; f=0.745 - +el: name=Sodium ; f=0.001 - +el: name=Phosphor ; f=0.001 - +el: name=Sulfur ; f=0.002 - +el: name=Chlorine ; f=0.003 - +el: name=Potassium ; f=0.002 - +el: name=Iron ; f=0.001 - +el: name=Cobalt ; f=0.0 - -Heart: d=1.05 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.104 - +el: name=Carbon ; f=0.139 - +el: name=Nitrogen ; f=0.029 - +el: name=Oxygen ; f=0.718 - +el: name=Sodium ; f=0.001 - +el: name=Phosphor ; f=0.002 - +el: name=Sulfur ; f=0.002 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.003 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Kidney: d=1.05 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.103 - +el: name=Carbon ; f=0.132 - +el: name=Nitrogen ; f=0.03 - +el: name=Oxygen ; f=0.724 - +el: name=Sodium ; f=0.002 - +el: name=Phosphor ; f=0.002 - +el: name=Sulfur ; f=0.002 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.002 - +el: name=Calcium ; f=0.001 - +el: name=Scandium ; f=0.0 - -Liver: d=1.06 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.102 - +el: name=Carbon ; f=0.139 - +el: name=Nitrogen ; f=0.03 - +el: name=Oxygen ; f=0.716 - +el: name=Sodium ; f=0.002 - +el: name=Phosphor ; f=0.003 - +el: name=Sulfur ; f=0.003 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.003 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Lymph: d=1.03 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.108 - +el: name=Carbon ; f=0.041 - +el: name=Nitrogen ; f=0.011 - +el: name=Oxygen ; f=0.832 - +el: name=Sodium ; f=0.003 - +el: name=Sulfur ; f=0.001 - +el: name=Chlorine ; f=0.004 - +el: name=Argon ; f=0.0 - +el: name=Potassium ; f=0.0 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Pancreas: d=1.04 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.106 - +el: name=Carbon ; f=0.169 - +el: name=Nitrogen ; f=0.022 - +el: name=Oxygen ; f=0.694 - +el: name=Sodium ; f=0.002 - +el: name=Phosphor ; f=0.002 - +el: name=Sulfur ; f=0.001 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.002 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Intestine: d=1.03 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.106 - +el: name=Carbon ; f=0.115 - +el: name=Nitrogen ; f=0.022 - +el: name=Oxygen ; f=0.751 - +el: name=Sodium ; f=0.001 - +el: name=Phosphor ; f=0.001 - +el: name=Sulfur ; f=0.001 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.001 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Skull: d=1.61 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.05 - +el: name=Carbon ; f=0.212 - +el: name=Nitrogen ; f=0.04 - +el: name=Oxygen ; f=0.435 - +el: name=Sodium ; f=0.001 - +el: name=Magnesium ; f=0.002 - +el: name=Phosphor ; f=0.081 - +el: name=Sulfur ; f=0.003 - +el: name=Calcium ; f=0.176 - +el: name=Scandium ; f=0.0 - +el: name=Titanium ; f=0.0 - -Cartilage: d=1.10 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.096 - +el: name=Carbon ; f=0.099 - +el: name=Nitrogen ; f=0.022 - +el: name=Oxygen ; f=0.744 - +el: name=Sodium ; f=0.005 - +el: name=Phosphor ; f=0.022 - +el: name=Sulfur ; f=0.009 - +el: name=Chlorine ; f=0.003 - +el: name=Argon ; f=0.0 - +el: name=Potassium ; f=0.0 - +el: name=Calcium ; f=0.0 - -Brain: d=1.04 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.107 - +el: name=Carbon ; f=0.145 - +el: name=Nitrogen ; f=0.022 - +el: name=Oxygen ; f=0.712 - +el: name=Sodium ; f=0.002 - +el: name=Phosphor ; f=0.004 - +el: name=Sulfur ; f=0.002 - +el: name=Chlorine ; f=0.003 - +el: name=Potassium ; f=0.003 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Spleen: d=1.06 g/cm3 ; n=11 - +el: name=Hydrogen ; f=0.103 - +el: name=Carbon ; f=0.113 - +el: name=Nitrogen ; f=0.032 - +el: name=Oxygen ; f=0.741 - +el: name=Sodium ; f=0.001 - +el: name=Phosphor ; f=0.003 - +el: name=Sulfur ; f=0.002 - +el: name=Chlorine ; f=0.002 - +el: name=Potassium ; f=0.003 - +el: name=Calcium ; f=0.0 - +el: name=Scandium ; f=0.0 - -Testis: d=1.04 g/cm3 ; n=9 - +el: name=Hydrogen ; f=0.106000 - +el: name=Carbon ; f=0.099000 - +el: name=Nitrogen ; f=0.020000 - +el: name=Oxygen ; f=0.766000 - +el: name=Sodium ; f=0.002000 - +el: name=Phosphor ; f=0.001000 - +el: name=Sulfur ; f=0.002000 - +el: name=Chlorine ; f=0.002000 - +el: name=Potassium ; f=0.002000 - -AirBodyInterface: d=0.296 g/cm3 ; n=15 - +el: name=Hydrogen ; f=0.10134 - +el: name=Carbon ; f=0.10238 - +el: name=Nitrogen ; f=0.02866 - +el: name=Oxygen ; f=0.75752 - +el: name=Sodium ; f=0.00184 - +el: name=Magnesium ; f=0.00007 - +el: name=Silicon ; f=0.00006 - +el: name=Phosphor ; f=0.00080 - +el: name=Sulfur ; f=0.00225 - +el: name=Chlorine ; f=0.00266 - +el: name=Potassium ; f=0.00194 - +el: name=Calcium ; f=0.00009 - +el: name=Iron ; f=0.00037 - +el: name=Zinc ; f=0.00001 - +el: name=Rubidium ; f=0.00001 - -Bones: d=1.4 g/cm3 ; n=18 - +el: name=Hydrogen ; f=0.07337 - +el: name=Carbon ; f=0.25475 - +el: name=Nitrogen ; f=0.03057 - +el: name=Oxygen ; f=0.47893 - +el: name=Fluorine ; f=0.00025 - +el: name=Sodium ; f=0.00326 - +el: name=Magnesium ; f=0.00112 - +el: name=Silicon ; f=0.00002 - +el: name=Phosphor ; f=0.05095 - +el: name=Sulfur ; f=0.00173 - +el: name=Chlorine ; f=0.00143 - +el: name=Potassium ; f=0.00153 - +el: name=Calcium ; f=0.10190 - +el: name=Iron ; f=0.00008 - +el: name=Zinc ; f=0.00005 - +el: name=Rubidium ; f=0.00002 - +el: name=Strontium ; f=0.00003 - +el: name=Lead ; f=0.00001 - -PE: d=0.93 g/cm3 ; n=2 - +el: name=Hydrogen ; n=2 - +el: name=Carbon ; n=1 - -CdTe: d=5.85 g/cm3 ; n=2; state=solid - +el: name=Cadmium ; f=0.468358 - +el: name=Tellurium ; f=0.531642 - -PMMA: d=1.19 g/cm3 ; n=3 ; state=solid - +el: name=Hydrogen ; f=0.080538 - +el: name=Carbon ; f=0.599848 - +el: name=Oxygen ; f=0.319614 - -Epoxy: d=1.0 g/cm3; n=3; state=solid - +el: name=Carbon ; n=1 - +el: name=Hydrogen ; n=1 - +el: name=Oxygen ; n=1 - -Carbide: d=15.8 g/cm3; n=2 ; state=solid - +el: name=Tungsten ; n=1 - +el: name=Carbon ; n=1 - -ABS: d=1.10 g/cm3; n=3 ; state=solid - +el: name=Hydrogen ; n=17 - +el: name=Carbon ; n=15 - +el: name=Nitrogen ; n=1 - -FR4_epoxy: d=1.85 g/cm3; n=4; state=solid - +el: name=Carbon ; f=0.6419 - +el: name=Hydrogen ; f=0.0643 - +el: name=Oxygen ; f=0.2036 - +el: name=Chlorine ; f=0.0902 - -FR4: d=1.85 g/cm3; n=11; state=solid - +el: name=Silicon ; f=0.15 - +el: name=Oxygen ; f=0.36 - +el: name=Calcium ; f=0.08 - +el: name=Aluminium ; f=0.04 - +el: name=Magnesium ; f=0.01 - +el: name=Boron ; f=0.01 - +el: name=Potassium ; f=0.01 - +el: name=Carbon ; f=0.27 - +el: name=Hydrogen ; f=0.03 - +el: name=Nitrogen ; f=0.03 - +el: name=Sodium ; f=0.01 - -FR4_Copper: d=2.2998 g/cm3; n=4; state=solid - +el: name=Gold ; f=0.01 - +el: name=Nickel ; f=0.02 - +el: name=Copper ; f=0.21 - +mat: name=FR4 ; f=0.76 - -FR4_Copper_epoxy: d=2.2998 g/cm3; n=7; state=solid - +el: name=Gold ; f=0.0131 - +el: name=Nickel ; f=0.0182 - +el: name=Copper ; f=0.2134 - +el: name=Carbon ; f=0.4848 - +el: name=Hydrogen ; f=0.0486 - +el: name=Oxygen ; f=0.1538 - +el: name=Chlorine ; f=0.0681 - -Gypsum: d=2.32 g/cm3 ; n=4 ; state=solid - +el: name=Hydrogen ; f=0.023416 - +el: name=Oxygen ; f=0.557572 - +el: name=Sulfur ; f=0.186215 - +el: name=Calcium ; f=0.232797 - -Concrete: d=2.35 g/cm3 ; n=10 ; state=solid - +el: name=Hydrogen ; f=0.0527 - +el: name=Oxygen ; f=0.4746 - +el: name=Sodium ; f=0.0162 - +el: name=Magnesium ; f=0.0024 - +el: name=Aluminium ; f=0.0433 - +el: name=Silicon ; f=0.3008 - +el: name=Sulfur ; f=0.0013 - +el: name=Potassium ; f=0.0182 - +el: name=Calcium ; f=0.0787 - +el: name=Iron ; f=0.0118 - -Concrete2: d=3.50 g/cm3 ; n=10 ; state=solid - +el: name=Hydrogen ; f=0.010000 - +el: name=Carbon ; f=0.001000 - +el: name=Oxygen ; f=0.529107 - +el: name=Sodium ; f=0.016000 - +el: name=Magnesium ; f=0.002000 - +el: name=Aluminium ; f=0.033872 - +el: name=Silicon ; f=0.337021 - +el: name=Potassium ; f=0.013000 - +el: name=Calcium ; f=0.044000 - +el: name=Iron ; f=0.014000 - -MRGEL: d=1.04 g/cm3; n=5 ; state=solid - +el: name=Hydrogen ; f=0.104161105 - +el: name=Carbon ; f=0.095211486 - +el: name=Nitrogen ; f=0.016995 - +el: name=Oxygen ; f=0.783004584 - +el: name=Sulfur ; f=0.000627825 - -Pyrex66: d=1.478 g/cm3 ; n=6 - +el: name=Oxygen ; f=0.5386 - +el: name=Silicon ; f=0.3768 - +el: name=Sodium ; f=0.0297 - +el: name=Phosphor ; f=0.0042 - +el: name=Boron ; f=0.0401 - +el: name=Aluminium ; f=0.0106 - -Steel: d=7.86 g/cm3; n=3; state=solid - +el: name=Carbon ; f=0.002 - +el: name=Manganese ; f=0.005 - +el: name=Iron ; f=0.993 - -GlassFiber: d=2.6 g/cm3; n=3; state=solid - +el: name=Hydrogen ; f=0.080538 - +el: name=Carbon ; f=0.599848 - +el: name=Oxygen ; f=0.319614 - -Polystyrene: d=0.05 g/cm3 ; n=2 ; state=solid - +el: name=Hydrogen ; n=8 - +el: name=Carbon ; n=8 - -LeadSb: d=11.16 g/cm3; n=2; state=solid - +el: name=Lead ; f=0.95 - +el: name=Antimony ; f=0.05 - -TiO2: d=4.23 g/cm3; n=2; state=solid - +el: name=Titanium ; f=0.5993 - +el: name=Oxygen ; f=0.4007 - -TiO: d=4.24 g/cm3; n=2; state=solid - +el: name=Titanium ; n=1 - +el: name=Oxygen ; n=1 - -Water_Catphan_LowD: d=0.85 g/cm3; n=2 ; state=liquid - +el: name=Hydrogen ; n=2 - +el: name=Oxygen ; n=1 \ No newline at end of file From 4be8a72a0724a4aa3448d7e06ed11b800ae97460 Mon Sep 17 00:00:00 2001 From: Thomas BAUDIER Date: Thu, 20 Nov 2025 15:59:02 +0100 Subject: [PATCH 6/8] Update electron flash Rename without capital letters Use test097_electron_flash_linac_helper.py to avoid code duplication Add data --- .../linacs/ElectronFlash/EF_function.py | 607 ------------ .../ElectronFlash/simulation_standard.py | 132 --- .../ElectronFlash/utils/spectra/9Mev_old.txt | 80 -- .../contrib/linacs/electron_flash/__init__.py | 0 .../linacs/electron_flash/electron_flash.py | 916 ++++++++++++++++++ .../utils/spectra/9Mev.txt | 0 opengate/tests/data | 2 +- .../test097_electron_flash_linac_helper.py | 178 ++++ .../test097a_electron_flash_linac.py | 30 + .../test097b_electron_flash_linac.py | 30 + .../test097c_electron_flash_linac.py | 27 + .../test097d_electron_flash_linac.py | 27 + .../test097e_electron_flash_linac.py} | 28 +- .../test097f_electron_flash_linac.py | 24 + .../test097g_electron_flash_linac.py | 24 + .../test097h_electron_flash_linac.py | 24 + .../test097i_electron_flash_linac.py | 24 + .../tests/src/test095_ElectronFlash_linac.py | 111 --- .../tests/src/test096_ElectronFlash_linac.py | 123 --- .../tests/src/test097_ElectronFlash_linac.py | 118 --- .../tests/src/test098_ElectronFlash_linac.py | 114 --- .../tests/src/test100_ElectronFlash_linac.py | 114 --- .../tests/src/test101_ElectronFlash_linac.py | 115 --- .../tests/src/test102_ElectronFlash_linac.py | 117 --- .../tests/src/test103_ElectronFlash_linac.py | 116 --- 25 files changed, 1319 insertions(+), 1762 deletions(-) delete mode 100644 opengate/contrib/linacs/ElectronFlash/EF_function.py delete mode 100644 opengate/contrib/linacs/ElectronFlash/simulation_standard.py delete mode 100644 opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev_old.txt create mode 100644 opengate/contrib/linacs/electron_flash/__init__.py create mode 100644 opengate/contrib/linacs/electron_flash/electron_flash.py rename opengate/contrib/linacs/{ElectronFlash => electron_flash}/utils/spectra/9Mev.txt (100%) create mode 100644 opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py create mode 100644 opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py create mode 100644 opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py create mode 100644 opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py create mode 100644 opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py rename opengate/tests/src/{test099_ElectronFlash_linac_geometry.py => advanced_tests/electron_flash/test097e_electron_flash_linac.py} (67%) create mode 100644 opengate/tests/src/advanced_tests/electron_flash/test097f_electron_flash_linac.py create mode 100644 opengate/tests/src/advanced_tests/electron_flash/test097g_electron_flash_linac.py create mode 100644 opengate/tests/src/advanced_tests/electron_flash/test097h_electron_flash_linac.py create mode 100644 opengate/tests/src/advanced_tests/electron_flash/test097i_electron_flash_linac.py delete mode 100644 opengate/tests/src/test095_ElectronFlash_linac.py delete mode 100644 opengate/tests/src/test096_ElectronFlash_linac.py delete mode 100644 opengate/tests/src/test097_ElectronFlash_linac.py delete mode 100644 opengate/tests/src/test098_ElectronFlash_linac.py delete mode 100644 opengate/tests/src/test100_ElectronFlash_linac.py delete mode 100644 opengate/tests/src/test101_ElectronFlash_linac.py delete mode 100644 opengate/tests/src/test102_ElectronFlash_linac.py delete mode 100644 opengate/tests/src/test103_ElectronFlash_linac.py diff --git a/opengate/contrib/linacs/ElectronFlash/EF_function.py b/opengate/contrib/linacs/ElectronFlash/EF_function.py deleted file mode 100644 index 349478443..000000000 --- a/opengate/contrib/linacs/ElectronFlash/EF_function.py +++ /dev/null @@ -1,607 +0,0 @@ -import opengate as gate -import numpy as np -import os, sys, logging -from scipy.spatial.transform import Rotation as R -import SimpleITK as sitk - - - -logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) - -cm = gate.g4_units.cm -mm = gate.g4_units.mm -MeV = gate.g4_units.MeV -deg = gate.g4_units.deg - - -material_colors = { - "Titanium" : [0.2, 0.2, 0.2, 0.6], - "Tecapeek" : [1.0, 0.6, 0.2, 0.6], - "Air" : [1.0, 0.6, 0.2, 0.6], - "AluminiumEGS": [0.8, 0.8, 0.8, 0.6], - "Tungsten" : [0.8, 0.8, 0.8, 0.6], - "PMMA" : [0.0, 0.6, 0.0, 0.5], - "Water" : [0.0, 0.0, 0.5, 0.5], - } - - -def add_tubs(sim, name, mother, rmin, rmax, height, material, translation, material_colors = None): - """ - This function creates and adds a cylindrical (Tubs) volume to the geometry. - - Args: - sim: The simulation object where the volume will be added. - name (str): Name of the volume. - mother (str): The name of the mother volume. - rmin (float): Inner radius of the tube in millimeters. - rmax (float): Outer radius of the tube in millimeters. - height (float): Full height of the tube in millimeters. - material (str): Material assigned to the volume. - translation (tuple): A 3-element tuple representing the (x, y, z) translation of the volume. - material_colors (dict): A dictionary mapping material names to RGB color strings or tuples. - - Returns: - The created volume object. - - """ - vol = sim.add_volume("Tubs", name) - vol.mother = mother - vol.rmin = rmin * mm - vol.rmax = rmax * mm - vol.dz = height * mm / 2 - vol.material = material - vol.translation = translation - if material_colors and material in material_colors: - vol.color = material_colors[material] - return vol - - -def add_cons(sim, name, mother, rmin1, rmax1, rmin2, rmax2, height, material, translation, material_colors = None): - """ - This function creates and adds a conical (Cons) volume to the geometry. - - Args: - sim: The simulation object where the volume will be added. - name (str): Name of the volume. - mother (str): The name of the mother volume. - rmin1 (float): Inner radius at the -Z end in millimeters. - rmax1 (float): Outer radius at the -Z end in millimeters. - rmin2 (float): Inner radius at the +Z end in millimeters. - rmax2 (float): Outer radius at the +Z end in millimeters. - height (float): Full height of the cone in millimeters. - material (str): Material assigned to the volume. - translation (tuple): A 3-element tuple representing the (x, y, z) translation of the volume. - material_colors (dict, optional): A dictionary mapping material names to RGB color strings or tuples. - - Returns: - The created volume object. - """ - vol = sim.add_volume("Cons", name) - vol.mother = mother - vol.rmin1 = rmin1 * mm - vol.rmax1 = rmax1 * mm - vol.rmin2 = rmin2 * mm - vol.rmax2 = rmax2 * mm - vol.dz = height / 2 - vol.material = material - vol.translation = translation - vol.sphi = 0 * deg - vol.dphi = 360 * deg - if material_colors and material in material_colors: - vol.color = material_colors[material] - return vol - -def add_box(sim, name, mother, lx, ly, lz, material, translation, material_colors = None): - """ - This function creates and adds a rectangular (Box) volume to the geometry. - - Args: - sim: The simulation object where the volume will be added. - name (str): Name of the volume. - mother (str): The name of the mother volume. - lx (float): Length of the box along the X-axis in millimeters. - ly (float): Length of the box along the Y-axis in millimeters. - lz (float): Length of the box along the Z-axis in millimeters. - material (str): Material assigned to the volume. - translation (tuple): A 3-element tuple representing the (x, y, z) translation of the volume. - material_colors (dict, optional): A dictionary mapping material names to RGB color strings or tuples. - - Returns: - The created volume object. - """ - vol = sim.add_volume("Box", name) - vol.mother = mother - vol.size = [lx, ly, lz] - vol.material = material - vol.translation = translation - if material_colors and material in material_colors: - vol.color = material_colors[material] - return vol - - -def get_object_zdimension(components, center_z): - """ - Computes the Z-dimension range (start and end Z coordinates) of the whole structure (made up of components). - - Args: - components (list): A list of component tuples. - center_z (float): Global Z-axis offset to apply to all components. - - Returns: - tuple: (start_z, end_z) in millimeters, representing the total Z-extent. - """ - get_z_bounds = lambda comp: (comp[-1][2] - comp[-3] / 2 + center_z, comp[-1][2] + comp[-3] / 2+center_z) - - start_z, _ = get_z_bounds(components[0]) - _, end_z = get_z_bounds(components[-1]) - return start_z, end_z - -def build_ElectronFlash(sim,center_x=0, center_y=0, center_z=0, material_colors=None): - """ - This function builds the full ElectronFlash linear accelerator (linac) geometry, starting from the titanium window up to the beginning of the BLD. - - Args: - sim: The simulation object where volumes will be added. - center_x (float): Offset in millimeters along the X-axis for the titanium window. - center_y (float): Offset in millimeters along the Y-axis for the titanium window. - center_z (float): Offset in millimeters along the Z-axis for the titanium window. - material_colors (dict, optional): A dictionary mapping material names to RGB color strings or tuples. - - Returns: - float: The Z-coordinate (in mm) of the end of the linac structure, useful when other components has to be added. - """ - if material_colors is None: - material_colors = {} - - def offset(mother, pos): - if mother == "world": - return [ - pos[0] + center_x, - pos[1] + center_y, - pos[2] + center_z - ] - return pos - - logger.info(f"Building ElectronFlash linac structure with center offset: X={center_x} mm, Y={center_y} mm, Z={center_z} mm") - - components = [ - # (func, name, mother, args..., material, translation) - (add_tubs,sim, "tit_window", "world", 0, 18.2, 0.055, "Titanium", [0, 0, 0.0275*mm]), - (add_tubs,sim, "cyl1", "world", 0, 34.6, 12.8, "Tecapeek", [0, 0, 6.455*mm]), - (add_cons,sim, "hollow_cone", "cyl1", 0, 7, 0, 19, 12.8, "Air", [0, 0, 0]), - (add_tubs,sim, "st_0", "world", 15, 34.6, 5, "Tecapeek", [0, 0, 15.355*mm]), - (add_tubs,sim, "st_1", "world", 15, 22, 14, "Tecapeek", [0, 0, 24.855*mm]), - (add_tubs,sim, "st_2", "world", 20, 24, 132, "Tecapeek", [0, 0, 97.855*mm]), - (add_tubs,sim, "st_3", "world", 31, 37, 35, "Tecapeek", [0, 0, 156.755*mm]), - (add_tubs,sim, "st_4", "world", 22.5, 37, 73.5, "Tecapeek", [0, 0, 211.005*mm]), - (add_tubs,sim, "st_5", "world", 37, 130.8, 20, "AluminiumEGS", [0, 0, 211.005*mm]), - (add_tubs,sim, "cyl6", "world", 0, 48, 69, "Tecapeek", [0, 0, 282.255*mm]), - (add_cons,sim, "hollow_cone1", "cyl6", 0, 22.5, 0, 31.5, 69, "Air", [0, 0, 0]), - (add_tubs,sim, "st_6", "world", 48, 72, 16, "Tecapeek", [0, 0, 255.755*mm]), - (add_tubs,sim, "st_7", "world", 48, 72, 29, "Tecapeek", [0, 0, 302.255*mm]), - - ] - - for comp in components: - func = comp[0] - sim = comp[1] - name = comp[2] - mother = comp[3] - *args, material, translation = comp[4:] - - func( - sim, - name, - mother, - *args, - material, - offset(mother, translation), - material_colors=material_colors - ) - start_z, end_z = get_object_zdimension(components, center_z) - - logger.info(f"Linac structure range: start at {start_z:.3f} mm, end at {end_z:.3f} mm") - return end_z - -def build_passive_collimation(sim, string_name, center_x=0, center_y=0, center_z=0, material_colors=None): - """ - Builds a passive collimation structure. - - Args: - sim: The simulation object where volumes are added. - string_name (str): Identifier for the collimator type. Allowed values are: - - 'app100' - - 'app40' - - 'app70' - - 'shaper40' - - 'mb_40_holes_11' - - 'mb_40_slit_11' - center_x (float, optional): X-axis offset (in mm) applied to all components. Defaults to 0. - center_y (float, optional): Y-axis offset (in mm) applied to all components. Defaults to 0. - center_z (float, optional): Z-axis offset (in mm) applied to all components. Defaults to 0. - material_colors (dict, optional): Dictionary mapping material names to colors. - - Raises: - ValueError: If `string_name` is not one of the allowed applicator types. - - Returns: - float: The Z-coordinate (in mm) of the end position of the constructed collimator structure. - """ - if material_colors is None: - material_colors = {} - - allowed_names = ['app100', 'app40', 'app70', 'shaper40', 'mb_40_holes_11', 'mb_40_slit_11'] - if string_name not in allowed_names: - raise ValueError(f"[ERROR] Unsupported applicator type: '{string_name}'. Allowed applicators are: {allowed_names}") - - def offset(mother, pos): - if mother == "world": - return [ - pos[0] + center_x, - pos[1] + center_y, - pos[2] + center_z - ] - return pos - - if string_name == 'app100': - components = [ - (add_tubs, sim, "cyl7", "world", 0, 72, 42.8, "PMMA", [0, 0, 21.4]), - (add_tubs, sim, "hollow_cyl", "cyl7", 0, 31.5, 10, "Air", [0, 0, -16.4*mm]), - (add_cons, sim, "hollow_cone2","cyl7", 0, 31.5, 0, 50, 32.8, "Air", [0, 0, 5*mm]), - (add_tubs, sim, "st_8", "world", 55, 72, 14.8, "PMMA", [0, 0, 50.2*mm]), - (add_tubs, sim, "app100", "world", 50, 55, 742, "PMMA", [0, 0, 413.8*mm]), - ] - elif string_name == 'app40': - components = [ - (add_tubs, sim, "cyl7", "world", 0, 72, 42.8, "PMMA", [0, 0, 21.4]), - (add_cons, sim, "hollow_cone2","cyl7", 0, 31.5, 0, 20, 42.8, "Air", [0, 0, 0]), - (add_tubs, sim, "st_8", "world", 25, 72, 10, "PMMA", [0, 0, 47.8*mm]), - (add_tubs, sim, "app40", "world", 20, 25, 357, "PMMA", [0, 0, 231.3*mm]), - ] - elif string_name == 'shaper40': - components = [ - (add_tubs, sim, "cyl_s", "world", 25.2, 26, 50, "PMMA", [0, 0, -28]), - (add_tubs, sim, "cyl_p", "world", 25.2, 75, 5, "PMMA", [0, 0, -0.5]), - ] - leaf_defs = [ - ("leaf1", [12.5, 0, 3.5]), - ("leaf2", [-12.5, 0, 3.5]), - ("leaf3", [0, 12.5, 8.5]), - ("leaf4", [0, -12.5, 8.5]), - ] - leaf_components = [ - (add_box, sim, name, "world", - 25 if name in ['leaf1', 'leaf2'] else 50, - 50 if name in ['leaf1', 'leaf2'] else 25, - 3, "Tungsten", pos) - for name, pos in leaf_defs - ] - final_components = components + leaf_components - elif string_name == 'mb_40_holes_11': - positions = [[x, y, 0] for x in [-4, -2, 0, 2, 4] for y in [-4, -2, 0, 2, 4]] - - components = [ - (add_tubs, sim, "cyl_mb1", "world", 25.2, 40, 25, "Tecapeek", [0, 0, -12.5]), #338.155 - (add_box , sim, "slab1", "world", 5 , 50, 2.5, "Tecapeek", [22.5, 0, 3.75]), - (add_box , sim, "slab2", "world", 5 , 50, 2.5, "Tecapeek", [-22.5, 0, 3.75]), - (add_box , sim, "slab3", "world", 40 , 5, 2.5, "Tecapeek", [0, -22.5, 3.75]), - ] - elif string_name == 'mb_40_slit_11': - positions = [[x, 0, 0] for x in [-4, -2, 0, 2, 4]] - - components = [ - (add_tubs, sim, "cyl_mb1", "world", 25.2, 40, 25, "Tecapeek", [0, 0, -12.5]), #338.155 - #(add_box , sim, "plate1", "world", 50 , 50, 2.5, "Tungsten", [0, 0, 1.25]), - #(add_box , sim, "plate2", "world", 40 , 40, 2.5, "Tungsten", [0, 0, 3.75]), - (add_box , sim, "slab1", "world", 5 , 50, 2.5, "Tecapeek", [22.5, 0, 3.75]), - (add_box , sim, "slab2", "world", 5 , 50, 2.5, "Tecapeek", [-22.5, 0, 3.75]), - (add_box , sim, "slab3", "world", 40 , 5, 2.5, "Tecapeek", [0, -22.5, 3.75]), - ] - - for comp in components: - func = comp[0] - sim = comp[1] - name = comp[2] - mother = comp[3] - *args, material, translation = comp[4:] - - func( - sim, - name, - mother, - *args, - material, - offset(mother, translation), - material_colors=material_colors - ) - - if string_name == 'shaper40': - leaf_objects = [] - for name, pos in leaf_defs: - obj = add_box(sim, name, "world", 25 if "leaf1" in name or "leaf2" in name else 50, - 50 if "leaf1" in name or "leaf2" in name else 25, - 3, "Tungsten", offset("world", pos), material_colors=material_colors) - leaf_objects.append(obj) - - if string_name == 'mb_40_holes_11': - plate_1 = add_box( sim, "plate1", "world", 50 , 50, 2.5, "Tungsten", offset("world", [0, 0, 1.25]) ) - plate_2 = add_box( sim, "plate2", "world", 40 , 40, 2.5, "Tungsten", offset("world", [0, 0, 3.75]) ) - - hole1_1 = add_box(sim, "hole1_1", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[0]) - hole1_2 = add_box(sim, "hole1_2", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[1]) - hole1_3 = add_box(sim, "hole1_3", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[2]) - hole1_4 = add_box(sim, "hole1_4", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[3]) - hole1_5 = add_box(sim, "hole1_5", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[4]) - hole1_6 = add_box(sim, "hole1_6", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[5]) - hole1_7 = add_box(sim, "hole1_7", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[6]) - hole1_8 = add_box(sim, "hole1_8", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[7]) - hole1_9 = add_box(sim, "hole1_9", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[8]) - hole1_10 = add_box(sim, "hole1_10", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[9]) - hole1_11 = add_box(sim, "hole1_11", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[10]) - hole1_12 = add_box(sim, "hole1_12", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[11]) - hole1_13 = add_box(sim, "hole1_13", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[12]) - hole1_14 = add_box(sim, "hole1_14", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[13]) - hole1_15 = add_box(sim, "hole1_15", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[14]) - hole1_16 = add_box(sim, "hole1_16", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[15]) - hole1_17 = add_box(sim, "hole1_17", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[16]) - hole1_18 = add_box(sim, "hole1_18", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[17]) - hole1_19 = add_box(sim, "hole1_19", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[18]) - hole1_20 = add_box(sim, "hole1_20", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[19]) - hole1_21 = add_box(sim, "hole1_21", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[20]) - hole1_22 = add_box(sim, "hole1_22", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[21]) - hole1_23 = add_box(sim, "hole1_23", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[22]) - hole1_24 = add_box(sim, "hole1_24", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[23]) - hole1_25 = add_box(sim, "hole1_25", plate_1, 1*mm, 1*mm, 2.5*mm, "Air", positions[24]) - hole2_1 = add_box(sim, "hole2_1", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[0]) - hole2_2 = add_box(sim, "hole2_2", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[1]) - hole2_3 = add_box(sim, "hole2_3", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[2]) - hole2_4 = add_box(sim, "hole2_4", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[3]) - hole2_5 = add_box(sim, "hole2_5", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[4]) - hole2_6 = add_box(sim, "hole2_6", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[5]) - hole2_7 = add_box(sim, "hole2_7", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[6]) - hole2_8 = add_box(sim, "hole2_8", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[7]) - hole2_9 = add_box(sim, "hole2_9", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[8]) - hole2_10 = add_box(sim, "hole2_10", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[9]) - hole2_11 = add_box(sim, "hole2_11", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[10]) - hole2_12 = add_box(sim, "hole2_12", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[11]) - hole2_13 = add_box(sim, "hole2_13", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[12]) - hole2_14 = add_box(sim, "hole2_14", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[13]) - hole2_15 = add_box(sim, "hole2_15", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[14]) - hole2_16 = add_box(sim, "hole2_16", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[15]) - hole2_17 = add_box(sim, "hole2_17", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[16]) - hole2_18 = add_box(sim, "hole2_18", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[17]) - hole2_19 = add_box(sim, "hole2_19", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[18]) - hole2_20 = add_box(sim, "hole2_20", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[19]) - hole2_21 = add_box(sim, "hole2_21", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[20]) - hole2_22 = add_box(sim, "hole2_22", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[21]) - hole2_23 = add_box(sim, "hole2_23", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[22]) - hole2_24 = add_box(sim, "hole2_24", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[23]) - hole2_25 = add_box(sim, "hole2_25", plate_2, 1*mm, 1*mm, 2.5*mm, "Air", positions[24]) - - - - elif string_name == 'mb_40_slit_11': - plate_1 = add_box( sim, "plate1", "world", 50 , 50, 2.5, "Tungsten", offset("world", [0, 0, 1.25]) ) - plate_2 = add_box( sim, "plate2", "world", 40 , 40, 2.5, "Tungsten", offset("world", [0, 0, 3.75]) ) - hole1_1 = add_box(sim, "hole1_1", plate_1, 1*mm, 10*mm, 2.5*mm, "Air", positions[0]) - hole1_2 = add_box(sim, "hole1_2", plate_1, 1*mm, 10*mm, 2.5*mm, "Air", positions[1]) - hole1_3 = add_box(sim, "hole1_3", plate_1, 1*mm, 10*mm, 2.5*mm, "Air", positions[2]) - hole1_4 = add_box(sim, "hole1_4", plate_1, 1*mm, 10*mm, 2.5*mm, "Air", positions[3]) - hole1_5 = add_box(sim, "hole1_5", plate_1, 1*mm, 10*mm, 2.5*mm, "Air", positions[4]) - hole2_1 = add_box(sim, "hole2_1", plate_2, 1*mm, 10*mm, 2.5*mm, "Air", positions[0]) - hole2_2 = add_box(sim, "hole2_2", plate_2, 1*mm, 10*mm, 2.5*mm, "Air", positions[1]) - hole2_3 = add_box(sim, "hole2_3", plate_2, 1*mm, 10*mm, 2.5*mm, "Air", positions[2]) - hole2_4 = add_box(sim, "hole2_4", plate_2, 1*mm, 10*mm, 2.5*mm, "Air", positions[3]) - hole2_5 = add_box(sim, "hole2_5", plate_2, 1*mm, 10*mm, 2.5*mm, "Air", positions[4]) - - - if string_name == 'shaper40': - start_z, end_z = get_object_zdimension(final_components, center_z) - logger.info(f"Passive collimator - '{string_name}' - range: start at {start_z:.3f} mm, end at {end_z:.3f} mm") - return end_z, leaf_objects - - elif string_name == ('mb_40_holes_11' or 'mb_40_slit_11'): - start_z, end_z = get_object_zdimension(components, center_z) - logger.info(f"Passive collimator - '{string_name}' - range: start at {start_z:.3f} mm, end at {end_z:.3f} mm") - return end_z - - else: - start_z, end_z = get_object_zdimension(components, center_z) - logger.info(f"Passive collimator - '{string_name}' - range: start at {start_z:.3f} mm, end at {end_z:.3f} mm") - return end_z - - - -def build_dosephantombox(sim, _string_name, material, center_x=0, center_y=0, center_z=0, - dimension_x=None, dimension_y=None, dimension_z=None, material_colors=None): - """ - Creates and adds a box-shaped volume to the simulation representing a typical dose phantom. - - Args: - sim: The simulation object where volumes are added. - _string_name (str): Unique identifier name for the box volume. - material (str): The material name assigned to the box volume. - center_x (float, optional): X-coordinate (in mm) of the box center. Defaults to 0. - center_y (float, optional): Y-coordinate (in mm) of the box center. Defaults to 0. - center_z (float, optional): Z-coordinate (in mm) of the box center. Defaults to 0. - dimension_x (float): Size of the box along the X-axis (in mm). Must be specified. - dimension_y (float): Size of the box along the Y-axis (in mm). Must be specified. - dimension_z (float): Size of the box along the Z-axis (in mm). Must be specified. - material_colors (dict, optional): Dictionary mapping material names to color values. - - Raises: - ValueError: If any of the dimensions (dimension_x, dimension_y, dimension_z) are None. - - Returns: - Volume: The created box volume object added to the simulation. - """ - if None in (dimension_x, dimension_y, dimension_z): - raise ValueError("[ERROR] All dimensions (dimension_x, dimension_y, dimension_z) must be specified.") - - if material_colors is None: - material_colors = {} - - box = sim.add_volume("Box", _string_name) - box.mother = "world" - box.size = [dimension_x, dimension_y, dimension_z] - box.material = material - box.translation = [center_x, center_y, center_z] - - if material in material_colors: - box.color = material_colors[material] - - logger.info(f"Box - '{_string_name}' - of material '{material}' built at: " - f"({center_x:.3f}, {center_y:.3f}, {center_z:.3f}) mm with size: " - f"{dimension_x:.3f} x {dimension_y:.3f} x {dimension_z:.3f} mm.") - - return box - -def add_source(sim, number_of_events): - """ - Adds an electron source to the simulation with a discrete energy spectrum loaded from a file. - - Args: - sim: The simulation object to which the source is added. - number_of_events (int): Number of events to simulate from the source. - - Returns: - source: The created source object. - """ - spectrum_path = "utils/spectra/9Mev.txt" - source = sim.add_source("GenericSource", "source") - source.particle = "e-" - source.energy.type = "spectrum_discrete" - source.energy.spectrum_energies, source.energy.spectrum_weights = np.loadtxt(spectrum_path, unpack=True) - source.position.type = "disc" - source.position.radius = 3 * mm - source.position.sigma_r = 0.8 * mm - source.position.centre = [0, 0, 0] - - source.direction.type = 'momentum' - source.direction.momentum = [0, 0, 1] - - source.n = number_of_events - - logger.info(f"Added source with spectrum from '{spectrum_path}', " - f"number of events: {number_of_events}, " - f"position radius: {source.position.radius} mm.") - - return source - -def set_shaper_aperture(leaf1, leaf2, leaf3, leaf4, aperture_x_mm, aperture_y_mm): - """ - Adjusts leaf positions to create a square/rectangular aperture. - - Args: - leaf1, leaf2: Leaves that define the X direction. - leaf3, leaf4: Leaves that define the Y direction. - aperture_x_mm (float): Desired aperture in X direction (must be <= 40 mm). - aperture_y_mm (float): Desired aperture in Y direction (must be <= 40 mm). - - Raises: - ValueError: If aperture_x_mm or aperture_y_mm exceeds 40 mm. - """ - if aperture_x_mm > 40 or aperture_y_mm > 40: - raise ValueError(f"[ERROR] Requested aperture exceeds maximum of 40 mm: " - f"X = {aperture_x_mm:.2f}, Y = {aperture_y_mm:.2f}") - - half_x = aperture_x_mm / 2 - half_y = aperture_y_mm / 2 - - # Update X leaves (left/right) - leaf1.translation[0] = half_x + leaf1.size[0] / 2 # right - leaf2.translation[0] = -half_x - leaf2.size[0] / 2 # left - - # Update Y leaves (top/bottom) - leaf3.translation[1] = half_y + leaf3.size[1] / 2 # top - leaf4.translation[1] = -half_y - leaf4.size[1] / 2 # bottom - - - -def rotate_leaves_around_z(leaf1, leaf2, leaf3, leaf4, angle_deg): - rot = R.from_euler('z', angle_deg, degrees=True) - rot_matrix = rot.as_matrix() # shape (3,3) - - for leaf in [leaf1, leaf2, leaf3, leaf4]: - x, y, z = leaf.translation - pos_rotated = rot.apply([x, y, z]) - leaf.translation = pos_rotated.tolist() - leaf.rotation = rot_matrix - -def obtain_pdd_from_image(path, mean_size=10): - """ - Loads a dose image and computes the normalized Percent Depth Dose (PDD) - from a central square ROI. - - Parameters: - path (str or Path): Path to the .mhd dose file. - - Returns: - pdd (np.ndarray): Normalized 1D PDD profile along depth (Z-axis). - """ - image = sitk.ReadImage(str(path)) - dose_array = sitk.GetArrayFromImage(image) - - y_center, x_center = int(dose_array.shape[1]/2), int(dose_array.shape[2]/2) - half = mean_size // 2 - roi_y = slice(y_center - half, y_center + half) - roi_x = slice(x_center - half, x_center + half) - - pdd = np.mean(dose_array[:, roi_y, roi_x], axis=(1, 2)) - pdd /= np.max(pdd) - - return pdd - -def obtain_profile_from_image(path, mean_size=10): - """ - Loads a dose image and computes the normalized Percent Depth Dose (PDD) - from a central square ROI. - - Parameters: - path (str or Path): Path to the .mhd dose file. - - Returns: - pdd (np.ndarray): Normalized 1D PDD profile along depth (Z-axis). - """ - image = sitk.ReadImage(str(path)) - dose_array = sitk.GetArrayFromImage(image) - - profile = np.mean(dose_array[0:7, 120:130,:], axis = (0,1)) - profile /= np.max(profile) - - return profile - -def evaluate_pdd_similarity(reference_pdd, test_pdd, tolerance=0.03): - """ - Compares two PDD curves using Mean Absolute Error (MAE). - - Parameters: - reference_pdd (np.1darray): Reference PDD curve (normalized). - test_pdd (np.1darray) : Test PDD curve (normalized). - tolerance (float) : MAE tolerance threshold. - - Returns: - passed (bool): True if MAE < tolerance. - """ - mae = np.mean(np.abs(reference_pdd - test_pdd)) - passed = mae < tolerance - - return passed, mae - -def evaluate_profile_similarity(reference_profile, test_profile, tolerance=0.2): - """ - Compares two PDD curves using Mean Absolute Error (MAE). - - Parameters: - reference_pdd (np.1darray): Reference PDD curve (normalized). - test_pdd (np.1darray) : Test PDD curve (normalized). - tolerance (float) : MAE tolerance threshold. - - Returns: - passed (bool): True if MAE < tolerance. - """ - mae = np.mean(np.abs(reference_profile - test_profile)) - passed = mae < tolerance - - return passed, mae \ No newline at end of file diff --git a/opengate/contrib/linacs/ElectronFlash/simulation_standard.py b/opengate/contrib/linacs/ElectronFlash/simulation_standard.py deleted file mode 100644 index df4db84d2..000000000 --- a/opengate/contrib/linacs/ElectronFlash/simulation_standard.py +++ /dev/null @@ -1,132 +0,0 @@ -import opengate as gate -import numpy as np -import EF_function as fun -import os, sys, logging - - - - -if __name__ == "__main__": - #===================================================== - # INITIALISATION - #===================================================== - - logger = logging.getLogger(__name__) - logger.setLevel(logging.INFO) - - - number_of_total_events = 1_000_000 - - - sim = gate.Simulation() - sim.verbose_level = gate.logger.RUN - sim.running_verbose_level = gate.logger.RUN - sim.g4_verbose = False - sim.g4_verbose_level = 1 - sim.visu = False - sim.visu_type = "qt" - sim.random_engine = "MersenneTwister" - sim.random_seed = "auto" - sim.output_dir = "output" - sim.number_of_threads = 4 - sim.progress_bar = True - sim.volume_manager.add_material_database(gate_materials_path) - if sim.visu: - sim.number_of_threads = 1 - number_of_total_events = 1 - number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 - - sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") - - #===================================================== - # GEOMETRY - #===================================================== - - mm = fun.mm - - ###### Build Linac from titanium window up to BLD - EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) - - - ###### Build different BLDs (app100, app40) - App100_end = fun.build_passive_collimation(sim, "app100", center_z=EF_end, material_colors=fun.material_colors) - #App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) - - ###### If the proper applicator is selected (app40), you can add the MB template or the beam shaper device - #MB_end = fun.build_passive_collimation(sim, "mb_40_holes_11", center_z=App40_end, material_colors=fun.material_colors) - - #shaper40_end, (leaf1, leaf2, leaf3, leaf4) = fun.build_passive_collimation(sim, "shaper40", center_z=App40_end, material_colors=fun.material_colors) - #fun.set_shaper_aperture(leaf1, leaf2, leaf3, leaf4, aperture_x_mm = 20, aperture_y_mm = 20) - #fun.rotate_leaves_around_z(leaf1, leaf2, leaf3, leaf4, angle_deg = 45) - - - #===================================================== - # PHANTOMS - #===================================================== - - ### Build a water phantom for openfield dose deposition - OK - dim_x, dim_y, dim_z = 250*mm, 250*mm, 60*mm - dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=App100_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) - - ### Build a water phantom for MB applications - OK - #dim_x, dim_y, dim_z = 100*mm, 100*mm, 30*mm - #dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=MB_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) - - ### Build a plane for phase space test - #dim_x, dim_y, dim_z = 150*mm, 150*mm, 0.01*mm - #phsp_plane = fun.build_dosephantombox(sim, "Phasespace_plane", "Air", center_z=MB_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) - - - - #===================================================== - # ACTORS - #===================================================== - - ## DoseActor for PDD/profiles (openfield and shaper) - dose = sim.add_actor("DoseActor", "dose") - dose.attached_to = "Waterbox" - dose.output_filename = "dose.mhd" - dose.hit_type = "random" - dose.size = [120, 120, 30] # Number of voxels - dose.spacing = [1 * mm, 1 * mm, 2 * mm] # Voxel size - dose.dose.active = True - dose.dose_uncertainty.active = False - dose.edep_uncertainty.active = False - - ## DoseActor for MB - OK - #dose = sim.add_actor("DoseActor", "dose") - #dose.attached_to = "Waterbox" - #dose.output_filename = "dose.mhd" - #dose.hit_type = "random" - #dose.size = [250, 250, 30] # Number of voxels - #dose.spacing = [0.1 * mm, 0.1 * mm, 1 * mm] # Voxel size - #dose.dose.active = True - #dose.dose_uncertainty.active = False - #dose.edep_uncertainty.active = False - - ### PHSP actor for tests - OK - #phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - #phsp.attached_to = phsp_plane.name - #phsp.attributes = ["KineticEnergy","PreDirection","EventPosition",] - #phsp.output_filename = "phsp.root" - - - #===================================================== - # PHYSICS - #===================================================== - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" - sim.physics_manager.set_production_cut("world", "all", 2*mm) - - - #===================================================== - # SOURCE - #===================================================== - source = fun.add_source(sim, number_of_events) - - - - #===================================================== - # START BEAMS - #===================================================== - sim.run() diff --git a/opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev_old.txt b/opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev_old.txt deleted file mode 100644 index 7bf5eab93..000000000 --- a/opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev_old.txt +++ /dev/null @@ -1,80 +0,0 @@ -0.100000 0.010140 -0.200000 0.010610 -0.400000 0.010170 -0.500000 0.010660 -0.600000 0.010670 -0.800000 0.012820 -0.900000 0.012130 -1.000000 0.014240 -1.200000 0.014720 -1.300000 0.014780 -1.400000 0.015790 -1.600000 0.016450 -1.700000 0.015990 -1.800000 0.016830 -2.000000 0.017250 -2.100000 0.018060 -2.200000 0.018160 -2.400000 0.018720 -2.500000 0.018940 -2.600000 0.019470 -2.800000 0.020100 -2.900000 0.020890 -3.000000 0.021930 -3.200000 0.022350 -3.300000 0.022880 -3.400000 0.024370 -3.600000 0.024890 -3.700000 0.026160 -3.800000 0.025970 -4.000000 0.037760 -4.100000 0.038230 -4.200000 0.049750 -4.400000 0.070680 -4.500000 0.051660 -4.600000 0.052850 -4.800000 0.054530 -4.900000 0.055060 -5.000000 0.067310 -5.200000 0.048420 -5.300000 0.039540 -5.400000 0.041320 -5.600000 0.043570 -5.700000 0.044770 -5.800000 0.047590 -6.000000 0.049810 -6.100000 0.051730 -6.200000 0.054590 -6.400000 0.056450 -6.500000 0.059620 -6.600000 0.062980 -6.800000 0.065880 -6.900000 0.068480 -7.000000 0.071810 -7.200000 0.077100 -7.300000 0.081210 -7.400000 0.084450 -7.600000 0.091070 -7.700000 0.094850 -7.800000 0.100830 -8.000000 0.107010 -8.100000 0.122463 -8.200000 0.130944 -8.400000 0.140327 -8.500000 0.145794 -8.600000 0.155859 -8.800000 0.165231 -8.900000 0.174790 -9.100000 0.184833 -9.300000 0.197934 -9.400000 0.206921 -9.500000 0.217404 -9.700000 0.230736 -9.800000 0.241120 -9.900000 0.251592 -10.100000 0.269731 -10.300000 0.301059 -10.500000 0.322168 -10.600000 0.346918 -10.700000 0.372229 -10.800000 1.000000 diff --git a/opengate/contrib/linacs/electron_flash/__init__.py b/opengate/contrib/linacs/electron_flash/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/opengate/contrib/linacs/electron_flash/electron_flash.py b/opengate/contrib/linacs/electron_flash/electron_flash.py new file mode 100644 index 000000000..f74612af0 --- /dev/null +++ b/opengate/contrib/linacs/electron_flash/electron_flash.py @@ -0,0 +1,916 @@ +import opengate as gate +import numpy as np +import os, sys, logging +from scipy.spatial.transform import Rotation as R +import SimpleITK as sitk + + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +material_colors = { + "Titanium": [0.2, 0.2, 0.2, 0.6], + "Tecapeek": [1.0, 0.6, 0.2, 0.6], + "Air": [1.0, 0.6, 0.2, 0.6], + "AluminiumEGS": [0.8, 0.8, 0.8, 0.6], + "Tungsten": [0.8, 0.8, 0.8, 0.6], + "PMMA": [0.0, 0.6, 0.0, 0.5], + "Water": [0.0, 0.0, 0.5, 0.5], +} + + +def add_tubs( + sim, name, mother, rmin, rmax, height, material, translation, material_colors=None +): + """ + This function creates and adds a cylindrical (Tubs) volume to the geometry. + + Args: + sim: The simulation object where the volume will be added. + name (str): Name of the volume. + mother (str): The name of the mother volume. + rmin (float): Inner radius of the tube in millimeters. + rmax (float): Outer radius of the tube in millimeters. + height (float): Full height of the tube in millimeters. + material (str): Material assigned to the volume. + translation (tuple): A 3-element tuple representing the (x, y, z) translation of the volume. + material_colors (dict): A dictionary mapping material names to RGB color strings or tuples. + + Returns: + The created volume object. + + """ + mm = gate.g4_units.mm + vol = sim.add_volume("Tubs", name) + vol.mother = mother + vol.rmin = rmin * mm + vol.rmax = rmax * mm + vol.dz = height * mm / 2 + vol.material = material + vol.translation = translation + if material_colors and material in material_colors: + vol.color = material_colors[material] + return vol + + +def add_cons( + sim, + name, + mother, + rmin1, + rmax1, + rmin2, + rmax2, + height, + material, + translation, + material_colors=None, +): + """ + This function creates and adds a conical (Cons) volume to the geometry. + + Args: + sim: The simulation object where the volume will be added. + name (str): Name of the volume. + mother (str): The name of the mother volume. + rmin1 (float): Inner radius at the -Z end in millimeters. + rmax1 (float): Outer radius at the -Z end in millimeters. + rmin2 (float): Inner radius at the +Z end in millimeters. + rmax2 (float): Outer radius at the +Z end in millimeters. + height (float): Full height of the cone in millimeters. + material (str): Material assigned to the volume. + translation (tuple): A 3-element tuple representing the (x, y, z) translation of the volume. + material_colors (dict, optional): A dictionary mapping material names to RGB color strings or tuples. + + Returns: + The created volume object. + """ + mm = gate.g4_units.mm + deg = gate.g4_units.deg + vol = sim.add_volume("Cons", name) + vol.mother = mother + vol.rmin1 = rmin1 * mm + vol.rmax1 = rmax1 * mm + vol.rmin2 = rmin2 * mm + vol.rmax2 = rmax2 * mm + vol.dz = height / 2 + vol.material = material + vol.translation = translation + vol.sphi = 0 * deg + vol.dphi = 360 * deg + if material_colors and material in material_colors: + vol.color = material_colors[material] + return vol + + +def add_box(sim, name, mother, lx, ly, lz, material, translation, material_colors=None): + """ + This function creates and adds a rectangular (Box) volume to the geometry. + + Args: + sim: The simulation object where the volume will be added. + name (str): Name of the volume. + mother (str): The name of the mother volume. + lx (float): Length of the box along the X-axis in millimeters. + ly (float): Length of the box along the Y-axis in millimeters. + lz (float): Length of the box along the Z-axis in millimeters. + material (str): Material assigned to the volume. + translation (tuple): A 3-element tuple representing the (x, y, z) translation of the volume. + material_colors (dict, optional): A dictionary mapping material names to RGB color strings or tuples. + + Returns: + The created volume object. + """ + vol = sim.add_volume("Box", name) + vol.mother = mother + vol.size = [lx, ly, lz] + vol.material = material + vol.translation = translation + if material_colors and material in material_colors: + vol.color = material_colors[material] + return vol + + +def get_object_zdimension(components, center_z): + """ + Computes the Z-dimension range (start and end Z coordinates) of the whole structure (made up of components). + + Args: + components (list): A list of component tuples. + center_z (float): Global Z-axis offset to apply to all components. + + Returns: + tuple: (start_z, end_z) in millimeters, representing the total Z-extent. + """ + get_z_bounds = lambda comp: ( + comp[-1][2] - comp[-3] / 2 + center_z, + comp[-1][2] + comp[-3] / 2 + center_z, + ) + + start_z, _ = get_z_bounds(components[0]) + _, end_z = get_z_bounds(components[-1]) + return start_z, end_z + + +def build_ElectronFlash(sim, center_x=0, center_y=0, center_z=0, material_colors=None): + """ + This function builds the full ElectronFlash linear accelerator (linac) geometry, starting from the titanium window up to the beginning of the BLD. + + Args: + sim: The simulation object where volumes will be added. + center_x (float): Offset in millimeters along the X-axis for the titanium window. + center_y (float): Offset in millimeters along the Y-axis for the titanium window. + center_z (float): Offset in millimeters along the Z-axis for the titanium window. + material_colors (dict, optional): A dictionary mapping material names to RGB color strings or tuples. + + Returns: + float: The Z-coordinate (in mm) of the end of the linac structure, useful when other components has to be added. + """ + mm = gate.g4_units.mm + if material_colors is None: + material_colors = {} + + def offset(mother, pos): + if mother == "world": + return [pos[0] + center_x, pos[1] + center_y, pos[2] + center_z] + return pos + + logger.info( + f"Building ElectronFlash linac structure with center offset: X={center_x} mm, Y={center_y} mm, Z={center_z} mm" + ) + + components = [ + # (func, name, mother, args..., material, translation) + ( + add_tubs, + sim, + "tit_window", + "world", + 0, + 18.2, + 0.055, + "Titanium", + [0, 0, 0.0275 * mm], + ), + (add_tubs, sim, "cyl1", "world", 0, 34.6, 12.8, "Tecapeek", [0, 0, 6.455 * mm]), + (add_cons, sim, "hollow_cone", "cyl1", 0, 7, 0, 19, 12.8, "Air", [0, 0, 0]), + (add_tubs, sim, "st_0", "world", 15, 34.6, 5, "Tecapeek", [0, 0, 15.355 * mm]), + (add_tubs, sim, "st_1", "world", 15, 22, 14, "Tecapeek", [0, 0, 24.855 * mm]), + (add_tubs, sim, "st_2", "world", 20, 24, 132, "Tecapeek", [0, 0, 97.855 * mm]), + (add_tubs, sim, "st_3", "world", 31, 37, 35, "Tecapeek", [0, 0, 156.755 * mm]), + ( + add_tubs, + sim, + "st_4", + "world", + 22.5, + 37, + 73.5, + "Tecapeek", + [0, 0, 211.005 * mm], + ), + ( + add_tubs, + sim, + "st_5", + "world", + 37, + 130.8, + 20, + "AluminiumEGS", + [0, 0, 211.005 * mm], + ), + (add_tubs, sim, "cyl6", "world", 0, 48, 69, "Tecapeek", [0, 0, 282.255 * mm]), + (add_cons, sim, "hollow_cone1", "cyl6", 0, 22.5, 0, 31.5, 69, "Air", [0, 0, 0]), + (add_tubs, sim, "st_6", "world", 48, 72, 16, "Tecapeek", [0, 0, 255.755 * mm]), + (add_tubs, sim, "st_7", "world", 48, 72, 29, "Tecapeek", [0, 0, 302.255 * mm]), + ] + + for comp in components: + func = comp[0] + sim = comp[1] + name = comp[2] + mother = comp[3] + *args, material, translation = comp[4:] + + func( + sim, + name, + mother, + *args, + material, + offset(mother, translation), + material_colors=material_colors, + ) + start_z, end_z = get_object_zdimension(components, center_z) + + logger.info( + f"Linac structure range: start at {start_z:.3f} mm, end at {end_z:.3f} mm" + ) + return end_z + + +def build_passive_collimation( + sim, string_name, center_x=0, center_y=0, center_z=0, material_colors=None +): + """ + Builds a passive collimation structure. + + Args: + sim: The simulation object where volumes are added. + string_name (str): Identifier for the collimator type. Allowed values are: + - 'app100' + - 'app40' + - 'app70' + - 'shaper40' + - 'mb_40_holes_11' + - 'mb_40_slit_11' + center_x (float, optional): X-axis offset (in mm) applied to all components. Defaults to 0. + center_y (float, optional): Y-axis offset (in mm) applied to all components. Defaults to 0. + center_z (float, optional): Z-axis offset (in mm) applied to all components. Defaults to 0. + material_colors (dict, optional): Dictionary mapping material names to colors. + + Raises: + ValueError: If `string_name` is not one of the allowed applicator types. + + Returns: + float: The Z-coordinate (in mm) of the end position of the constructed collimator structure. + """ + mm = gate.g4_units.mm + if material_colors is None: + material_colors = {} + + allowed_names = [ + "app100", + "app40", + "app70", + "shaper40", + "mb_40_holes_11", + "mb_40_slit_11", + ] + if string_name not in allowed_names: + raise ValueError( + f"[ERROR] Unsupported applicator type: '{string_name}'. Allowed applicators are: {allowed_names}" + ) + + def offset(mother, pos): + if mother == "world": + return [pos[0] + center_x, pos[1] + center_y, pos[2] + center_z] + return pos + + if string_name == "app100": + components = [ + (add_tubs, sim, "cyl7", "world", 0, 72, 42.8, "PMMA", [0, 0, 21.4]), + ( + add_tubs, + sim, + "hollow_cyl", + "cyl7", + 0, + 31.5, + 10, + "Air", + [0, 0, -16.4 * mm], + ), + ( + add_cons, + sim, + "hollow_cone2", + "cyl7", + 0, + 31.5, + 0, + 50, + 32.8, + "Air", + [0, 0, 5 * mm], + ), + (add_tubs, sim, "st_8", "world", 55, 72, 14.8, "PMMA", [0, 0, 50.2 * mm]), + (add_tubs, sim, "app100", "world", 50, 55, 742, "PMMA", [0, 0, 413.8 * mm]), + ] + elif string_name == "app40": + components = [ + (add_tubs, sim, "cyl7", "world", 0, 72, 42.8, "PMMA", [0, 0, 21.4]), + ( + add_cons, + sim, + "hollow_cone2", + "cyl7", + 0, + 31.5, + 0, + 20, + 42.8, + "Air", + [0, 0, 0], + ), + (add_tubs, sim, "st_8", "world", 25, 72, 10, "PMMA", [0, 0, 47.8 * mm]), + (add_tubs, sim, "app40", "world", 20, 25, 357, "PMMA", [0, 0, 231.3 * mm]), + ] + elif string_name == "shaper40": + components = [ + (add_tubs, sim, "cyl_s", "world", 25.2, 26, 50, "PMMA", [0, 0, -28]), + (add_tubs, sim, "cyl_p", "world", 25.2, 75, 5, "PMMA", [0, 0, -0.5]), + ] + leaf_defs = [ + ("leaf1", [12.5, 0, 3.5]), + ("leaf2", [-12.5, 0, 3.5]), + ("leaf3", [0, 12.5, 8.5]), + ("leaf4", [0, -12.5, 8.5]), + ] + leaf_components = [ + ( + add_box, + sim, + name, + "world", + 25 if name in ["leaf1", "leaf2"] else 50, + 50 if name in ["leaf1", "leaf2"] else 25, + 3, + "Tungsten", + pos, + ) + for name, pos in leaf_defs + ] + final_components = components + leaf_components + elif string_name == "mb_40_holes_11": + positions = [[x, y, 0] for x in [-4, -2, 0, 2, 4] for y in [-4, -2, 0, 2, 4]] + + components = [ + ( + add_tubs, + sim, + "cyl_mb1", + "world", + 25.2, + 40, + 25, + "Tecapeek", + [0, 0, -12.5], + ), # 338.155 + (add_box, sim, "slab1", "world", 5, 50, 2.5, "Tecapeek", [22.5, 0, 3.75]), + (add_box, sim, "slab2", "world", 5, 50, 2.5, "Tecapeek", [-22.5, 0, 3.75]), + (add_box, sim, "slab3", "world", 40, 5, 2.5, "Tecapeek", [0, -22.5, 3.75]), + ] + elif string_name == "mb_40_slit_11": + positions = [[x, 0, 0] for x in [-4, -2, 0, 2, 4]] + + components = [ + ( + add_tubs, + sim, + "cyl_mb1", + "world", + 25.2, + 40, + 25, + "Tecapeek", + [0, 0, -12.5], + ), # 338.155 + # (add_box , sim, "plate1", "world", 50 , 50, 2.5, "Tungsten", [0, 0, 1.25]), + # (add_box , sim, "plate2", "world", 40 , 40, 2.5, "Tungsten", [0, 0, 3.75]), + (add_box, sim, "slab1", "world", 5, 50, 2.5, "Tecapeek", [22.5, 0, 3.75]), + (add_box, sim, "slab2", "world", 5, 50, 2.5, "Tecapeek", [-22.5, 0, 3.75]), + (add_box, sim, "slab3", "world", 40, 5, 2.5, "Tecapeek", [0, -22.5, 3.75]), + ] + + for comp in components: + func = comp[0] + sim = comp[1] + name = comp[2] + mother = comp[3] + *args, material, translation = comp[4:] + + func( + sim, + name, + mother, + *args, + material, + offset(mother, translation), + material_colors=material_colors, + ) + + if string_name == "shaper40": + leaf_objects = [] + for name, pos in leaf_defs: + obj = add_box( + sim, + name, + "world", + 25 if "leaf1" in name or "leaf2" in name else 50, + 50 if "leaf1" in name or "leaf2" in name else 25, + 3, + "Tungsten", + offset("world", pos), + material_colors=material_colors, + ) + leaf_objects.append(obj) + + if string_name == "mb_40_holes_11": + plate_1 = add_box( + sim, + "plate1", + "world", + 50, + 50, + 2.5, + "Tungsten", + offset("world", [0, 0, 1.25]), + ) + plate_2 = add_box( + sim, + "plate2", + "world", + 40, + 40, + 2.5, + "Tungsten", + offset("world", [0, 0, 3.75]), + ) + + hole1_1 = add_box( + sim, "hole1_1", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[0] + ) + hole1_2 = add_box( + sim, "hole1_2", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[1] + ) + hole1_3 = add_box( + sim, "hole1_3", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[2] + ) + hole1_4 = add_box( + sim, "hole1_4", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[3] + ) + hole1_5 = add_box( + sim, "hole1_5", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[4] + ) + hole1_6 = add_box( + sim, "hole1_6", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[5] + ) + hole1_7 = add_box( + sim, "hole1_7", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[6] + ) + hole1_8 = add_box( + sim, "hole1_8", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[7] + ) + hole1_9 = add_box( + sim, "hole1_9", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[8] + ) + hole1_10 = add_box( + sim, "hole1_10", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[9] + ) + hole1_11 = add_box( + sim, "hole1_11", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[10] + ) + hole1_12 = add_box( + sim, "hole1_12", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[11] + ) + hole1_13 = add_box( + sim, "hole1_13", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[12] + ) + hole1_14 = add_box( + sim, "hole1_14", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[13] + ) + hole1_15 = add_box( + sim, "hole1_15", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[14] + ) + hole1_16 = add_box( + sim, "hole1_16", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[15] + ) + hole1_17 = add_box( + sim, "hole1_17", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[16] + ) + hole1_18 = add_box( + sim, "hole1_18", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[17] + ) + hole1_19 = add_box( + sim, "hole1_19", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[18] + ) + hole1_20 = add_box( + sim, "hole1_20", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[19] + ) + hole1_21 = add_box( + sim, "hole1_21", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[20] + ) + hole1_22 = add_box( + sim, "hole1_22", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[21] + ) + hole1_23 = add_box( + sim, "hole1_23", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[22] + ) + hole1_24 = add_box( + sim, "hole1_24", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[23] + ) + hole1_25 = add_box( + sim, "hole1_25", plate_1, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[24] + ) + hole2_1 = add_box( + sim, "hole2_1", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[0] + ) + hole2_2 = add_box( + sim, "hole2_2", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[1] + ) + hole2_3 = add_box( + sim, "hole2_3", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[2] + ) + hole2_4 = add_box( + sim, "hole2_4", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[3] + ) + hole2_5 = add_box( + sim, "hole2_5", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[4] + ) + hole2_6 = add_box( + sim, "hole2_6", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[5] + ) + hole2_7 = add_box( + sim, "hole2_7", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[6] + ) + hole2_8 = add_box( + sim, "hole2_8", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[7] + ) + hole2_9 = add_box( + sim, "hole2_9", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[8] + ) + hole2_10 = add_box( + sim, "hole2_10", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[9] + ) + hole2_11 = add_box( + sim, "hole2_11", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[10] + ) + hole2_12 = add_box( + sim, "hole2_12", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[11] + ) + hole2_13 = add_box( + sim, "hole2_13", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[12] + ) + hole2_14 = add_box( + sim, "hole2_14", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[13] + ) + hole2_15 = add_box( + sim, "hole2_15", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[14] + ) + hole2_16 = add_box( + sim, "hole2_16", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[15] + ) + hole2_17 = add_box( + sim, "hole2_17", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[16] + ) + hole2_18 = add_box( + sim, "hole2_18", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[17] + ) + hole2_19 = add_box( + sim, "hole2_19", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[18] + ) + hole2_20 = add_box( + sim, "hole2_20", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[19] + ) + hole2_21 = add_box( + sim, "hole2_21", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[20] + ) + hole2_22 = add_box( + sim, "hole2_22", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[21] + ) + hole2_23 = add_box( + sim, "hole2_23", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[22] + ) + hole2_24 = add_box( + sim, "hole2_24", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[23] + ) + hole2_25 = add_box( + sim, "hole2_25", plate_2, 1 * mm, 1 * mm, 2.5 * mm, "Air", positions[24] + ) + + elif string_name == "mb_40_slit_11": + plate_1 = add_box( + sim, + "plate1", + "world", + 50, + 50, + 2.5, + "Tungsten", + offset("world", [0, 0, 1.25]), + ) + plate_2 = add_box( + sim, + "plate2", + "world", + 40, + 40, + 2.5, + "Tungsten", + offset("world", [0, 0, 3.75]), + ) + hole1_1 = add_box( + sim, "hole1_1", plate_1, 1 * mm, 10 * mm, 2.5 * mm, "Air", positions[0] + ) + hole1_2 = add_box( + sim, "hole1_2", plate_1, 1 * mm, 10 * mm, 2.5 * mm, "Air", positions[1] + ) + hole1_3 = add_box( + sim, "hole1_3", plate_1, 1 * mm, 10 * mm, 2.5 * mm, "Air", positions[2] + ) + hole1_4 = add_box( + sim, "hole1_4", plate_1, 1 * mm, 10 * mm, 2.5 * mm, "Air", positions[3] + ) + hole1_5 = add_box( + sim, "hole1_5", plate_1, 1 * mm, 10 * mm, 2.5 * mm, "Air", positions[4] + ) + hole2_1 = add_box( + sim, "hole2_1", plate_2, 1 * mm, 10 * mm, 2.5 * mm, "Air", positions[0] + ) + hole2_2 = add_box( + sim, "hole2_2", plate_2, 1 * mm, 10 * mm, 2.5 * mm, "Air", positions[1] + ) + hole2_3 = add_box( + sim, "hole2_3", plate_2, 1 * mm, 10 * mm, 2.5 * mm, "Air", positions[2] + ) + hole2_4 = add_box( + sim, "hole2_4", plate_2, 1 * mm, 10 * mm, 2.5 * mm, "Air", positions[3] + ) + hole2_5 = add_box( + sim, "hole2_5", plate_2, 1 * mm, 10 * mm, 2.5 * mm, "Air", positions[4] + ) + + if string_name == "shaper40": + start_z, end_z = get_object_zdimension(final_components, center_z) + logger.info( + f"Passive collimator - '{string_name}' - range: start at {start_z:.3f} mm, end at {end_z:.3f} mm" + ) + return end_z, leaf_objects + + elif string_name == ("mb_40_holes_11" or "mb_40_slit_11"): + start_z, end_z = get_object_zdimension(components, center_z) + logger.info( + f"Passive collimator - '{string_name}' - range: start at {start_z:.3f} mm, end at {end_z:.3f} mm" + ) + return end_z + + else: + start_z, end_z = get_object_zdimension(components, center_z) + logger.info( + f"Passive collimator - '{string_name}' - range: start at {start_z:.3f} mm, end at {end_z:.3f} mm" + ) + return end_z + + +def build_dosephantombox( + sim, + _string_name, + material, + center_x=0, + center_y=0, + center_z=0, + dimension_x=None, + dimension_y=None, + dimension_z=None, + material_colors=None, +): + """ + Creates and adds a box-shaped volume to the simulation representing a typical dose phantom. + + Args: + sim: The simulation object where volumes are added. + _string_name (str): Unique identifier name for the box volume. + material (str): The material name assigned to the box volume. + center_x (float, optional): X-coordinate (in mm) of the box center. Defaults to 0. + center_y (float, optional): Y-coordinate (in mm) of the box center. Defaults to 0. + center_z (float, optional): Z-coordinate (in mm) of the box center. Defaults to 0. + dimension_x (float): Size of the box along the X-axis (in mm). Must be specified. + dimension_y (float): Size of the box along the Y-axis (in mm). Must be specified. + dimension_z (float): Size of the box along the Z-axis (in mm). Must be specified. + material_colors (dict, optional): Dictionary mapping material names to color values. + + Raises: + ValueError: If any of the dimensions (dimension_x, dimension_y, dimension_z) are None. + + Returns: + Volume: The created box volume object added to the simulation. + """ + if None in (dimension_x, dimension_y, dimension_z): + raise ValueError( + "[ERROR] All dimensions (dimension_x, dimension_y, dimension_z) must be specified." + ) + + if material_colors is None: + material_colors = {} + + box = sim.add_volume("Box", _string_name) + box.mother = "world" + box.size = [dimension_x, dimension_y, dimension_z] + box.material = material + box.translation = [center_x, center_y, center_z] + + if material in material_colors: + box.color = material_colors[material] + + logger.info( + f"Box - '{_string_name}' - of material '{material}' built at: " + f"({center_x:.3f}, {center_y:.3f}, {center_z:.3f}) mm with size: " + f"{dimension_x:.3f} x {dimension_y:.3f} x {dimension_z:.3f} mm." + ) + + return box + + +def add_source(sim, number_of_events): + """ + Adds an electron source to the simulation with a discrete energy spectrum loaded from a file. + + Args: + sim: The simulation object to which the source is added. + number_of_events (int): Number of events to simulate from the source. + + Returns: + source: The created source object. + """ + mm = gate.g4_units.mm + spectrum_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "utils", "spectra", "9Mev.txt" + ) + source = sim.add_source("GenericSource", "source") + source.particle = "e-" + source.energy.type = "spectrum_discrete" + source.energy.spectrum_energies, source.energy.spectrum_weights = np.loadtxt( + spectrum_path, unpack=True + ) + source.position.type = "disc" + source.position.radius = 3 * mm + source.position.sigma_r = 0.8 * mm + source.position.centre = [0, 0, 0] + + source.direction.type = "momentum" + source.direction.momentum = [0, 0, 1] + + source.n = number_of_events + + logger.info( + f"Added source with spectrum from '{spectrum_path}', " + f"number of events: {number_of_events}, " + f"position radius: {source.position.radius} mm." + ) + + return source + + +def set_shaper_aperture(leaf1, leaf2, leaf3, leaf4, aperture_x_mm, aperture_y_mm): + """ + Adjusts leaf positions to create a square/rectangular aperture. + + Args: + leaf1, leaf2: Leaves that define the X direction. + leaf3, leaf4: Leaves that define the Y direction. + aperture_x_mm (float): Desired aperture in X direction (must be <= 40 mm). + aperture_y_mm (float): Desired aperture in Y direction (must be <= 40 mm). + + Raises: + ValueError: If aperture_x_mm or aperture_y_mm exceeds 40 mm. + """ + if aperture_x_mm > 40 or aperture_y_mm > 40: + raise ValueError( + f"[ERROR] Requested aperture exceeds maximum of 40 mm: " + f"X = {aperture_x_mm:.2f}, Y = {aperture_y_mm:.2f}" + ) + + half_x = aperture_x_mm / 2 + half_y = aperture_y_mm / 2 + + # Update X leaves (left/right) + leaf1.translation[0] = half_x + leaf1.size[0] / 2 # right + leaf2.translation[0] = -half_x - leaf2.size[0] / 2 # left + + # Update Y leaves (top/bottom) + leaf3.translation[1] = half_y + leaf3.size[1] / 2 # top + leaf4.translation[1] = -half_y - leaf4.size[1] / 2 # bottom + + +def rotate_leaves_around_z(leaf1, leaf2, leaf3, leaf4, angle_deg): + rot = R.from_euler("z", angle_deg, degrees=True) + rot_matrix = rot.as_matrix() # shape (3,3) + + for leaf in [leaf1, leaf2, leaf3, leaf4]: + x, y, z = leaf.translation + pos_rotated = rot.apply([x, y, z]) + leaf.translation = pos_rotated.tolist() + leaf.rotation = rot_matrix + + +def obtain_pdd_from_image(path, mean_size=10): + """ + Loads a dose image and computes the normalized Percent Depth Dose (PDD) + from a central square ROI. + + Parameters: + path (str or Path): Path to the .mhd dose file. + + Returns: + pdd (np.ndarray): Normalized 1D PDD profile along depth (Z-axis). + """ + image = sitk.ReadImage(str(path)) + dose_array = sitk.GetArrayFromImage(image) + + y_center, x_center = int(dose_array.shape[1] / 2), int(dose_array.shape[2] / 2) + half = mean_size // 2 + roi_y = slice(y_center - half, y_center + half) + roi_x = slice(x_center - half, x_center + half) + + pdd = np.mean(dose_array[:, roi_y, roi_x], axis=(1, 2)) + pdd /= np.max(pdd) + + return pdd + + +def obtain_profile_from_image(path, mean_size=10): + """ + Loads a dose image and computes the normalized Percent Depth Dose (PDD) + from a central square ROI. + + Parameters: + path (str or Path): Path to the .mhd dose file. + + Returns: + pdd (np.ndarray): Normalized 1D PDD profile along depth (Z-axis). + """ + image = sitk.ReadImage(str(path)) + dose_array = sitk.GetArrayFromImage(image) + + profile = np.mean(dose_array[0:7, 120:130, :], axis=(0, 1)) + profile /= np.max(profile) + + return profile + + +def evaluate_pdd_similarity(reference_pdd, test_pdd, tolerance=0.03): + """ + Compares two PDD curves using Mean Absolute Error (MAE). + + Parameters: + reference_pdd (np.1darray): Reference PDD curve (normalized). + test_pdd (np.1darray) : Test PDD curve (normalized). + tolerance (float) : MAE tolerance threshold. + + Returns: + passed (bool): True if MAE < tolerance. + """ + mae = np.mean(np.abs(reference_pdd - test_pdd)) + passed = mae < tolerance + + return passed, mae + + +def evaluate_profile_similarity(reference_profile, test_profile, tolerance=0.2): + """ + Compares two PDD curves using Mean Absolute Error (MAE). + + Parameters: + reference_pdd (np.1darray): Reference PDD curve (normalized). + test_pdd (np.1darray) : Test PDD curve (normalized). + tolerance (float) : MAE tolerance threshold. + + Returns: + passed (bool): True if MAE < tolerance. + """ + mae = np.mean(np.abs(reference_profile - test_profile)) + passed = mae < tolerance + + return passed, mae diff --git a/opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev.txt b/opengate/contrib/linacs/electron_flash/utils/spectra/9Mev.txt similarity index 100% rename from opengate/contrib/linacs/ElectronFlash/utils/spectra/9Mev.txt rename to opengate/contrib/linacs/electron_flash/utils/spectra/9Mev.txt diff --git a/opengate/tests/data b/opengate/tests/data index fdab18992..3ebf12674 160000 --- a/opengate/tests/data +++ b/opengate/tests/data @@ -1 +1 @@ -Subproject commit fdab189926aff471f829a411af3de77ae1c05d98 +Subproject commit 3ebf12674232452f3bbcc2b85d6790faaeefa1f8 diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py b/opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py new file mode 100644 index 000000000..1e6dfa0a8 --- /dev/null +++ b/opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py @@ -0,0 +1,178 @@ +import logging + +import opengate as gate +import opengate.contrib.linacs.electron_flash.electron_flash as fun +from opengate.tests import utility + +# test_ElectronFlash_dose_app40.py + + +def create_electron_flash_simulation(paths, passive_collimation, fantom): + # ===================================================== + # INITIALISATION + # ===================================================== + + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + mm = gate.g4_units.mm + + number_of_total_events = 5000 + + sim = gate.Simulation() + sim.verbose_level = gate.logger.RUN + sim.running_verbose_level = gate.logger.RUN + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.visu_type = "qt" + sim.random_engine = "MersenneTwister" + sim.random_seed = 18101996 + sim.output_dir = paths.output + sim.number_of_threads = 3 + sim.progress_bar = True + if sim.visu: + sim.number_of_threads = 1 + number_of_total_events = 1 + number_of_events = int(number_of_total_events / sim.number_of_threads) + 1 + + sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") + + # ===================================================== + # GEOMETRY + # ===================================================== + + ef_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) + if passive_collimation in ["app40", "app100"]: + app_end = fun.build_passive_collimation( + sim, + passive_collimation, + center_z=ef_end, + material_colors=fun.material_colors, + ) + dim_x, dim_y, dim_z = 250 * mm, 250 * mm, 60 * mm + elif passive_collimation == "mb_40_slit_11": + mb_end = fun.build_passive_collimation( + sim, "app40", center_z=ef_end, material_colors=fun.material_colors + ) + app_end = fun.build_passive_collimation( + sim, + passive_collimation, + center_z=mb_end, + material_colors=fun.material_colors, + ) + dim_x, dim_y, dim_z = 100 * mm, 100 * mm, 20 * mm + elif passive_collimation == "shaper40": + shaper_end = fun.build_passive_collimation( + sim, "app40", center_z=ef_end, material_colors=fun.material_colors + ) + app_end, (leaf1, leaf2, leaf3, leaf4) = fun.build_passive_collimation( + sim, + passive_collimation, + center_z=shaper_end, + material_colors=fun.material_colors, + ) + fun.set_shaper_aperture( + leaf1, leaf2, leaf3, leaf4, aperture_x_mm=25, aperture_y_mm=35 + ) + fun.rotate_leaves_around_z(leaf1, leaf2, leaf3, leaf4, angle_deg=45) + dim_x, dim_y, dim_z = 250 * mm, 250 * mm, 60 * mm + elif passive_collimation == "nose": + app_end = ef_end + if fantom == "Phasespace_plane": + dim_x, dim_y, dim_z = 150 * mm, 150 * mm, 0.01 * mm + + # ===================================================== + # PHANTOMS + # ===================================================== + if fantom == "Waterbox": + dosephantom = fun.build_dosephantombox( + sim, + "Waterbox", + "Water", + center_z=app_end + dim_z / 2, + dimension_x=dim_x, + dimension_y=dim_y, + dimension_z=dim_z, + material_colors=fun.material_colors, + ) + + dose = sim.add_actor("DoseActor", "dose") + dose.attached_to = "Waterbox" + dose.output_filename = "dose_test_" + passive_collimation + ".mhd" + dose.hit_type = "random" + dose.size = [120, 120, 30] # Number of voxels + dose.spacing = [1 * mm, 1 * mm, 2 * mm] # Voxel size + if passive_collimation == "mb_40_slit_11": + dose.size = [250, 250, 20] # Number of voxels + dose.spacing = [0.1 * mm, 0.1 * mm, 1 * mm] # Voxel size + dose.dose.active = True + dose.dose_uncertainty.active = False + dose.edep_uncertainty.active = False + elif fantom == "Phasespace_plane": + phsp_plane = fun.build_dosephantombox( + sim, + "Phasespace_plane", + "Air", + center_z=app_end + dim_z / 2, + dimension_x=dim_x, + dimension_y=dim_y, + dimension_z=dim_z, + material_colors=fun.material_colors, + ) + + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp.attached_to = phsp_plane.name + phsp.attributes = [ + "KineticEnergy", + "EventPosition", + ] + phsp.output_filename = "phsp_test_" + passive_collimation + ".root" + + # ===================================================== + # PHYSICS + # ===================================================== + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" + sim.physics_manager.set_production_cut("world", "all", 2 * mm) + + # ===================================================== + # SOURCE + # ===================================================== + + source = fun.add_source(sim, number_of_events) + + return sim + + +def analyze_dose(path_reference_dose, path_test_dose): + reference_pdd = fun.obtain_pdd_from_image(path_reference_dose) + test_pdd = fun.obtain_pdd_from_image(path_test_dose) + is_ok, mae = fun.evaluate_pdd_similarity(reference_pdd, test_pdd) + return (is_ok, mae) + + +def analyze_root(paths, path_reference_root_phsp, path_test_root_phsp): + keys = [ + "KineticEnergy", + "EventPosition_X", + "EventPosition_Y", + ] + tols = [0.8, 0.8, 0.8] + br = "PhaseSpace;1" + name = "test_EF_" + str(path_reference_root_phsp).split("_")[-1] + ".png" + is_ok = utility.compare_root3( + path_reference_root_phsp, + path_test_root_phsp, + br, + br, + keys, + keys, + tols, + None, + None, + paths.output / name, + nb_bins=150, + hits_tol=10**6, + ) + return is_ok diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py new file mode 100644 index 000000000..7d9109367 --- /dev/null +++ b/opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py @@ -0,0 +1,30 @@ +import opengate as gate +from test097_electron_flash_linac_helper import * +from opengate.tests import utility + +# test_ElectronFlash_dose_app40.py + + +if __name__ == "__main__": + # ===================================================== + # INITIALISATION + # ===================================================== + + paths = utility.get_default_test_paths( + __file__, output_folder="test097_electron_flash_linac" + ) + + sim = create_electron_flash_simulation( + paths, passive_collimation="app40", fantom="WaterBox" + ) + + sim.run() + + # ===================================================== + # Perform test + # ===================================================== + + path_reference_dose = paths.output_ref / "dose_reference_app40_dose.mhd" + path_test_dose = sim.get_actor("dose").dose.get_output_path() + is_ok, mae = analyze_dose(path_reference_dose, path_test_dose) + utility.test_ok(is_ok) diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py new file mode 100644 index 000000000..f75524fe6 --- /dev/null +++ b/opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py @@ -0,0 +1,30 @@ +import opengate as gate +from opengate.tests.src.test088_emcalc_actor import is_ok +from test097_electron_flash_linac_helper import * +from opengate.tests import utility +from opengate.contrib.linacs.ElectronFlash.electron_flash import * + +# test_ElectronFlash_dose_app100 + + +if __name__ == "__main__": + # ===================================================== + # INITIALISATION + # ===================================================== + + paths = utility.get_default_test_paths( + __file__, output_folder="test097_electron_flash_linac" + ) + + sim = create_electron_flash_simulation(paths, passive_collimation="app100") + + sim.run() + + # ===================================================== + # Perform test + # ===================================================== + + path_reference_dose = paths.output_ref / "dose_reference_app100_dose.mhd" + path_test_dose = sim.get_actor("dose").dose.get_output_path() + is_ok, mae = analyze_dose(path_reference_dose, path_test_dose) + utility.test_ok(is_ok) diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py new file mode 100644 index 000000000..60a54637e --- /dev/null +++ b/opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py @@ -0,0 +1,27 @@ +import opengate as gate +from test097_electron_flash_linac_helper import * +from opengate.tests import utility +from opengate.contrib.linacs.ElectronFlash.electron_flash import * + +# test MB slit + +if __name__ == "__main__": + # ===================================================== + # INITIALISATION + # ===================================================== + paths = utility.get_default_test_paths( + __file__, output_folder="test097_electron_flash_linac" + ) + + sim = create_electron_flash_simulation(paths, passive_collimation="mb_40_slit_11") + + sim.run() + + # ===================================================== + # Perform test + # ===================================================== + + path_reference_dose = paths.output_ref / "dose_reference_mb_40_slit_11_dose.mhd" + path_test_dose = sim.get_actor("dose").dose.get_output_path() + is_ok, mae = analyze_dose(path_reference_dose, path_test_dose) + utility.test_ok(is_ok) diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py new file mode 100644 index 000000000..0d46a6215 --- /dev/null +++ b/opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py @@ -0,0 +1,27 @@ +import opengate as gate +from test097_electron_flash_linac_helper import * +from opengate.tests import utility +from opengate.contrib.linacs.ElectronFlash.electron_flash import * + +# test MB slit + +if __name__ == "__main__": + # ===================================================== + # INITIALISATION + # ===================================================== + paths = utility.get_default_test_paths( + __file__, output_folder="test097_electron_flash_linac" + ) + + sim = create_electron_flash_simulation(paths, passive_collimation="shaper40") + + sim.run() + + # ===================================================== + # Perform test + # ===================================================== + + path_reference_dose = paths.output_ref / "dose_reference_shaper40_dose.mhd" + path_test_dose = sim.get_actor("dose").dose.get_output_path() + is_ok, mae = analyze_dose(path_reference_dose, path_test_dose) + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test099_ElectronFlash_linac_geometry.py b/opengate/tests/src/advanced_tests/electron_flash/test097e_electron_flash_linac.py similarity index 67% rename from opengate/tests/src/test099_ElectronFlash_linac_geometry.py rename to opengate/tests/src/advanced_tests/electron_flash/test097e_electron_flash_linac.py index 125acf7bd..d46bc5c70 100644 --- a/opengate/tests/src/test099_ElectronFlash_linac_geometry.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097e_electron_flash_linac.py @@ -1,38 +1,38 @@ import opengate as gate import math -from scipy.spatial.transform import Rotation as R -import function as fun - +import opengate.contrib.linacs.ElectronFlash.electron_flash as fun + def test_geometry_length(_geometry_string): sim = gate.Simulation() expected_lengths = { - "ElectronFlash" : 316.755, - "app100" : 784.8, - "app40" : 409.8, - "shaper40" : 63.0, + "ElectronFlash": 316.755, + "app100": 784.8, + "app40": 409.8, + "shaper40": 63.0, "mb_40_holes_11": 30.0, - "mb_40_slit_11" : 30.0 + "mb_40_slit_11": 30.0, } if _geometry_string == "ElectronFlash": total_length = fun.build_ElectronFlash(sim) elif _geometry_string == "shaper40": - total_length, (_, _, _, _) = fun.build_passive_collimation(sim, _geometry_string, center_z=53) + total_length, (_, _, _, _) = fun.build_passive_collimation( + sim, _geometry_string, center_z=53 + ) elif _geometry_string in ("mb_40_holes_11", "mb_40_slit_11"): total_length = fun.build_passive_collimation(sim, _geometry_string, center_z=25) else: total_length = fun.build_passive_collimation(sim, _geometry_string) expected = expected_lengths.get(_geometry_string) assert expected is not None, f"Unknown geometry: {_geometry_string}" - assert math.isclose(total_length, expected, rel_tol=1e-5), \ - f"Length mismatch for {_geometry_string}: expected {expected}, got {total_length}" - + assert math.isclose( + total_length, expected, rel_tol=1e-5 + ), f"Length mismatch for {_geometry_string}: expected {expected}, got {total_length}" + - test_geometry_length("ElectronFlash") test_geometry_length("app100") test_geometry_length("app40") test_geometry_length("shaper40") test_geometry_length("mb_40_holes_11") test_geometry_length("mb_40_slit_11") - \ No newline at end of file diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097f_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097f_electron_flash_linac.py new file mode 100644 index 000000000..09b3338c1 --- /dev/null +++ b/opengate/tests/src/advanced_tests/electron_flash/test097f_electron_flash_linac.py @@ -0,0 +1,24 @@ +import opengate as gate +from test097_electron_flash_linac_helper import * +from opengate.tests import utility + +if __name__ == "__main__": + paths = utility.get_default_test_paths( + __file__, output_folder="test097_electron_flash_linac" + ) + + sim = create_electron_flash_simulation( + paths, passive_collimation="app40", fantom="Phasespace_plane" + ) + + sim.run() + + # ===================================================== + # Perform test + # ===================================================== + + path_reference_root_phsp = paths.output_ref / "phsp_reference_app40.root" + path_test_root_phsp = sim.get_actor("PhaseSpace").get_output_path() + is_ok = analyze_root(paths, path_reference_root_phsp, path_test_root_phsp) + + utility.test_ok(is_ok) diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097g_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097g_electron_flash_linac.py new file mode 100644 index 000000000..db9f017f4 --- /dev/null +++ b/opengate/tests/src/advanced_tests/electron_flash/test097g_electron_flash_linac.py @@ -0,0 +1,24 @@ +import opengate as gate +from test097_electron_flash_linac_helper import * +from opengate.tests import utility + +if __name__ == "__main__": + paths = utility.get_default_test_paths( + __file__, output_folder="test097_electron_flash_linac" + ) + + sim = create_electron_flash_simulation( + paths, passive_collimation="app100", fantom="Phasespace_plane" + ) + + sim.run() + + # ===================================================== + # Perform test + # ===================================================== + + path_reference_root_phsp = paths.output_ref / "phsp_reference_app100.root" + path_test_root_phsp = sim.get_actor("PhaseSpace").get_output_path() + is_ok = analyze_root(paths, path_reference_root_phsp, path_test_root_phsp) + + utility.test_ok(is_ok) diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097h_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097h_electron_flash_linac.py new file mode 100644 index 000000000..3f7bb36a2 --- /dev/null +++ b/opengate/tests/src/advanced_tests/electron_flash/test097h_electron_flash_linac.py @@ -0,0 +1,24 @@ +import opengate as gate +from test097_electron_flash_linac_helper import * +from opengate.tests import utility + +if __name__ == "__main__": + paths = utility.get_default_test_paths( + __file__, output_folder="test097_electron_flash_linac" + ) + + sim = create_electron_flash_simulation( + paths, passive_collimation="nose", fantom="Phasespace_plane" + ) + + sim.run() + + # ===================================================== + # Perform test + # ===================================================== + + path_reference_root_phsp = paths.output_ref / "phsp_reference_nose.root" + path_test_root_phsp = sim.get_actor("PhaseSpace").get_output_path() + is_ok = analyze_root(paths, path_reference_root_phsp, path_test_root_phsp) + + utility.test_ok(is_ok) diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097i_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097i_electron_flash_linac.py new file mode 100644 index 000000000..9e8b30d50 --- /dev/null +++ b/opengate/tests/src/advanced_tests/electron_flash/test097i_electron_flash_linac.py @@ -0,0 +1,24 @@ +import opengate as gate +from test097_electron_flash_linac_helper import * +from opengate.tests import utility + +if __name__ == "__main__": + paths = utility.get_default_test_paths( + __file__, output_folder="test097_electron_flash_linac" + ) + + sim = create_electron_flash_simulation( + paths, passive_collimation="shaper40", fantom="Phasespace_plane" + ) + + sim.run() + + # ===================================================== + # Perform test + # ===================================================== + + path_reference_root_phsp = paths.output_ref / "phsp_reference_shaper40.root" + path_test_root_phsp = sim.get_actor("PhaseSpace").get_output_path() + is_ok = analyze_root(paths, path_reference_root_phsp, path_test_root_phsp) + + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test095_ElectronFlash_linac.py b/opengate/tests/src/test095_ElectronFlash_linac.py deleted file mode 100644 index 5ed40b45c..000000000 --- a/opengate/tests/src/test095_ElectronFlash_linac.py +++ /dev/null @@ -1,111 +0,0 @@ -import opengate as gate -import numpy as np -import function as fun -import os, sys, logging -from function import mm -from opengate.tests import utility - -# test_ElectronFlash_dose_app40.py - - -if __name__ == "__main__": - #===================================================== - # INITIALISATION - #===================================================== - - - logger = logging.getLogger(__name__) - logger.setLevel(logging.INFO) - - number_of_total_events = 500_000 - - paths = utility.get_default_test_paths(__file__, output_folder="test095_ElectronFlash_linac") - - sim = gate.Simulation() - sim.verbose_level = gate.logger.RUN - sim.running_verbose_level = gate.logger.RUN - sim.g4_verbose = False - sim.g4_verbose_level = 1 - sim.visu = False - sim.visu_type = "qt" - sim.random_engine = "MersenneTwister" - sim.random_seed = 18101996 - sim.output_dir = paths.output - sim.number_of_threads = 3 - sim.progress_bar = True - if sim.visu: - sim.number_of_threads = 1 - number_of_total_events = 1 - number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 - - sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") - - #===================================================== - # GEOMETRY - #===================================================== - - EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) - App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) - - #===================================================== - # PHANTOMS - #===================================================== - - dim_x, dim_y, dim_z = 250*mm, 250*mm, 60*mm - dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=App40_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) - - #===================================================== - # ACTORS - #===================================================== - - dose = sim.add_actor("DoseActor", "dose") - dose.attached_to = "Waterbox" - dose.output_filename = "dose_test_app40.mhd" - dose.hit_type = "random" - dose.size = [120, 120, 30] # Number of voxels - dose.spacing = [1 * mm, 1 * mm, 2 * mm] # Voxel size - dose.dose.active = True - dose.dose_uncertainty.active = False - dose.edep_uncertainty.active = False - - - #===================================================== - # PHYSICS - #===================================================== - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" - sim.physics_manager.set_production_cut("world", "all", 2*mm) - - - #===================================================== - # SOURCE - #===================================================== - - source = fun.add_source(sim, number_of_events) - - - #===================================================== - # START BEAMS - #===================================================== - - sim.run() - - - #===================================================== - # Perform test - #===================================================== - - path_reference_dose = paths.output_ref / "dose_reference_app40_dose.mhd" - path_test_dose = dose.dose.get_output_path() - - reference_pdd = fun.obtain_pdd_from_image(path_reference_dose) - test_pdd = fun.obtain_pdd_from_image(path_test_dose) - is_ok, mae = fun.evaluate_pdd_similarity(reference_pdd, test_pdd) - - if is_ok: - print("test_ElectronFlash_dose_app40.py : PDD comparison test passed.") - else: - print("test_ElectronFlash_dose_app40.py : PDD comparison test failed. MAE = {:.3f}".format(mae)) - - - \ No newline at end of file diff --git a/opengate/tests/src/test096_ElectronFlash_linac.py b/opengate/tests/src/test096_ElectronFlash_linac.py deleted file mode 100644 index b59f54d11..000000000 --- a/opengate/tests/src/test096_ElectronFlash_linac.py +++ /dev/null @@ -1,123 +0,0 @@ -import opengate as gate -import numpy as np -import function as fun -import os, sys, logging -import SimpleITK as sitk -from function import mm -from opengate.tests import utility - -# test_ElectronFlash_dose_app100 - - -if __name__ == "__main__": - #===================================================== - # INITIALISATION - #===================================================== - - # test_ElectronFlash_dose_app40.py - - logger = logging.getLogger(__name__) - logger.setLevel(logging.INFO) - - number_of_total_events = 500_000 - - paths = utility.get_default_test_paths(__file__, output_folder="test096_ElectronFlash_linac") - - sim = gate.Simulation() - sim.verbose_level = gate.logger.RUN - sim.running_verbose_level = gate.logger.RUN - sim.g4_verbose = False - sim.g4_verbose_level = 1 - sim.visu = False - sim.visu_type = "qt" - sim.random_engine = "MersenneTwister" - sim.random_seed = 18101996 - sim.output_dir = paths.output - sim.number_of_threads = 3 - sim.progress_bar = True - if sim.visu: - sim.number_of_threads = 1 - number_of_total_events = 1 - number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 - - sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") - - - #===================================================== - # GEOMETRY - #===================================================== - - EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) - App100_end = fun.build_passive_collimation(sim, "app100", center_z=EF_end, material_colors=fun.material_colors) - - #===================================================== - # PHANTOMS - #===================================================== - - dim_x, dim_y, dim_z = 250*mm, 250*mm, 60*mm - dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=App100_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) - - #===================================================== - # ACTORS - #===================================================== - - dose = sim.add_actor("DoseActor", "dose") - dose.attached_to = "Waterbox" - dose.output_filename = "dose_test_app100.mhd" - dose.hit_type = "random" - dose.size = [120, 120, 30] # Number of voxels - dose.spacing = [1 * mm, 1 * mm, 2 * mm] # Voxel size - dose.dose.active = True - dose.dose_uncertainty.active = False - dose.edep_uncertainty.active = False - - - #===================================================== - # PHYSICS - #===================================================== - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" - sim.physics_manager.set_production_cut("world", "all", 2*mm) - - - #===================================================== - # SOURCE - #===================================================== - - source = fun.add_source(sim, number_of_events) - - - #===================================================== - # START BEAMS - #===================================================== - - sim.run() - - - #===================================================== - # Perform test - #===================================================== - - path_reference_dose = paths.output_ref / "dose_reference_app100_dose.mhd" - path_test_dose = dose.dose.get_output_path() - - reference_pdd = fun.obtain_pdd_from_image(path_reference_dose) - test_pdd = fun.obtain_pdd_from_image(path_test_dose) - is_ok, mae = fun.evaluate_pdd_similarity(reference_pdd, test_pdd) - - if is_ok: - print("test002_ElectronFlash_linac : PDD comparison test passed.") - else: - print("test002_ElectronFlash_linac : PDD comparison test failed. MAE = {:.3f}".format(mae)) - - - - - - - - - - - - \ No newline at end of file diff --git a/opengate/tests/src/test097_ElectronFlash_linac.py b/opengate/tests/src/test097_ElectronFlash_linac.py deleted file mode 100644 index 06ecb9a4b..000000000 --- a/opengate/tests/src/test097_ElectronFlash_linac.py +++ /dev/null @@ -1,118 +0,0 @@ -import opengate as gate -import numpy as np -import function as fun -import os, sys, logging -from function import mm -from opengate.tests import utility -import SimpleITK as sitk - -# test MB slit - -if __name__ == "__main__": - #===================================================== - # INITIALISATION - #===================================================== - - logger = logging.getLogger(__name__) - logger.setLevel(logging.INFO) - - - number_of_total_events = 500_000 - - paths = utility.get_default_test_paths(__file__, output_folder="test097_ElectronFlash_linac") - - sim = gate.Simulation() - sim.verbose_level = gate.logger.RUN - sim.running_verbose_level = gate.logger.RUN - sim.g4_verbose = False - sim.g4_verbose_level = 1 - sim.visu = False - sim.visu_type = "qt" - sim.random_engine = "MersenneTwister" - sim.random_seed = 18101996 - sim.output_dir = paths.output - sim.number_of_threads = 3 - sim.progress_bar = True - - if sim.visu: - sim.number_of_threads = 1 - number_of_total_events = 1 - number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 - - sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") - - - #===================================================== - # GEOMETRY - #===================================================== - - EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) - App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) - MB_end = fun.build_passive_collimation(sim, "mb_40_slit_11", center_z=App40_end, material_colors=fun.material_colors) - - - #===================================================== - # PHANTOMS - #===================================================== - - dim_x, dim_y, dim_z = 100*mm, 100*mm, 20*mm - dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=MB_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) - - #===================================================== - # ACTORS - #===================================================== - - dose = sim.add_actor("DoseActor", "dose") - dose.attached_to = "Waterbox" - dose.output_filename = "dose_test_MBslit.mhd" - dose.hit_type = "random" - dose.size = [250, 250, 20] # Number of voxels - dose.spacing = [0.1 * mm, 0.1 * mm, 1 * mm] # Voxel size - dose.dose.active = True - dose.dose_uncertainty.active = False - dose.edep_uncertainty.active = False - - - #===================================================== - # PHYSICS - #===================================================== - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" - sim.physics_manager.set_production_cut("world", "all", 2*mm) - - - #===================================================== - # SOURCE - #===================================================== - - source = fun.add_source(sim, number_of_events) - - - #===================================================== - # START BEAMS - #===================================================== - - sim.run() - - - #===================================================== - # Perform test - #===================================================== - - path_reference_dose = paths.output_ref / "dose_reference_MBslit_dose.mhd" - path_test_dose = dose.dose.get_output_path() - - reference_profile = fun.obtain_pdd_from_image(path_reference_dose) - test_profile = fun.obtain_pdd_from_image(path_test_dose) - is_ok, mae = fun.evaluate_profile_similarity(reference_profile, test_profile) - - if is_ok: - print("test003_ElectronFlash_linac.py : Profile comparison test passed.") - else: - print("test003_ElectronFlash_linac.py : Profile comparison test failed. MAE = {:.3f}".format(mae)) - - - - - - \ No newline at end of file diff --git a/opengate/tests/src/test098_ElectronFlash_linac.py b/opengate/tests/src/test098_ElectronFlash_linac.py deleted file mode 100644 index 14ee00684..000000000 --- a/opengate/tests/src/test098_ElectronFlash_linac.py +++ /dev/null @@ -1,114 +0,0 @@ -import opengate as gate -import numpy as np -import function as fun -import os, sys, logging -from function import mm -from opengate.tests import utility - -if __name__ == "__main__": - #===================================================== - # INITIALISATION - #===================================================== - - - logger = logging.getLogger(__name__) - logger.setLevel(logging.INFO) - - - number_of_total_events = 500_000 - - paths = utility.get_default_test_paths(__file__, output_folder="test098_ElectronFlash_linac") - - sim = gate.Simulation() - sim.verbose_level = gate.logger.RUN - sim.running_verbose_level = gate.logger.RUN - sim.g4_verbose = False - sim.g4_verbose_level = 1 - sim.visu = False - sim.visu_type = "qt" - sim.random_engine = "MersenneTwister" - sim.random_seed = 18101996 - sim.output_dir = paths.output - sim.number_of_threads = 3 - sim.progress_bar = True - - if sim.visu: - sim.number_of_threads = 1 - number_of_total_events = 1 - number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 - - sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") - - - #===================================================== - # GEOMETRY - #===================================================== - - EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) - App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) - shaper40_end, (leaf1, leaf2, leaf3, leaf4) = fun.build_passive_collimation(sim, "shaper40", center_z=App40_end, material_colors=fun.material_colors) - fun.set_shaper_aperture(leaf1, leaf2, leaf3, leaf4, aperture_x_mm = 25, aperture_y_mm = 35) - fun.rotate_leaves_around_z(leaf1, leaf2, leaf3, leaf4, angle_deg = 45) - - #===================================================== - # PHANTOMS - #===================================================== - - dim_x, dim_y, dim_z = 250*mm, 250*mm, 60*mm - dosephantom = fun.build_dosephantombox(sim, "Waterbox", "Water", center_z=shaper40_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) - - #===================================================== - # ACTORS - #===================================================== - - dose = sim.add_actor("DoseActor", "dose") - dose.attached_to = "Waterbox" - dose.output_filename = "dose_test_shaper40.mhd" - dose.hit_type = "random" - dose.size = [120, 120, 30] # Number of voxels - dose.spacing = [1 * mm, 1 * mm, 2 * mm] # Voxel size - dose.dose.active = True - dose.dose_uncertainty.active = False - dose.edep_uncertainty.active = False - - - #===================================================== - # PHYSICS - #===================================================== - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" - sim.physics_manager.set_production_cut("world", "all", 2*mm) - - - #===================================================== - # SOURCE - #===================================================== - - source = fun.add_source(sim, number_of_events) - - - #===================================================== - # START BEAMS - #===================================================== - - sim.run() - - #===================================================== - # Perform test - #===================================================== - - path_reference_dose = paths.output_ref / "dose_reference_shaper40_dose.mhd" - path_test_dose = dose.dose.get_output_path() - - reference_pdd = fun.obtain_pdd_from_image(path_reference_dose) - test_pdd = fun.obtain_pdd_from_image(path_test_dose) - is_ok, mae = fun.evaluate_pdd_similarity(reference_pdd, test_pdd) - - if is_ok: - print("test004_ElectronFlash_linac : PDD comparison test passed.") - else: - print("test004_ElectronFlash_linac : PDD comparison test failed. MAE = {:.3f}".format(mae)) - - - - \ No newline at end of file diff --git a/opengate/tests/src/test100_ElectronFlash_linac.py b/opengate/tests/src/test100_ElectronFlash_linac.py deleted file mode 100644 index ce28491da..000000000 --- a/opengate/tests/src/test100_ElectronFlash_linac.py +++ /dev/null @@ -1,114 +0,0 @@ -import opengate as gate -import numpy as np -import function as fun -import os, sys, logging -from function import mm -from opengate.tests import utility - -if __name__ == "__main__": - #===================================================== - # INITIALISATION - #===================================================== - - - logger = logging.getLogger(__name__) - logger.setLevel(logging.INFO) - - - number_of_total_events = 500_000 - - paths = utility.get_default_test_paths(__file__, output_folder="test100_ElectronFlash_linac") - - sim = gate.Simulation() - sim.verbose_level = gate.logger.RUN - sim.running_verbose_level = gate.logger.RUN - sim.g4_verbose = False - sim.g4_verbose_level = 1 - sim.visu = False - sim.visu_type = "qt" - sim.random_engine = "MersenneTwister" - sim.random_seed = 18101996 - sim.output_dir = paths.output - sim.number_of_threads = 3 - sim.progress_bar = True - - if sim.visu: - sim.number_of_threads = 1 - number_of_total_events = 1 - number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 - - sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") - - - #===================================================== - # GEOMETRY - #===================================================== - - EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) - App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) - - #===================================================== - # PHANTOMS - #===================================================== - - dim_x, dim_y, dim_z = 150*mm, 150*mm, 0.01*mm - phsp_plane = fun.build_dosephantombox(sim, "Phasespace_plane", "Air", center_z=App40_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) - - #===================================================== - # ACTORS - #===================================================== - - phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - phsp.attached_to = phsp_plane.name - phsp.attributes = ["KineticEnergy","PreDirection","EventPosition",] - phsp.output_filename = "phsp_test_app40.root" - - - #===================================================== - # PHYSICS - #===================================================== - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" - sim.physics_manager.set_production_cut("world", "all", 2*mm) - - - #===================================================== - # SOURCE - #===================================================== - - source = fun.add_source(sim, number_of_events) - - - #===================================================== - # START BEAMS - #===================================================== - - sim.run() - - #===================================================== - # Perform test - #===================================================== - - path_reference_root_phsp = paths.output_ref / "phsp_reference_app40.root" - path_test_root_phsp = phsp.get_output_path() - - keys = ["KineticEnergy" ,"PreDirection_X","PreDirection_Y","EventPosition_X","EventPosition_Y"] - tols = [0.8, 0.8, 0.8, 0.8, 0.8] - br = "PhaseSpace;1" - is_ok = utility.compare_root3( - path_reference_root_phsp, - path_test_root_phsp, - br, - br, - keys, - keys, - tols, - None, - None, - paths.output / "test_EF_app40.png", - nb_bins=150, - hits_tol = 10**6 - ) - - - \ No newline at end of file diff --git a/opengate/tests/src/test101_ElectronFlash_linac.py b/opengate/tests/src/test101_ElectronFlash_linac.py deleted file mode 100644 index 37f7460a7..000000000 --- a/opengate/tests/src/test101_ElectronFlash_linac.py +++ /dev/null @@ -1,115 +0,0 @@ -import opengate as gate -import numpy as np -import function as fun -import os, sys, logging -from function import mm -from opengate.tests import utility - -if __name__ == "__main__": - #===================================================== - # INITIALISATION - #===================================================== - - - logger = logging.getLogger(__name__) - logger.setLevel(logging.INFO) - - - number_of_total_events = 500_000 - - paths = utility.get_default_test_paths(__file__, output_folder="test101_ElectronFlash_linac") - - sim = gate.Simulation() - sim.verbose_level = gate.logger.RUN - sim.running_verbose_level = gate.logger.RUN - sim.g4_verbose = False - sim.g4_verbose_level = 1 - sim.visu = False - sim.visu_type = "qt" - sim.random_engine = "MersenneTwister" - sim.random_seed = 18101996 - sim.output_dir = paths.output - sim.number_of_threads = 3 - sim.progress_bar = True - - if sim.visu: - sim.number_of_threads = 1 - number_of_total_events = 1 - number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 - - sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") - - - #===================================================== - # GEOMETRY - #===================================================== - - EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) - App100_end = fun.build_passive_collimation(sim, "app100", center_z=EF_end, material_colors=fun.material_colors) - - #===================================================== - # PHANTOMS - #===================================================== - - dim_x, dim_y, dim_z = 150*mm, 150*mm, 0.01*mm - phsp_plane = fun.build_dosephantombox(sim, "Phasespace_plane", "Air", center_z=App100_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) - - #===================================================== - # ACTORS - #===================================================== - - phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - phsp.attached_to = phsp_plane.name - phsp.attributes = ["KineticEnergy","PreDirection","EventPosition",] - phsp.output_filename = "phsp_test_app100.root" - - - #===================================================== - # PHYSICS - #===================================================== - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" - sim.physics_manager.set_production_cut("world", "all", 2*mm) - - - #===================================================== - # SOURCE - #===================================================== - - source = fun.add_source(sim, number_of_events) - - - #===================================================== - # START BEAMS - #===================================================== - - sim.run() - - - #===================================================== - # Perform test - #===================================================== - - path_reference_root_phsp = paths.output_ref / "phsp_reference_app100.root" - path_test_root_phsp = phsp.get_output_path() - - keys = ["KineticEnergy" ,"PreDirection_X","PreDirection_Y","EventPosition_X","EventPosition_Y"] - tols = [0.8, 0.8, 0.8, 0.8, 0.8] - br = "PhaseSpace;1" - is_ok = utility.compare_root3( - path_reference_root_phsp, - path_test_root_phsp, - br, - br, - keys, - keys, - tols, - None, - None, - paths.output / "test_EF_app100.png", - nb_bins=150, - hits_tol = 10**6 - ) - - - \ No newline at end of file diff --git a/opengate/tests/src/test102_ElectronFlash_linac.py b/opengate/tests/src/test102_ElectronFlash_linac.py deleted file mode 100644 index 5f2a5fc4e..000000000 --- a/opengate/tests/src/test102_ElectronFlash_linac.py +++ /dev/null @@ -1,117 +0,0 @@ -import opengate as gate -import numpy as np -import function as fun -import os, sys, logging -from function import mm -from opengate.tests import utility - - - - -if __name__ == "__main__": - #===================================================== - # INITIALISATION - #===================================================== - - - logger = logging.getLogger(__name__) - logger.setLevel(logging.INFO) - - - number_of_total_events = 500_000 - - paths = utility.get_default_test_paths(__file__, output_folder="test102_ElectronFlash_linac") - - sim = gate.Simulation() - sim.verbose_level = gate.logger.RUN - sim.running_verbose_level = gate.logger.RUN - sim.g4_verbose = False - sim.g4_verbose_level = 1 - sim.visu = False - sim.visu_type = "qt" - sim.random_engine = "MersenneTwister" - sim.random_seed = 18101996 - sim.output_dir = paths.output - sim.number_of_threads = 3 - sim.progress_bar = True - - if sim.visu: - sim.number_of_threads = 1 - number_of_total_events = 1 - number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 - - sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") - - - #===================================================== - # GEOMETRY - #===================================================== - - ###### Build Linac from titanium window up to BLD - EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) - - - #===================================================== - # PHANTOMS - #===================================================== - - dim_x, dim_y, dim_z = 150*mm, 150*mm, 0.01*mm - phsp_plane = fun.build_dosephantombox(sim, "Phasespace_plane", "Air", center_z=EF_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) - - - #===================================================== - # ACTORS - #===================================================== - - - phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - phsp.attached_to = phsp_plane.name - phsp.attributes = ["KineticEnergy","PreDirection","EventPosition",] - phsp.output_filename = "phsp_test_nose.root" - - - #===================================================== - # PHYSICS - #===================================================== - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" - sim.physics_manager.set_production_cut("world", "all", 2*mm) - - - #===================================================== - # SOURCE - #===================================================== - - source = fun.add_source(sim, number_of_events) - - - #===================================================== - # START BEAMS - #===================================================== - - sim.run() - - #===================================================== - # Perform test - #===================================================== - - path_reference_root_phsp = paths.output_ref / "phsp_reference_nose.root" - path_test_root_phsp = phsp.get_output_path() - - keys = ["KineticEnergy" ,"PreDirection_X","PreDirection_Y","EventPosition_X","EventPosition_Y"] - tols = [0.8, 0.8, 0.8, 0.8, 0.8] - br = "PhaseSpace;1" - is_ok = utility.compare_root3( - path_reference_root_phsp, - path_test_root_phsp, - br, - br, - keys, - keys, - tols, - None, - None, - paths.output / "test_EF_nose.png", - nb_bins=150, - hits_tol = 10**6 - ) \ No newline at end of file diff --git a/opengate/tests/src/test103_ElectronFlash_linac.py b/opengate/tests/src/test103_ElectronFlash_linac.py deleted file mode 100644 index f9d100112..000000000 --- a/opengate/tests/src/test103_ElectronFlash_linac.py +++ /dev/null @@ -1,116 +0,0 @@ -import opengate as gate -import numpy as np -import function as fun -import os, sys, logging -from function import mm -from opengate.tests import utility - -if __name__ == "__main__": - #===================================================== - # INITIALISATION - #===================================================== - - - logger = logging.getLogger(__name__) - logger.setLevel(logging.INFO) - - - number_of_total_events = 500_000 - - paths = utility.get_default_test_paths(__file__, output_folder="test103_ElectronFlash_linac") - - sim = gate.Simulation() - sim.verbose_level = gate.logger.RUN - sim.running_verbose_level = gate.logger.RUN - sim.g4_verbose = False - sim.g4_verbose_level = 1 - sim.visu = False - sim.visu_type = "qt" - sim.random_engine = "MersenneTwister" - sim.random_seed = 18101996 - sim.output_dir = paths.output - sim.number_of_threads = 3 - sim.progress_bar = True - - if sim.visu: - sim.number_of_threads = 1 - number_of_total_events = 1 - number_of_events = int(number_of_total_events/sim.number_of_threads) + 1 - - sim.volume_manager.add_material_database(paths.data / "GateMaterials.db") - - - #===================================================== - # GEOMETRY - #===================================================== - - EF_end = fun.build_ElectronFlash(sim, material_colors=fun.material_colors) - App40_end = fun.build_passive_collimation(sim, "app40", center_z=EF_end, material_colors=fun.material_colors) - shaper40_end, (leaf1, leaf2, leaf3, leaf4) = fun.build_passive_collimation(sim, "shaper40", center_z=App40_end, material_colors=fun.material_colors) - fun.set_shaper_aperture(leaf1, leaf2, leaf3, leaf4, aperture_x_mm = 25, aperture_y_mm = 35) - fun.rotate_leaves_around_z(leaf1, leaf2, leaf3, leaf4, angle_deg = 45) - #===================================================== - # PHANTOMS - #===================================================== - - dim_x, dim_y, dim_z = 150*mm, 150*mm, 0.01*mm - phsp_plane = fun.build_dosephantombox(sim, "Phasespace_plane", "Air", center_z=shaper40_end+dim_z/2,dimension_x=dim_x, dimension_y=dim_y, dimension_z=dim_z, material_colors=fun.material_colors) - - #===================================================== - # ACTORS - #===================================================== - - phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - phsp.attached_to = phsp_plane.name - phsp.attributes = ["KineticEnergy","PreDirection","EventPosition",] - phsp.output_filename = "phsp_test_shaper40.root" - - - #===================================================== - # PHYSICS - #===================================================== - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" - sim.physics_manager.set_production_cut("world", "all", 2*mm) - - - #===================================================== - # SOURCE - #===================================================== - - source = fun.add_source(sim, number_of_events) - - - #===================================================== - # START BEAMS - #===================================================== - - sim.run() - - #===================================================== - # Perform test - #===================================================== - - path_reference_root_phsp = paths.output_ref / "phsp_reference_shaper40.root" - path_test_root_phsp = phsp.get_output_path() - - keys = ["KineticEnergy" ,"PreDirection_X","PreDirection_Y","EventPosition_X","EventPosition_Y"] - tols = [0.8, 0.8, 0.8, 0.8, 0.8] - br = "PhaseSpace;1" - is_ok = utility.compare_root3( - path_reference_root_phsp, - path_test_root_phsp, - br, - br, - keys, - keys, - tols, - None, - None, - paths.output / "test_EF_shaper40.png", - nb_bins=150, - hits_tol = 10**6 - ) - - - \ No newline at end of file From 0090e722dda12b66d07f2081ebdea0dfaf336840 Mon Sep 17 00:00:00 2001 From: Thomas BAUDIER Date: Sat, 20 Dec 2025 13:14:21 +0100 Subject: [PATCH 7/8] lint electron flash Remove source.position.cente and sigma_r --- .../contrib/linacs/electron_flash/electron_flash.py | 11 ++++++----- opengate/tests/src/advanced_tests/__init__.py | 0 .../src/advanced_tests/electron_flash/__init__.py | 0 .../test097_electron_flash_linac_helper.py | 6 +++--- .../electron_flash/test097a_electron_flash_linac.py | 3 ++- .../electron_flash/test097b_electron_flash_linac.py | 9 +++++---- .../electron_flash/test097c_electron_flash_linac.py | 8 +++++--- .../electron_flash/test097d_electron_flash_linac.py | 8 +++++--- .../electron_flash/test097e_electron_flash_linac.py | 5 +++-- .../electron_flash/test097f_electron_flash_linac.py | 3 ++- .../electron_flash/test097g_electron_flash_linac.py | 3 ++- .../electron_flash/test097h_electron_flash_linac.py | 3 ++- .../electron_flash/test097i_electron_flash_linac.py | 3 ++- 13 files changed, 37 insertions(+), 25 deletions(-) create mode 100644 opengate/tests/src/advanced_tests/__init__.py create mode 100644 opengate/tests/src/advanced_tests/electron_flash/__init__.py diff --git a/opengate/contrib/linacs/electron_flash/electron_flash.py b/opengate/contrib/linacs/electron_flash/electron_flash.py index f74612af0..f7f8b952e 100644 --- a/opengate/contrib/linacs/electron_flash/electron_flash.py +++ b/opengate/contrib/linacs/electron_flash/electron_flash.py @@ -1,9 +1,12 @@ -import opengate as gate +import logging +import os +import sys + import numpy as np -import os, sys, logging -from scipy.spatial.transform import Rotation as R import SimpleITK as sitk +from scipy.spatial.transform import Rotation as R +import opengate as gate logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -776,8 +779,6 @@ def add_source(sim, number_of_events): ) source.position.type = "disc" source.position.radius = 3 * mm - source.position.sigma_r = 0.8 * mm - source.position.centre = [0, 0, 0] source.direction.type = "momentum" source.direction.momentum = [0, 0, 1] diff --git a/opengate/tests/src/advanced_tests/__init__.py b/opengate/tests/src/advanced_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/opengate/tests/src/advanced_tests/electron_flash/__init__.py b/opengate/tests/src/advanced_tests/electron_flash/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py b/opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py index 1e6dfa0a8..098c99e96 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py @@ -85,10 +85,10 @@ def create_electron_flash_simulation(paths, passive_collimation, fantom): # ===================================================== # PHANTOMS # ===================================================== - if fantom == "Waterbox": + if fantom == "WaterBox": dosephantom = fun.build_dosephantombox( sim, - "Waterbox", + "WaterBox", "Water", center_z=app_end + dim_z / 2, dimension_x=dim_x, @@ -98,7 +98,7 @@ def create_electron_flash_simulation(paths, passive_collimation, fantom): ) dose = sim.add_actor("DoseActor", "dose") - dose.attached_to = "Waterbox" + dose.attached_to = "WaterBox" dose.output_filename = "dose_test_" + passive_collimation + ".mhd" dose.hit_type = "random" dose.size = [120, 120, 30] # Number of voxels diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py index 7d9109367..ddb5b6d76 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py @@ -1,5 +1,6 @@ -import opengate as gate from test097_electron_flash_linac_helper import * + +import opengate as gate from opengate.tests import utility # test_ElectronFlash_dose_app40.py diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py index f75524fe6..81f8992c7 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py @@ -1,8 +1,7 @@ -import opengate as gate -from opengate.tests.src.test088_emcalc_actor import is_ok from test097_electron_flash_linac_helper import * + +import opengate as gate from opengate.tests import utility -from opengate.contrib.linacs.ElectronFlash.electron_flash import * # test_ElectronFlash_dose_app100 @@ -16,7 +15,9 @@ __file__, output_folder="test097_electron_flash_linac" ) - sim = create_electron_flash_simulation(paths, passive_collimation="app100") + sim = create_electron_flash_simulation( + paths, passive_collimation="app100", fantom="WaterBox" + ) sim.run() diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py index 60a54637e..ac2bb6ce5 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py @@ -1,7 +1,7 @@ -import opengate as gate from test097_electron_flash_linac_helper import * + +import opengate as gate from opengate.tests import utility -from opengate.contrib.linacs.ElectronFlash.electron_flash import * # test MB slit @@ -13,7 +13,9 @@ __file__, output_folder="test097_electron_flash_linac" ) - sim = create_electron_flash_simulation(paths, passive_collimation="mb_40_slit_11") + sim = create_electron_flash_simulation( + paths, passive_collimation="mb_40_slit_11", fantom="WaterBox" + ) sim.run() diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py index 0d46a6215..0f3a9d949 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py @@ -1,7 +1,7 @@ -import opengate as gate from test097_electron_flash_linac_helper import * + +import opengate as gate from opengate.tests import utility -from opengate.contrib.linacs.ElectronFlash.electron_flash import * # test MB slit @@ -13,7 +13,9 @@ __file__, output_folder="test097_electron_flash_linac" ) - sim = create_electron_flash_simulation(paths, passive_collimation="shaper40") + sim = create_electron_flash_simulation( + paths, passive_collimation="shaper40", fantom="WaterBox" + ) sim.run() diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097e_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097e_electron_flash_linac.py index d46bc5c70..f8923f134 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097e_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097e_electron_flash_linac.py @@ -1,6 +1,7 @@ -import opengate as gate import math -import opengate.contrib.linacs.ElectronFlash.electron_flash as fun + +import opengate as gate +import opengate.contrib.linacs.electron_flash.electron_flash as fun def test_geometry_length(_geometry_string): diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097f_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097f_electron_flash_linac.py index 09b3338c1..5528e6aa0 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097f_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097f_electron_flash_linac.py @@ -1,5 +1,6 @@ -import opengate as gate from test097_electron_flash_linac_helper import * + +import opengate as gate from opengate.tests import utility if __name__ == "__main__": diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097g_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097g_electron_flash_linac.py index db9f017f4..44e9a904b 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097g_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097g_electron_flash_linac.py @@ -1,5 +1,6 @@ -import opengate as gate from test097_electron_flash_linac_helper import * + +import opengate as gate from opengate.tests import utility if __name__ == "__main__": diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097h_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097h_electron_flash_linac.py index 3f7bb36a2..de28616e2 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097h_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097h_electron_flash_linac.py @@ -1,5 +1,6 @@ -import opengate as gate from test097_electron_flash_linac_helper import * + +import opengate as gate from opengate.tests import utility if __name__ == "__main__": diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097i_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097i_electron_flash_linac.py index 9e8b30d50..913f116bd 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097i_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097i_electron_flash_linac.py @@ -1,5 +1,6 @@ -import opengate as gate from test097_electron_flash_linac_helper import * + +import opengate as gate from opengate.tests import utility if __name__ == "__main__": From a2813b7a8d55aee2939ff4d9bdbd199778536d99 Mon Sep 17 00:00:00 2001 From: Thomas BAUDIER Date: Mon, 5 Jan 2026 15:03:32 +0100 Subject: [PATCH 8/8] Update tests and data for electron flash --- opengate/tests/data | 2 +- .../test097_electron_flash_linac_helper.py | 6 +++--- .../electron_flash/test097a_electron_flash_linac.py | 11 ++++++++++- .../electron_flash/test097b_electron_flash_linac.py | 11 ++++++++++- .../electron_flash/test097c_electron_flash_linac.py | 12 ++++++++++-- .../electron_flash/test097d_electron_flash_linac.py | 12 ++++++++++-- opengate/tests/src/geometry/test007_volumes.py | 9 ++++++--- 7 files changed, 50 insertions(+), 13 deletions(-) diff --git a/opengate/tests/data b/opengate/tests/data index 3ebf12674..ddce79354 160000 --- a/opengate/tests/data +++ b/opengate/tests/data @@ -1 +1 @@ -Subproject commit 3ebf12674232452f3bbcc2b85d6790faaeefa1f8 +Subproject commit ddce79354d6ab39ccee3309f7d7cb926e50dfc45 diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py b/opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py index 098c99e96..1f4ece374 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097_electron_flash_linac_helper.py @@ -29,7 +29,7 @@ def create_electron_flash_simulation(paths, passive_collimation, fantom): sim.random_engine = "MersenneTwister" sim.random_seed = 18101996 sim.output_dir = paths.output - sim.number_of_threads = 3 + sim.number_of_threads = 1 sim.progress_bar = True if sim.visu: sim.number_of_threads = 1 @@ -145,10 +145,10 @@ def create_electron_flash_simulation(paths, passive_collimation, fantom): return sim -def analyze_dose(path_reference_dose, path_test_dose): +def analyze_dose(path_reference_dose, path_test_dose, tolerance=0.03): reference_pdd = fun.obtain_pdd_from_image(path_reference_dose) test_pdd = fun.obtain_pdd_from_image(path_test_dose) - is_ok, mae = fun.evaluate_pdd_similarity(reference_pdd, test_pdd) + is_ok, mae = fun.evaluate_pdd_similarity(reference_pdd, test_pdd, tolerance) return (is_ok, mae) diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py index ddb5b6d76..af09aae53 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097a_electron_flash_linac.py @@ -28,4 +28,13 @@ path_reference_dose = paths.output_ref / "dose_reference_app40_dose.mhd" path_test_dose = sim.get_actor("dose").dose.get_output_path() is_ok, mae = analyze_dose(path_reference_dose, path_test_dose) - utility.test_ok(is_ok) + tolerance = 0.15 + is_ok, mae = analyze_dose(path_reference_dose, path_test_dose, tolerance) + utility.test_ok( + is_ok, + f"Analyze dose between\n" + f"path_reference_dose {path_reference_dose} \n" + f"path_test_dose {path_test_dose} \n" + f"Mae: {mae:.2f} | (tolerance is {tolerance:.2f} ) \n\n" + f" ", + ) diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py index 81f8992c7..3985f3520 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097b_electron_flash_linac.py @@ -28,4 +28,13 @@ path_reference_dose = paths.output_ref / "dose_reference_app100_dose.mhd" path_test_dose = sim.get_actor("dose").dose.get_output_path() is_ok, mae = analyze_dose(path_reference_dose, path_test_dose) - utility.test_ok(is_ok) + tolerance = 0.20 + is_ok, mae = analyze_dose(path_reference_dose, path_test_dose, tolerance) + utility.test_ok( + is_ok, + f"Analyze dose between\n" + f"path_reference_dose {path_reference_dose} \n" + f"path_test_dose {path_test_dose} \n" + f"Mae: {mae:.2f} | (tolerance is {tolerance:.2f} ) \n\n" + f" ", + ) diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py index ac2bb6ce5..deb526554 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097c_electron_flash_linac.py @@ -25,5 +25,13 @@ path_reference_dose = paths.output_ref / "dose_reference_mb_40_slit_11_dose.mhd" path_test_dose = sim.get_actor("dose").dose.get_output_path() - is_ok, mae = analyze_dose(path_reference_dose, path_test_dose) - utility.test_ok(is_ok) + tolerance = 0.55 + is_ok, mae = analyze_dose(path_reference_dose, path_test_dose, tolerance) + utility.test_ok( + is_ok, + f"Analyze dose between\n" + f"path_reference_dose {path_reference_dose} \n" + f"path_test_dose {path_test_dose} \n" + f"Mae: {mae:.2f} | (tolerance is {tolerance:.2f} ) \n\n" + f" ", + ) diff --git a/opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py b/opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py index 0f3a9d949..014648971 100644 --- a/opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py +++ b/opengate/tests/src/advanced_tests/electron_flash/test097d_electron_flash_linac.py @@ -25,5 +25,13 @@ path_reference_dose = paths.output_ref / "dose_reference_shaper40_dose.mhd" path_test_dose = sim.get_actor("dose").dose.get_output_path() - is_ok, mae = analyze_dose(path_reference_dose, path_test_dose) - utility.test_ok(is_ok) + tolerance = 0.15 + is_ok, mae = analyze_dose(path_reference_dose, path_test_dose, tolerance) + utility.test_ok( + is_ok, + f"Analyze dose between\n" + f"path_reference_dose {path_reference_dose} \n" + f"path_test_dose {path_test_dose} \n" + f"Mae: {mae:.2f} | (tolerance is {tolerance:.2f} ) \n\n" + f" ", + ) diff --git a/opengate/tests/src/geometry/test007_volumes.py b/opengate/tests/src/geometry/test007_volumes.py index aa2c27481..3b208ddc0 100755 --- a/opengate/tests/src/geometry/test007_volumes.py +++ b/opengate/tests/src/geometry/test007_volumes.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import pathlib + +from scipy.spatial.transform import Rotation + import opengate as gate from opengate.tests import utility -from scipy.spatial.transform import Rotation -import pathlib def user_hook_volume(simulation_engine): @@ -53,14 +55,15 @@ def check_mat(se): assert len(mnist) == 309 # Geant4 11.1 assert mdb == [ "Vacuum", + "Tecapeek", "Nickel", "Gold", "Carbon", "Copper", "Aluminium", + "AluminiumEGS", "Titanium", "Beryllium", - "AluminiumEGS", "Uranium", "Silicon", "Zinc",