The Radiation Model plugin provides GPU-accelerated ray tracing for radiation simulation using OptiX. This documentation is based on the actual implementation.
Overview
The RadiationModel class provides advanced radiation modeling and ray tracing capabilities for realistic light interaction simulations in plant canopies and scenes.
Requirements
The radiation plugin requires:
- NVIDIA GPU with CUDA support
- CUDA Toolkit installed
- OptiX runtime (bundled with PyHelios)
Basic Usage
from pyhelios import Context, RadiationModel
context = Context()
patch_uuid = context.addPatch(
center=vec3(0, 0, 0),
size=vec2(2, 2),
color=RGBcolor(0.3, 0.7, 0.2)
)
with RadiationModel(context) as radiation:
radiation.addRadiationBand("PAR")
source_id = radiation.addCollimatedRadiationSource()
radiation.setSourceFlux(source_id, "PAR", 1000.0)
radiation.setDirectRayCount("PAR", 100)
radiation.setDiffuseRayCount("PAR", 300)
radiation.runBand("PAR")
results = radiation.getTotalAbsorbedFlux()
print(f"Total absorbed flux: {sum(results)} W")
Radiation Bands
Basic Band Management
radiation.addRadiationBand("PAR")
radiation.addRadiationBand("NIR")
radiation.addRadiationBand("SW")
radiation.addRadiationBand("custom", wavelength_min=400.0, wavelength_max=700.0)
radiation.copyRadiationBand("PAR", "PAR_copy")
Common Radiation Bands
bands = {
"PAR": "Photosynthetically Active Radiation (400-700 nm)",
"NIR": "Near Infrared (700-1100 nm)",
"SW": "Shortwave (300-3000 nm)",
"UV": "Ultraviolet (280-400 nm)",
"VIS": "Visible (380-750 nm)"
}
for band, description in bands.items():
radiation.addRadiationBand(band)
print(f"Added {band}: {description}")
Radiation Sources
Collimated Sources
source_id = radiation.addCollimatedRadiationSource()
source_id = radiation.addCollimatedRadiationSource(
direction=(0.3, 0.3, -0.9)
)
source_id = radiation.addCollimatedRadiationSource(
direction=vec3(0.3, 0.3, -0.9)
)
source_id = radiation.addCollimatedRadiationSource(
direction=SphericalCoord(1.0, 45.0, 135.0)
)
radiation.setSourceFlux(source_id, "PAR", 1200.0)
Spherical Sources
source_id = radiation.addSphereRadiationSource(
position=(0, 0, 10),
radius=0.5
)
radiation.setSourceFlux(source_id, "PAR", 800.0)
Sun Sources
sun_id = radiation.addSunSphereRadiationSource(
radius=0.5,
zenith=45.0,
azimuth=180.0,
position_scaling=1.0,
angular_width=0.53,
flux_scaling=1.0
)
radiation.setSourceFlux(sun_id, "PAR", 1200.0)
Flux Configuration
Source Flux Management
radiation.setSourceFlux(source_id, "PAR", 1000.0)
source_ids = [source1, source2, source3]
radiation.setSourceFlux(source_ids, "PAR", 800.0)
current_flux = radiation.getSourceFlux(source_id, "PAR")
print(f"Source flux: {current_flux} W/m²")
radiation.setDiffuseRadiationFlux("PAR", 200.0)
Ray Configuration
Ray Count Settings
radiation.setDirectRayCount("PAR", 1000)
radiation.setDiffuseRayCount("PAR", 3000)
radiation.setDirectRayCount("PAR", 5000)
radiation.setDiffuseRayCount("PAR", 10000)
Ray Count Guidelines
ray_configs = {
"fast": {"direct": 100, "diffuse": 300},
"standard": {"direct": 1000, "diffuse": 3000},
"high_quality": {"direct": 5000, "diffuse": 15000},
"research": {"direct": 10000, "diffuse": 30000}
}
config = ray_configs["standard"]
radiation.setDirectRayCount("PAR", config["direct"])
radiation.setDiffuseRayCount("PAR", config["diffuse"])
Advanced Configuration
Scattering Control
radiation.setScatteringDepth("PAR", 3)
radiation.setMinScatterEnergy("PAR", 0.01)
Emission Control
radiation.enableEmission("thermal")
radiation.disableEmission("PAR")
Simulation Execution
Geometry Updates
radiation.updateGeometry()
radiation.updateGeometry([patch_uuid, triangle_uuid])
Running Simulations
CRITICAL PERFORMANCE NOTE: When simulating multiple radiation bands, ALWAYS run all bands in a single runBand()
call rather than sequential single-band calls. This provides significant computational efficiency gains (often 2-5x faster) because:
- GPU ray tracing setup is performed once for all bands
- Scene geometry acceleration structures are reused across bands
- OptiX kernel launches are batched together
- Memory transfers between CPU/GPU are minimized
- Ray traversal computations are shared between spectral bands
radiation.runBand(["PAR", "NIR", "SW"])
radiation.runBand("PAR")
radiation.runBand("NIR")
radiation.runBand("SW")
radiation.runBand("PAR")
Performance Comparison
import time
start_time = time.time()
radiation.runBand("PAR")
radiation.runBand("NIR")
radiation.runBand("SW")
sequential_time = time.time() - start_time
start_time = time.time()
radiation.runBand(["PAR", "NIR", "SW"])
multiband_time = time.time() - start_time
print(f"Sequential: {sequential_time:.2f}s")
print(f"Multi-band: {multiband_time:.2f}s")
print(f"Speedup: {sequential_time/multiband_time:.1f}x faster")
Results and Analysis
Flux Results
results = radiation.getTotalAbsorbedFlux()
total_absorption = sum(results)
print(f"Total absorbed flux: {total_absorption:.2f} W")
all_uuids = context.getAllUUIDs()
for i, uuid in enumerate(all_uuids):
if i < len(results):
flux = results[i]
area = context.getPrimitiveArea(uuid)
flux_density = flux / area if area > 0 else 0
print(f"Primitive {uuid}: {flux:.2f} W ({flux_density:.2f} W/m²)")
Radiation Analysis
radiation_data = radiation.getTotalAbsorbedFlux()
import statistics
mean_flux = statistics.mean(radiation_data)
max_flux = max(radiation_data)
min_flux = min(radiation_data)
std_flux = statistics.stdev(radiation_data)
print(f"Radiation statistics:")
print(f" Mean: {mean_flux:.2f} W")
print(f" Max: {max_flux:.2f} W")
print(f" Min: {min_flux:.2f} W")
print(f" Std Dev: {std_flux:.2f} W")
Complete Workflow Example
from pyhelios import Context, WeberPennTree, WPTType, RadiationModel
try:
context = Context()
ground_uuid = context.addPatch(
center=vec3(0, 0, 0),
size=vec2(10, 10),
color=RGBcolor(0.3, 0.2, 0.1)
)
wpt = WeberPennTree(context)
tree_id = wpt.buildTree(WPTType.LEMON)
with RadiationModel(context) as radiation:
radiation.addRadiationBand("PAR")
radiation.addRadiationBand("NIR")
radiation.addRadiationBand("SW")
sun_id = radiation.addSunSphereRadiationSource(
radius=0.5,
zenith=30.0,
azimuth=135.0
)
radiation.setSourceFlux(sun_id, "PAR", 600.0)
radiation.setSourceFlux(sun_id, "NIR", 500.0)
radiation.setSourceFlux(sun_id, "SW", 1200.0)
radiation.setDiffuseRadiationFlux("PAR", 100.0)
radiation.setDiffuseRadiationFlux("NIR", 80.0)
radiation.setDiffuseRadiationFlux("SW", 200.0)
for band in ["PAR", "NIR", "SW"]:
radiation.setDirectRayCount(band, 2000)
radiation.setDiffuseRayCount(band, 6000)
radiation.setScatteringDepth(band, 2)
radiation.updateGeometry()
radiation.runBand(["PAR", "NIR", "SW"])
results = radiation.getTotalAbsorbedFlux()
leaf_uuids = wpt.getLeafUUIDs(tree_id)
leaf_absorption = 0
all_uuids = context.getAllUUIDs()
for i, uuid in enumerate(all_uuids):
if uuid in leaf_uuids and i < len(results):
leaf_absorption += results[i]
print(f"Total scene absorption: {sum(results):.2f} W")
print(f"Leaf absorption: {leaf_absorption:.2f} W")
print(f"Ground absorption: {results[all_uuids.index(ground_uuid)]:.2f} W")
for band in ["PAR", "NIR", "SW"]:
print(f"Band {band} results available in primitive data: radiation_flux_{band}")
except RadiationModelError as e:
print(f"Radiation modeling failed: {e}")
print("Ensure NVIDIA GPU with CUDA support is available")
print("Check that radiation plugin is compiled: build_scripts/build_helios --plugins radiation")
except Exception as e:
print(f"Simulation setup failed: {e}")
print("Check geometry creation and tree generation parameters")
Data Storage
results = radiation.getTotalAbsorbedFlux()
all_uuids = context.getAllUUIDs()
for i, uuid in enumerate(all_uuids):
if i < len(results):
context.setPrimitiveDataFloat(uuid, "radiation_flux_PAR", results[i])
area = context.getPrimitiveArea(uuid)
flux_density = results[i] / area if area > 0 else 0
context.setPrimitiveDataFloat(uuid, "flux_density_PAR", flux_density)
context.colorPrimitiveByDataPseudocolor(
all_uuids, "flux_density_PAR", "hot", 256
)
Camera and Image Functions
Note: Camera functions are available in Helios core v1.3.47+ and PyHelios v0.0.4+
The RadiationModel now includes advanced camera functionality for generating synthetic images, object detection training data, and auto-calibrated imagery.
Camera Image Generation
filename = radiation.write_camera_image(
camera="overhead_camera",
bands=["Red", "Green", "Blue"],
imagefile_base="scene_rgb",
image_path="./images",
frame=-1
)
print(f"Camera image saved to: {filename}")
filename = radiation.write_norm_camera_image(
camera="side_camera",
bands=["NIR", "Red"],
imagefile_base="false_color",
image_path="./output"
)
radiation.write_camera_image_data(
camera="overhead_camera",
band="RGB",
imagefile_base="raw_data",
image_path="./data"
)
Object Detection Training Data
Generate YOLO-format bounding boxes for machine learning training:
radiation.write_image_bounding_boxes(
camera_label="training_camera",
primitive_data_labels="leaf_type",
object_class_ids=1,
image_file="training_001.jpg",
classes_txt_file="plant_classes.txt",
image_path="./annotations"
)
radiation.write_image_bounding_boxes(
camera_label="training_camera",
primitive_data_labels=["leaves", "stems", "fruits"],
object_class_ids=[0, 1, 2],
image_file="training_002.jpg",
classes_txt_file="classes.txt"
)
radiation.write_image_bounding_boxes(
camera_label="training_camera",
object_data_labels=["tree_1", "tree_2", "shrub_1"],
object_class_ids=[10, 10, 20],
image_file="training_003.jpg"
)
Segmentation Masks for Instance Segmentation
Generate COCO-format JSON files for semantic/instance segmentation:
radiation.write_image_segmentation_masks(
camera_label="segmentation_camera",
primitive_data_labels="plant_part",
object_class_ids=1,
json_filename="segmentation_001.json",
image_file="seg_image_001.jpg",
append_file=False
)
radiation.write_image_segmentation_masks(
camera_label="segmentation_camera",
primitive_data_labels=["leaf", "bark", "soil"],
object_class_ids=[1, 2, 3],
json_filename="multi_class_seg.json",
image_file="scene_segmentation.jpg",
append_file=True
)
radiation.write_image_segmentation_masks(
camera_label="segmentation_camera",
object_data_labels=["individual_plant_1", "individual_plant_2"],
object_class_ids=[100, 101],
json_filename="instance_segmentation.json",
image_file="plant_instances.jpg"
)
Auto-Calibrated Camera Images
Automatic color correction for realistic imagery:
filename = radiation.auto_calibrate_camera_image(
camera_label="rgb_camera",
red_band_label="Red",
green_band_label="Green",
blue_band_label="Blue",
output_file_path="auto_calibrated_image.jpg"
)
filename = radiation.auto_calibrate_camera_image(
camera_label="multispectral_camera",
red_band_label="Band_670nm",
green_band_label="Band_550nm",
blue_band_label="Band_450nm",
output_file_path="calibrated_multispectral.jpg",
print_quality_report=True,
algorithm="MATRIX_3X3_AUTO",
ccm_export_file_path="color_correction_matrix.txt"
)
print(f"Calibrated image saved to: {filename}")
Color Correction Algorithms
Choose the appropriate algorithm based on your needs:
algorithms = {
"DIAGONAL_ONLY": "Simple white balance correction (fastest)",
"MATRIX_3X3_AUTO": "Full 3x3 matrix with stability fallback (recommended)",
"MATRIX_3X3_FORCE": "Force 3x3 matrix even if potentially unstable"
}
for algorithm, description in algorithms.items():
filename = radiation.auto_calibrate_camera_image(
camera_label="test_camera",
red_band_label="R", green_band_label="G", blue_band_label="B",
output_file_path=f"calibrated_{algorithm.lower()}.jpg",
algorithm=algorithm
)
print(f"{algorithm}: {description} -> {filename}")
Complete Camera Pipeline Example
from pyhelios import Context, WeberPennTree, WPTType, RadiationModel
context = Context()
wpt = WeberPennTree(context)
tree_id = wpt.buildTree(WPTType.APPLE)
leaf_uuids = wpt.getLeafUUIDs(tree_id)
branch_uuids = wpt.getBranchUUIDs(tree_id)
for uuid in leaf_uuids:
context.setPrimitiveDataString(uuid, "plant_part", "leaf")
context.setPrimitiveDataString(uuid, "species", "apple")
for uuid in branch_uuids:
context.setPrimitiveDataString(uuid, "plant_part", "branch")
context.setPrimitiveDataString(uuid, "species", "apple")
ground = context.addPatch(center=vec3(0,0,0), size=vec2(10,10))
context.setPrimitiveDataString(ground, "plant_part", "soil")
try:
with RadiationModel(context) as radiation:
radiation.addRadiationBand("Red")
radiation.addRadiationBand("Green")
radiation.addRadiationBand("Blue")
radiation.addRadiationBand("NIR")
sun_id = radiation.addSunSphereRadiationSource(
radius=0.5, zenith=45.0, azimuth=180.0)
radiation.setSourceFlux(sun_id, "Red", 250.0)
radiation.setSourceFlux(sun_id, "Green", 350.0)
radiation.setSourceFlux(sun_id, "Blue", 200.0)
radiation.setSourceFlux(sun_id, "NIR", 400.0)
radiation.updateGeometry()
radiation.runBand(["Red", "Green", "Blue", "NIR"])
rgb_filename = radiation.write_camera_image(
camera="overhead_rgb",
bands=["Red", "Green", "Blue"],
imagefile_base="apple_tree_rgb"
)
nir_filename = radiation.write_camera_image(
camera="side_view",
bands=["NIR"],
imagefile_base="apple_tree_nir"
)
radiation.write_image_bounding_boxes(
camera_label="overhead_rgb",
primitive_data_labels=["leaf", "branch", "soil"],
object_class_ids=[0, 1, 2],
image_file=rgb_filename,
classes_txt_file="plant_classes.txt"
)
radiation.write_image_segmentation_masks(
camera_label="overhead_rgb",
primitive_data_labels=["leaf", "branch", "soil"],
object_class_ids=[0, 1, 2],
json_filename="apple_tree_segmentation.json",
image_file=rgb_filename
)
calibrated_filename = radiation.auto_calibrate_camera_image(
camera_label="overhead_rgb",
red_band_label="Red",
green_band_label="Green",
blue_band_label="Blue",
output_file_path="apple_tree_calibrated.jpg",
print_quality_report=True
)
print("Camera pipeline completed:")
print(f" RGB Image: {rgb_filename}")
print(f" NIR Image: {nir_filename}")
print(f" Calibrated: {calibrated_filename}")
print(f" Training data: plant_classes.txt + YOLO format labels")
print(f" Segmentation: apple_tree_segmentation.json")
except RadiationModelError as e:
print(f"Camera processing failed: {e}")
Camera Function Error Handling
try:
filename = radiation.write_camera_image(
camera="test_camera",
bands=["R", "G", "B"],
imagefile_base="test"
)
except TypeError as e:
print(f"Parameter error: {e}")
except ValueError as e:
print(f"Value error: {e}")
except RuntimeError as e:
print(f"Camera operation failed: {e}")
Error Handling
from pyhelios import RadiationModel
try:
radiation = RadiationModel(context)
except Exception as e:
print(f"RadiationModel initialization failed: {e}")
if "NVIDIA GPU" in str(e):
print("Ensure NVIDIA GPU with CUDA support is available")
elif "OptiX" in str(e):
print("OptiX runtime error - check graphics drivers")
elif "GPU" in str(e):
print(f"GPU initialization failed: {e}")
Build Requirements
# Build with radiation plugin
build_scripts/build_helios --plugins radiation
# Or use GPU profile
build_scripts/build_helios --plugins radiation
# Check if radiation is available
python -c "from pyhelios.plugins import get_plugin_registry; print(get_plugin_registry().is_plugin_available('radiation'))"
This documentation covers the actual RadiationModel implementation in PyHelios, verified against the wrapper code and high-level interface.