Skip to main content

Cone Beam Reconstruction and Void Analysis Workflow

Workflows 📥 Download Script

Automate CT Analysis in Volvicon — Reconstruction, Void Analysis, Reports.

This tutorial walks through the automated workflow for cone beam CT reconstruction, void analysis, and final report generation. It provides a step‑by‑step overview of how to process projection data, evaluate reconstructed volumes, and produce a analysis report.

Prerequisites

  • The Volvicon application must be running
  • Sample projection files suitable for cone beam reconstruction

Scripting Api Initialization

import ScriptingApi as api
import os

# Create Application instance
app = api.Application()

# Closing the current project, if there is one
app.close_project()

# Get the volume and analysis operations
volume_operations = app.get_volume_operations()
analysis_operations = app.get_analysis_operations()

Cone Beam CT Reconstruction

# Reconstruct 3D volume from cone beam CT projection images using FDK algorithm

# Helper function to create file paths with zero-padded numbering
def create_file_paths(directory, prefix, start, end, num_zeros, extension='.tif'):
file_paths = []
for i in range(start, end + 1):
padded_number = str(i).zfill(num_zeros)
filename = f"{prefix}{padded_number}{extension}"
full_path = os.path.join(directory, filename)
file_paths.append(full_path)
return file_paths

projection_files = create_file_paths(
directory="C:/Samples/Projections",
prefix="FewView_Object",
start=100001,
end=101001,
num_zeros=6
)

# Print the generated file paths (for verification)
for path in projection_files:
print(path)

# Configure reconstruction settings
cone_beam_reconstruction_settings = api.ConeBeamReconstructionSettings()

# Inversion settings (optional, for correcting projection orientation)
cone_beam_reconstruction_settings.invert_x = False # Mirror horizontally
cone_beam_reconstruction_settings.invert_y = False # Mirror vertically
cone_beam_reconstruction_settings.invert_z = False # Reverse projection order
cone_beam_reconstruction_settings.invert_intensity = False # Invert intensity values

# Geometry parameters (critical for accurate reconstruction)
cone_beam_reconstruction_settings.source_to_detector_distance = 815.19018878 # Distance from X-ray source to detector (mm)
cone_beam_reconstruction_settings.source_to_isocenter_distance = 153.48085 # Distance from source to object center (mm)
cone_beam_reconstruction_settings.detector_pixel_size_x = 0.4 # Detector pixel width (mm)
cone_beam_reconstruction_settings.detector_pixel_size_y = 0.4 # Detector pixel height (mm)
cone_beam_reconstruction_settings.xray_scan_start_angle = 0.0 # Starting scan angle (degrees)
cone_beam_reconstruction_settings.xray_scan_total_angle = 360.0 # Total scan arc (degrees, 360° for full scan)
cone_beam_reconstruction_settings.is_clockwise = False # Rotation direction (False = anti-clockwise)

# Projection offset parameters (for detector misalignment correction)
cone_beam_reconstruction_settings.projection_offset_x = 0.0 # Horizontal detector offset (mm)
cone_beam_reconstruction_settings.projection_offset_y = 0.0 # Vertical detector offset (mm)
cone_beam_reconstruction_settings.source_offset_x = 0.0 # Horizontal source offset (mm)
cone_beam_reconstruction_settings.source_offset_y = 0.0 # Vertical source offset (mm)
cone_beam_reconstruction_settings.out_of_plane_angle = 0.0 # Detector tilt around X axis (degrees)
cone_beam_reconstruction_settings.in_plane_angle = 0.0 # Detector tilt around Z axis (degrees)

# Displaced detector settings (for short scans)
cone_beam_reconstruction_settings.enable_displaced_detector = False # Enable for off-center detector
cone_beam_reconstruction_settings.angular_gap_threshold = 20.0 # Gap threshold for short scan detection (degrees)

# Preprocessing parameters
cone_beam_reconstruction_settings.enable_logarithm_conversion = True # Apply logarithmic transformation

# Reconstruction filter parameters
cone_beam_reconstruction_settings.truncation_correction = 1.0 # Truncation correction factor (>0 enables correction)
cone_beam_reconstruction_settings.hann_cut_frequency = 0.0 # Hann filter frequency (0.0-1.5, higher=sharper)

# Post-processing parameters
cone_beam_reconstruction_settings.auto_invert_intensity = False # Auto-invert if contrast is reversed
cone_beam_reconstruction_settings.auto_crop = True # Auto-crop to object bounds
cone_beam_reconstruction_settings.crop_margin = 10 # Margin around cropped object (pixels)

# Sharpening parameters (Unsharp Mask)
cone_beam_reconstruction_settings.enable_sharpening = True # Enable USM sharpening
cone_beam_reconstruction_settings.sharpening_iterations = 2 # Number of sharpening passes (1-5)
cone_beam_reconstruction_settings.sharpening_radius = 3 # Sharpening radius (1-5 pixels)
cone_beam_reconstruction_settings.sharpening_contrast = 50 # Sharpening amount (10-200 percent)

# Example 1: Reconstruct from projection files on disk
reconstructed_volume = volume_operations.reconstruct_volume_using_cone_beam_reconstruction(
"", # volumeName: empty string when using files
projection_files, # fileNames: list of projection file paths
cone_beam_reconstruction_settings # reconstruction parameters
)

print(f"Reconstructed volume from files: {reconstructed_volume}")

volume_operations.bilateral_filter([reconstructed_volume], 1, 8000, 2)

volume_render_properties_operations = volume_operations.get_render_properties_operations()
volume_render_properties_operations.set_blend_mode([reconstructed_volume], 0)
volume_render_properties_operations.set_shade_enabled([reconstructed_volume], True)

Void / Inclusion Analysis Workflow

void_inclusion_params = api.VoidInclusionParams()
void_inclusion_params.mode = api.VoidInclusionTargetType.Void
void_inclusion_params.method = api.VoidInclusionMethod.Absolute
void_inclusion_params.auto_absolute_contrast = True
void_inclusion_params.requested_statistics = [
"Volume (mm³)",
"Volume fraction (%)",
"Centroid X (mm)",
"Centroid Y (mm)",
"Centroid Z (mm)",
"Compactness",
"Sphericity",
"Equivalent diameter (mm)"
]

void_inclusion_filtering_params = api.VoidInclusionFilteringParams()
void_inclusion_filtering_params.enable_geometric_filtering = True
void_inclusion_filtering_params.probability_threshold = 0.0
void_inclusion_filtering_params.min_voxel_count = 27
void_inclusion_filtering_params.max_voxel_count = 10000000
void_inclusion_filtering_params.min_compactness = 0.0
void_inclusion_filtering_params.max_compactness = 1.0
void_inclusion_filtering_params.min_sphericity = 0.0
void_inclusion_filtering_params.max_sphericity = 1.0
void_inclusion_filtering_params.min_volume_fraction_percent = 0.0
void_inclusion_filtering_params.max_volume_fraction_percent = 100.0
void_inclusion_filtering_params.min_equivalent_diameter = 0.0
void_inclusion_filtering_params.max_equivalent_diameter = 1000000.0
void_inclusion_filtering_params.max_count = 100

void_inclusion_params.filter_result_params = void_inclusion_filtering_params

vi_name = analysis_operations.create_void_inclusion_analysis(
"VoidInclusion_1",
reconstructed_volume,
"", # No ROI mask
void_inclusion_params
)

# B. Running
if analysis_operations.run_analysis(vi_name):
print(f"Analysis run successfully: {vi_name}")
all_masks = app.get_all_mask_names()
if all_masks:
last_mask = all_masks[-1]
app.isolate_masks([last_mask])
app.set_mask_3d_preview_quality(api.Mask3dPreviewQuality.Optimal)
app.generate_mask_3d_preview(app.get_visible_mask_names())

app.set_volumes_visible([reconstructed_volume], False)

# C. Getting Info and Results
analysis_info = analysis_operations.get_analysis_info(vi_name)
print(f"Analysis info: name={analysis_info.name}, type={analysis_info.type}")

# Get defect results
void_inclusion_results = analysis_operations.get_void_inclusion_results(vi_name)
print(f"Void/Inclusion Stats:")
print(f" Total defects: {void_inclusion_results.total_defects_found}")
print(f" Total defect volume: {void_inclusion_results.total_defect_volume}")
print(f" Porosity: {void_inclusion_results.porosity}")
print(f" Label statistics count: {len(void_inclusion_results.label_statistics)}")

# Get descriptive statistics
print(f"descriptive_statistics_count: {len(void_inclusion_results.descriptive_statistics)}")
for VoidInclusionDescriptiveStatistic in void_inclusion_results.descriptive_statistics:
statType = VoidInclusionDescriptiveStatistic.statistic_type
stdDev = VoidInclusionDescriptiveStatistic.std_deviation
print(f"{statType}: {stdDev}")

# Get label statistics
print(f"label_statistics_count: {len(void_inclusion_results.label_statistics)}")
for VoidInclusionLabelStatistics in void_inclusion_results.label_statistics:
label = VoidInclusionLabelStatistics.label
for VoidInclusionStatistic in VoidInclusionLabelStatistics.statistics:
statType = VoidInclusionStatistic.statistic_type
value = VoidInclusionStatistic.values[0]
print(f"{label} | {statType}: {value}")

# D. Writing Results to Disk
output_dir = r'C:\Samples\Output'
os.makedirs(output_dir, exist_ok=True)
analysis_operations.write_void_inclusion_results_to_disk(void_inclusion_results, os.path.join(output_dir, 'void_inclusion.txt'))

# E. Changing Display Settings
analysis_display_settings = api.AnalysisDisplaySettings()
analysis_display_settings.lookup_table_type = api.LookupTableType.ReverseRainbow
analysis_operations.set_display_settings(vi_name, analysis_display_settings)

# F. Updating Analysis
analysis_operations.update_analysis(vi_name)

Post-Analysis Operations

# Save snapshots and the project to disk, then close the project to free up memory

app.save_snapshot_to_disk(api.SnapshotType.Application, 'C:/Samples/Application.png')
app.save_snapshot_to_disk(api.SnapshotType.Scene, 'C:/Samples/Scene.png')
app.save_snapshot_to_disk(api.SnapshotType.View3D, 'C:/Samples/View3D.png')

app.save_project("C:/Samples/cbct-workflow.vvcx")

app.close_project()

print("Analysis operations completed successfully.")