Volume Operations
Volume Operations Tutorial.
This tutorial demonstrates operations on 3D volume images including import, export, filtering, resampling, cropping, and transformation.
Prerequisites
- Volvicon application must be running
- Sample volume files for import examples
Listing Volumes​
all_volumes = app.get_all_volume_names()
print(f"Available volumes: {all_volumes}")
Importing Volumes​
# Supported formats: mha, mhd, nii, nii.gz, nrrd, nhdr, hdr, img.gz, gipl, lsm, vti
# Import single volume
# volume_name = volume_operations.import_3d_image_from_disk(r'C:\data\ct_scan.mha')
# print(f"Imported volume: {volume_name}")
# Import multiple volumes
# volume_names = volume_operations.import_3d_images_from_disk([
# r'C:\data\volume1.mha',
# r'C:\data\volume2.nii.gz'
# ])
# # Import a headerless (raw) volume file.
# # A raw file stores only the voxel values, so the layout has to be described here.
# # Every value must match how the file was written, otherwise the import fails.
# raw_options = api.RawVolumeImportOptions()
# raw_options.voxel_data_type = api.RawVoxelDataType.UnsignedInt16
# raw_options.dimension_x = 512
# raw_options.dimension_y = 512
# raw_options.dimension_z = 300
# raw_options.spacing_x = 0.4
# raw_options.spacing_y = 0.4
# raw_options.spacing_z = 0.8
# raw_options.header_size = 0 # bytes to skip before the voxel data starts
# raw_options.little_endian = True
# volume_name = volume_operations.import_raw_volume_from_disk(r'C:\data\scan.raw', raw_options)
# print(f"Imported raw volume: {volume_name}")
# # Discover DICOM series in a directory and import one selected series
# dicom_options = api.DicomSeriesImportOptions()
# dicom_series = volume_operations.get_dicom_series_in_directory(r'C:\data\dicom', dicom_options)
# for series in dicom_series:
# dicom_series_info : api.DicomSeriesInfo = series
# print(dicom_series_info.series_index, dicom_series_info.series_description, dicom_series_info.series_instance_uid)
# if dicom_series:
# # The first UID becomes the primary imported series when multiple compatible series are provided.
# dicom_series_info : api.DicomSeriesInfo = dicom_series[0]
# volume_name = volume_operations.import_dicom_series_from_directory(r'C:\data\dicom', [dicom_series_info.series_instance_uid], dicom_options)
# # Discover DICOM series in a directory and import multiple selected series (i.e. 4D time series)
# dicom_options = api.DicomSeriesImportOptions()
# dicom_series = volume_operations.get_dicom_series_in_directory(r'C:\data\dicom', dicom_options)
# series_instance_uids = []
# for series in dicom_series:
# dicom_series_info : api.DicomSeriesInfo = series
# series_instance_uids.append(dicom_series_info.series_instance_uid)
# print(dicom_series_info.series_index, dicom_series_info.series_description, dicom_series_info.series_instance_uid)
# print()
# if dicom_series:
# # The first UID controls the imported volume name, metadata, display settings, and initial frame.
# volume_name = volume_operations.import_dicom_series_from_directory(r'C:\data\dicom', series_instance_uids, dicom_options)
# # Import an ordered image stack
# image_dir = r'C:\data\stack'
# image_files = sorted(
# os.path.join(image_dir, name)
# for name in os.listdir(image_dir)
# if name.lower().endswith(('.bmp', '.jpg', '.jpeg', '.png', '.tif', '.tiff'))
# )
# image_stack_import_options = api.ImageStackImportOptions()
# image_stack_import_options.spacing_x = 0.4
# image_stack_import_options.spacing_y = 0.4
# image_stack_import_options.spacing_z = 0.8
# volume_name = volume_operations.import_image_stack_from_files(image_files, image_stack_import_options)
Exporting Volumes​
# Supported formats: mha, mhd, nii, nii.gz, nrrd, nhdr, hdr, img.gz, gipl, lsm, vti
# Export single volume to file
# volume_operations.export_volume_image_to_disk('Volume_1', r'C:\output\volume.mha')
# Export multiple volumes to a directory.
# Returns True only when every listed volume was written, so the result is worth checking.
# if not volume_operations.export_volume_images_to_disk(['Volume_1', 'Volume_2'], r'C:\output', 'mha'):
# print("One or more volumes could not be exported.")
# # Helper function to ensure output directory exists (create parents as needed) return the directory path as a string.
# def ensure_dir(path: str) -> str:
# p = Path(path)
# p.mkdir(parents=True, exist_ok=True)
# return str(p)
# # Export a volume as DICOM images
# volume_operations.export_volume_image_as_dicom_series('Volume_1', ensure_dir(r'C:\output\dicom'), 'IMG')
# # Export all slices on the XY plane (Plane 3)
# volume_operations.export_volume_image_as_image_stack('Volume_1', ensure_dir(r'C:\output\stack'), 'Slice', 'png', api.SlicePlane.XY)
# # Export a selected XY-plane slice range
# volume_operations.export_volume_image_slice_range_as_image_stack('Volume_1', ensure_dir(r'C:\output\stack_range'), 'Slice', 'png', api.SlicePlane.XY, 10, 50, 2)
# # Export the current XZ-plane slice (Plane 2)
# volume_operations.export_current_slice_to_disk('Volume_1', r'C:\output\current_slice.png', api.SlicePlane.XZ)
# # Export an XY-plane slice video
# volume_operations.export_volume_image_as_video('Volume_1', r'C:\output\xy_video.avi', api.SlicePlane.XY, True, 15, 'H264')
Creating Blank Volumes​
# Create a blank volume with specified properties
# volume_name = volume_operations.create_blank_volume(
# "New_Volume",
# [256, 256, 128], # Dimensions [x, y, z] in voxels
# [0.5, 0.5, 1.0], # Spacing [x, y, z] in mm
# api.VoxelDataType.Unsigned8Bit
# )
Volume Properties​
if all_volumes:
vol = all_volumes[0]
# Get dimensions
dims = volume_operations.get_dimensions(vol)
print(f"Dimensions: {dims[0]} x {dims[1]} x {dims[2]} voxels")
# Get spacing
spacing = volume_operations.get_spacing(vol)
print(f"Spacing: {spacing[0]:.4f} x {spacing[1]:.4f} x {spacing[2]:.4f} mm")
# Get origin
origin = volume_operations.get_origin(vol)
print(f"Origin: ({origin[0]:.2f}, {origin[1]:.2f}, {origin[2]:.2f}) mm")
# Get scalar range (intensity range of the volume as [min, max])
scalar_range = volume_operations.get_scalar_range(vol)
print(f"Intensity range: {scalar_range[0]} to {scalar_range[1]}")
# Get bounds and physical center
bounds = volume_operations.get_bounds(vol)
print(f"Bounds: x=({bounds[0]:.2f}, {bounds[1]:.2f}), y=({bounds[2]:.2f}, {bounds[3]:.2f}), z=({bounds[4]:.2f}, {bounds[5]:.2f})")
physical_center = volume_operations.get_physical_center(vol)
print(f"Physical center: ({physical_center[0]:.2f}, {physical_center[1]:.2f}, {physical_center[2]:.2f}) mm")
# Get memory usage and current slice state
memory_size_mb = volume_operations.get_memory_size_in_mbytes(vol)
print(f"Memory size: {memory_size_mb:.2f} MB")
current_slice_depth = volume_operations.get_current_slice_depth(vol)
print(f"Current slice depth: {current_slice_depth}")
current_slice_point = volume_operations.convert_voxel_index_to_physical_point(vol, current_slice_depth)
print(f"Current slice point: ({current_slice_point[0]:.2f}, {current_slice_point[1]:.2f}, {current_slice_point[2]:.2f}) mm")
round_trip_index = volume_operations.convert_physical_point_to_voxel_index(vol, current_slice_point)
print(f"Round-trip voxel index: {round_trip_index}")
# Get frame and metadata information (only applicable for multi-frame volumes such as 4D time series)
stored_frames = volume_operations.get_number_of_frames(vol)
print(f"Stored frames: {stored_frames}")
if stored_frames > 0:
active_frame_index = volume_operations.get_active_frame_index(vol)
print(f"Active frame index: {active_frame_index}")
# Activate a specific stored frame (index is clamped to the available range)
# volume_operations.set_active_frame_index(vol, 0)
metadata_keys = volume_operations.get_meta_data_keys(vol)
print(f"Metadata keys: {len(metadata_keys)}")
if metadata_keys:
first_key = metadata_keys[0]
metadata_value : api.DicomTag = volume_operations.get_meta_data(vol, first_key)
print(f"Metadata[{first_key}] = {metadata_value.value}")
metadata_entries = volume_operations.get_meta_data_dictionary(vol)
print(f"Metadata entries: {len(metadata_entries)}")
for entry in metadata_entries[:3]:
metadata_entry : api.MetaDataEntry = entry
print(f" {metadata_entry.key}: {metadata_entry.value}")
# Create or replace a custom metadata entry
# custom_tag = api.DicomTag()
# custom_tag.tag = '9999|0001'
# custom_tag.description = 'Custom Note'
# custom_tag.value_representation = 'LO'
# custom_tag.value = 'Reviewed'
# volume_operations.set_meta_data(vol, 'custom.note', custom_tag)
# Replace the full metadata list
# metadata_entry = api.MetaDataEntry()
# metadata_entry.key = 'custom.reviewed'
# metadata_entry.tag = '9999|0002'
# metadata_entry.description = 'Review Status'
# metadata_entry.value_representation = 'LO'
# metadata_entry.value = 'Approved'
# volume_operations.set_meta_data_dictionary(vol, [metadata_entry])
# Get gray value at voxel index
# gray_values = volume_operations.get_gray_value_at_voxel(vol, [128, 128, 64])
# print(f"Gray value at [128,128,64]: {gray_values}")
# Get gray value at world coordinates
# gray_values = volume_operations.get_gray_value_at_world_coordinates(vol, [0.0, 0.0, 0.0])
# print(f"Gray value at origin: {gray_values}")
Spatial Transformations​
# if all_volumes:
# # Center volume at origin
# volume_operations.center_at_origin([all_volumes[0]])
#
# # Change spacing
# volume_operations.change_spacing([all_volumes[0]], [1.0, 1.0, 1.0])
#
# # Change origin
# volume_operations.change_origin([all_volumes[0]], [0.0, 0.0, 0.0])
#
# # Translate origin
# volume_operations.translate_origin([all_volumes[0]], [10.0, 10.0, 0.0])
#
# # Reorient (rotate around axis)
# volume_operations.reorient(
# [all_volumes[0]],
# 45.0, # angle (degrees)
# [0.0, 0.0, 1.0], # axis (vector)
# api.Interpolation.Linear, # volumeInterpolation
# api.Interpolation.Nearest # maskInterpolation
# )
#
# # Flip along axis
# volume_operations.flip(
# [all_volumes[0]],
# api.FlipAxis.X, # axis (api.FlipAxis.X, api.FlipAxis.Y, or api.FlipAxis.Z)
# False # aboutOrigin (bool)
# )
#
# # Swap (permute) two spatial axes
# # Useful when data is imported with axes in the wrong order
# volume_operations.swap_axes(
# [all_volumes[0]],
# api.SwapAxesPair.XY # pair: api.SwapAxesPair.XY, api.SwapAxesPair.XZ, or api.SwapAxesPair.YZ
# )
#
# # Swap multiple volumes at once
# # volume_operations.swap_axes(all_volumes, api.SwapAxesPair.YZ)
#
# # Reslice along an arbitrary plane
# # The plane is defined by an origin point and a unit normal vector.
# # This resamples the volume into a new axis system defined by the plane.
# volume_operations.reslice(
# [all_volumes[0]],
# [0.0, 0.0, 0.0], # planeOrigin [x, y, z] in world coordinates
# [1.0, 0.0, 0.0], # planeNormal [nx, ny, nz] unit normal (YZ plane)
# api.Interpolation.Linear, # volumeInterpolation
# api.Interpolation.Nearest # maskInterpolation
# )
Cropping and Padding​
# if all_volumes:
# # Crop to bounds [xMin, xMax, yMin, yMax, zMin, zMax] in mm
# volume_operations.crop([all_volumes[0]], [-50.0, 50.0, -50.0, 50.0, -30.0, 30.0])
#
# # Pad project image data so mirrored surfaces and masks remain in bounds
# # Get surface and mask names to consider for mirroring
# all_surfaces = app.get_all_surface_names()
# all_masks = app.get_all_mask_names()
# volume_operations.pad_for_mirroring(
# all_volumes[0], # volumeName (str)
# all_surfaces, # surfaceNames (list)
# all_masks, # maskNames (list)
# [0.0, 0.0, 0.0], # planeOrigin [x, y, z] in mm
# [1.0, 0.0, 0.0] # planeNormal [x, y, z] (unit vector)
# )
#
# # Shrink to bounds (bounds can extend outside original)
# volume_operations.shrink([all_volumes[0]], [-100.0, 100.0, -100.0, 100.0, -50.0, 50.0])
#
# # Add padding (in voxels)
# volume_operations.pad(
# [all_volumes[0]],
# [10, 10, 5], # Lower bounds padding [x, y, z]
# [10, 10, 5] # Upper bounds padding [x, y, z]
# )
Resampling​
# if all_volumes:
# volume_operations.resample(
# [all_volumes[0]],
# [512, 512, 256], # Target dimensions
# [0.5, 0.5, 0.5], # Target spacing in mm
# api.Interpolation.Linear, # volumeInterpolation
# api.Interpolation.Nearest # maskInterpolation
# )
Smoothing Filters​
# if all_volumes:
# # Gaussian filter
# volume_operations.gaussian_filter(
# [all_volumes[0]],
# 1.0, # standard deviation (float)
# 3.0, # radiusFactor (float)
# True # threeDimensional (bool)
# )
#
# # Discrete Gaussian filter
# volume_operations.discrete_gaussian_filter(
# [all_volumes[0]],
# [1.0, 1.0, 1.0], # sigma (list of floats for X,Y,Z)
# False # useImageSpacing=False (bool: True=mm, False=pixels)
# )
#
# # Recursive Gaussian filter (efficient for large kernels)
# volume_operations.recursive_gaussian_filter(
# [all_volumes[0]],
# [1.0, 1.0, 1.0] # sigma (list of floats for X,Y,Z)
# )
#
# # Mean filter
# volume_operations.mean_filter([all_volumes[0]], [1, 1, 1]) # radius [x,y,z] in pixels (ints)
#
# # Median filter
# volume_operations.median_filter([all_volumes[0]], [1, 1, 1]) # radius [x,y,z] in pixels (ints)
#
# # Bilateral filter (edge-preserving)
# volume_operations.bilateral_filter(
# [all_volumes[0]],
# 2.0, # domainSigmaX in pixels (float)
# 2.0, # domainSigmaY in pixels (float)
# 2.0, # domainSigmaZ in pixels (float)
# 50.0, # rangeSigma (float)
# 2 # kernelRadius (int)
# )
#
# # Binomial blur (fast approximation to Gaussian)
# volume_operations.binomial_blur_filter([all_volumes[0]], 5) # iterations=5 (int)
Edge-Preserving Smoothing​
# if all_volumes:
# # Curvature anisotropic diffusion
# volume_operations.curvature_anisotropic_diffusion_filter(
# [all_volumes[0]],
# 5, # iterations (int)
# 0.0625, # timeStep (float)
# 3.0 # conductance (float)
# )
#
# # Gradient anisotropic diffusion
# volume_operations.gradient_anisotropic_diffusion_filter(
# [all_volumes[0]],
# 5, # iterations (int)
# 0.0625, # timeStep (float)
# 3.0 # conductance (float)
# )
#
# # Curvature flow
# volume_operations.curvature_flow_filter(
# [all_volumes[0]],
# 10, # iterations (int)
# 0.0625 # timeStep (float)
# )
#
# # Min-max curvature flow
# volume_operations.min_max_curvature_flow_filter(
# [all_volumes[0]],
# 10, # iterations (int)
# 0.0625, # timeStep (float)
# 1 # stencilRadius (int)
# )
#
# # Patch-based denoising (non-local means)
# volume_operations.patch_based_denoising_filter(
# [all_volumes[0]],
# 4.0, # patchRadius (float)
# "Gaussian", # noiseModel (string)
# 1, # iterations (int)
# 0.1 # fidelityWeight (float)
# )
Edge Enhancement​
# if all_volumes:
# # Laplacian edge sharpening
# volume_operations.laplacian_edge_sharpening([all_volumes[0]])
#
# # Convolution-based edge sharpening
# volume_operations.convolution_based_edge_sharpening([all_volumes[0]])
#
# # Gradient magnitude
# volume_operations.gradient_magnitude_filter([all_volumes[0]])
#
# # Gradient magnitude with recursive Gaussian
# volume_operations.gradient_magnitude_recursive_gaussian_filter([all_volumes[0]], 1.0) # sigma (float)
#
# # Laplacian of Gaussian
# volume_operations.laplacian_of_gaussian_filter([all_volumes[0]], 1.0) # sigma (float)
#
# # Unsharp mask sharpening
# volume_operations.unsharp_mask_filter([all_volumes[0]], 1.0, 0.5) # sigma (float), amount (float)
#
# # Butterworth high-pass filter
# volume_operations.butterworth_high_pass_filter([all_volumes[0]], 0.1, 0.1) # xCutOff, yCutOff (float)
Intensity Transformations​
# if all_volumes:
# # Rescale intensity to range
# volume_operations.rescale_intensity_filter([all_volumes[0]], 0, 255) # minIntensity, maxIntensity (int)
#
# # Clamp intensity to range
# volume_operations.clamp_intensity_filter([all_volumes[0]], 10, 120) # lowerBound, upperBound (float)
#
# # Intensity windowing
# volume_operations.intensity_windowing_filter(
# [all_volumes[0]],
# 10, # windowMinimum (float)
# 120, # windowMaximum (float)
# 0, # outputMinimum (float)
# 255 # outputMaximum (float)
# )
#
# # Normalize (zero mean, unit variance)
# volume_operations.normalize_filter([all_volumes[0]])
#
# # Invert intensity
# volume_operations.invert_intensity([all_volumes[0]])
#
# # Sigmoid transformation
# volume_operations.sigmoid_filter(
# [all_volumes[0]],
# 0, # outputMin (int)
# 255, # outputMax (int)
# 100, # alpha (float: offset)
# 10 # beta (float: steepness)
# )
Morphological Operations​
# if all_volumes:
# # Available: Erode, Dilate, Open, Close
# volume_operations.morphological_operation(
# [all_volumes[0]],
# api.MorphologicalOperation.Dilate,
# [2, 2, 2] # Ball radius in pixels
# )
#
# # Opening by reconstruction
# volume_operations.opening_by_reconstruction_filter([all_volumes[0]], [1, 1, 1]) # radius [x, y, z] in voxels
#
# # Closing by reconstruction
# volume_operations.closing_by_reconstruction_filter([all_volumes[0]], [1, 1, 1]) # radius [x, y, z] in voxels
Correction Filters​
# if all_volumes:
# # Metal artifact reduction
# volume_operations.metal_artifact_reduction_filter(
# [all_volumes[0]],
# 3000.0, # metalThreshold (float)
# 1.5 # inpaintSigma (float)
# )
#
# # Bad pixel correction
# volume_operations.bad_pixel_correction_filter(
# [all_volumes[0]],
# api.CBR_SpeckleRemovalMode.SinglePixel,
# 3.0 # thresholdMultiplier (float)
# )
#
# # Scatter correction
# volume_operations.scatter_correction_filter(
# [all_volumes[0]],
# 1.0, # sigma (float)
# 0.1 # amplitude (float)
# )
#
# # Histogram equalization variants
# volume_operations.slice_histogram_equalization_filter([all_volumes[0]], api.FlipAxis.Z)
# volume_operations.cylindrical_histogram_equalization_filter([all_volumes[0]], api.FlipAxis.Z)
#
# # Adaptive histogram equalization (local contrast enhancement)
# volume_operations.adaptive_histogram_equalization_filter(
# [all_volumes[0]],
# 0.3, # alpha: 0 = classical equalization, 1 = unsharp masking
# 0.3, # beta: 0 = full equalization, 1 = pass-through (with alpha=1)
# 5, # window radius X in voxels
# 5, # window radius Y in voxels
# 5 # window radius Z in voxels
# )
Distance Maps​
# if all_volumes:
# # Danielsson distance map (unsigned)
# volume_operations.danielsson_distance_map_filter([all_volumes[0]])
#
# # Signed Danielsson distance map
# volume_operations.signed_danielsson_distance_map_filter([all_volumes[0]])
#
# # Signed Maurer distance map
# volume_operations.signed_maurer_distance_map_filter([all_volumes[0]])
Regional Analysis​
# if all_volumes:
# # Extract regional minima
# volume_operations.regional_minima_filter([all_volumes[0]])
#
# # Extract regional maxima
# volume_operations.regional_maxima_filter([all_volumes[0]])
Combining Volumes​
# if len(all_volumes) >= 2:
# # Combine operations: Addition, Subtraction, Multiplication, Division, Mean, Minimum, Maximum
# result = volume_operations.combine(
# all_volumes[0],
# all_volumes[1],
# "", # Target name (not used when createNewObject=True)
# api.CombineOperation.Addition, # operation (Addition, Subtraction, Multiplication, Division, Mean, Minimum, Maximum)
# True # createNewObject (bool)
# )
Apply Mask to Volume​
# all_masks = app.get_all_mask_names()
# if all_volumes and all_masks:
# volume_operations.apply_mask_to_volume(
# all_volumes[0],
# all_masks[0],
# 0 # voxelIntensityValue=0 (int: value for voxels outside mask)
# )
Create Masks from Volumes​
# if all_volumes:
# # Direct copy to mask
# mask_names = volume_operations.create_mask_by_direct_copying([all_volumes[0]])
#
# # Create masks from pre-segmented volume
# mask_names = volume_operations.create_masks_by_segmentation(
# [all_volumes[0]],
# 1, # lowerThreshold (int)
# 255, # upperThreshold (int)
# True # createMultilabelMask=True (bool)
# )
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:/Data/Projections",
# prefix="XYZ_Object",
# start=100001,
# end=100999,
# 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.19 # Distance from X-ray source to detector (mm)
# cone_beam_reconstruction_settings.source_to_isocenter_distance = 153.48 # 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)
#
# # Automatic scan geometry correction: estimates the horizontal detector offset
# # (center of rotation) from opposing projections and replaces projection_offset_x.
# # Requires a scan arc of at least 180 degrees.
# cone_beam_reconstruction_settings.enable_auto_detector_offset_x = False
#
# # Preprocessing parameters
# cone_beam_reconstruction_settings.enable_logarithm_conversion = True # Apply logarithmic transformation
#
# # Reconstruction filter parameters
# cone_beam_reconstruction_settings.truncation_correction = 1.0 # Detector-width fraction feathered (0.0-1.0, 0 = off)
# cone_beam_reconstruction_settings.reconstruction_filter = api.CBR_ReconstructionFilter.RamLak # RamLak/SheppLogan/Hann/Hamming
# cone_beam_reconstruction_settings.filter_cut_frequency = 0.0 # Cut frequency (0.0-1.0, 0 = pure ramp, lower = smoother)
#
# # 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)
#
# # Noise reduction on the reconstructed volume (runs before sharpening)
# cone_beam_reconstruction_settings.noise_reduction_mode = api.CBR_NoiseReductionMode.Off # Off/Median/Gaussian/EdgePreserving
# cone_beam_reconstruction_settings.noise_reduction_level = api.CBR_NoiseReductionLevel.Medium # Low/Medium/High
#
# # Histogram contrast windowing (percentile-based outlier clipping and stretching)
# cone_beam_reconstruction_settings.enable_histogram_windowing = True # Improve the contrast distribution
# cone_beam_reconstruction_settings.histogram_windowing_margin_percent = 5.0 # Margin so the clip does not cut tightly (percent)
#
# # Sharpening parameters (Unsharp Mask)
# cone_beam_reconstruction_settings.enable_sharpening = False # Disable 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}")
#
# # Example 2: Reconstruct from active volume (3D stack of projections)
# # First load a 3D volume where each slice is a projection image
# # projection_stack = volume_operations.import_3d_image_from_disk(r'C:\data\projections_stack.mha')
# # reconstructed_volume = volume_operations.reconstruct_volume_using_cone_beam_reconstruction(
# # projection_stack, # volumeName: name of loaded projection stack
# # [], # fileNames: empty list when using volume
# # cone_beam_reconstruction_settings # reconstruction parameters
# # )
# # print(f"Reconstructed volume from stack: {reconstructed_volume}")
print("Volume operations tutorial completed successfully.")
Related Resources​
- API Reference - API documentation
- Quick Reference - Common methods at a glance