From d260165a09235d830be7dc3639ea9b584991477b Mon Sep 17 00:00:00 2001 From: Chandan Sarkar Date: Tue, 10 Feb 2026 12:14:22 +0100 Subject: [PATCH 1/2] feat: Implement single gpu for test --- bin/tofu | 5 +++ tofu/config.py | 36 +++++++++++++++++++++- tofu/rgbareco.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 tofu/rgbareco.py diff --git a/bin/tofu b/bin/tofu index f9c8e8c..05ff1c0 100755 --- a/bin/tofu +++ b/bin/tofu @@ -36,6 +36,9 @@ def run_genreco(args): from tofu import genreco genreco.genreco(args) +def run_rgba_bp(args): + from tofu import rgbareco + rgbareco.run_rgba_bp(args) def run_flat_correct(args): from tofu import preprocess @@ -214,6 +217,8 @@ def main(): ('interactive', run_shell, tomo_params, "Run interactive mode"), ('find-large-spots', run_find_large_spots, ('find-large-spots',), "Find large spots on images"), ('inpaint', run_inpaint, ('inpaint',), "Inpaint images"), + ('rgbabp', run_rgba_bp, config.RGBA_BP_PARAMS, "RGBA Backprojection for " + "parallel beam tomography") ] if sys.version < '3.7': diff --git a/tofu/config.py b/tofu/config.py index 27ce12d..0757f47 100644 --- a/tofu/config.py +++ b/tofu/config.py @@ -852,11 +852,45 @@ 'metavar': 'PATH'}, } +SECTIONS['rgba-backproject'] = { + 'burst': { + 'default': None, + 'type': restrict_value((0, None), dtype=int), + 'help': "Number of projections processed per kernel invocation"}, + 'overall-angle': { + 'default': None, + 'ezdefault': 360, + 'type': float, + 'unit': "deg", + 'help': "The total angle over which projections were taken"}, + 'center-position-x': { + 'default': None, + 'type': tupleize(), + 'unit': "pixel", + 'help': "X rotation axis position on a projection"}, + 'center-position-z': { + 'default': None, + 'ezdefault': "0", + 'type': tupleize(), + 'unit': "pixel", + 'help': "Z rotation axis position on a projection"}, + 'region': { + 'default': "0,1,1", + 'type': tupleize(num_items=3), + 'help': "z axis parameter region as from,to,step"}, + 'rgbabp-padding-mode': { + 'choices': ['none', 'clamp', 'clamp_to_edge', 'repeat', 'mirrored_repeat'], + 'default': 'clamp', + 'help': "Padded values assignment for the filtered projection"} +} + TOMO_PARAMS = ('flat-correction', 'reconstruction', 'tomographic-reconstruction', 'fbp', 'dfi', 'ir', 'sart', 'sbtv') PREPROC_PARAMS = ('preprocess', 'cone-beam-weight', 'flat-correction', 'retrieve-phase') LAMINO_PARAMS = PREPROC_PARAMS + ('laminographic-reconstruction',) GEN_RECO_PARAMS = PREPROC_PARAMS + ('general-reconstruction',) +RGBA_BP_PARAMS = ('preprocess', 'flat-correction', 'retrieve-phase', 'rgba-backproject') +FFC_FLT_PARAMS = ('preprocess', 'flat-correction') NICE_NAMES = ('General', 'Input', 'Flat field correction', 'Phase retrieval', 'Sinogram generation', 'General reconstruction', 'Tomographic reconstruction', @@ -864,7 +898,7 @@ 'Direct Fourier Inversion', 'Iterative reconstruction', 'SART', 'SBTV', 'GUI settings', 'Estimation', 'Performance', 'Preprocess', 'Cone beam weight', 'General reconstruction', 'Find large spots', - 'Inpaint') + 'Inpaint', 'Minimal IO', 'RGBA Backproject') # Add unit info to help strings diff --git a/tofu/rgbareco.py b/tofu/rgbareco.py new file mode 100644 index 0000000..6474f17 --- /dev/null +++ b/tofu/rgbareco.py @@ -0,0 +1,80 @@ +import argparse +import logging +import time +from threading import Thread +from gi.repository import Ufo +from .preprocess import create_flat_correct_pipeline, create_projection_filtering_pipeline +from .tasks import get_task, get_writer +from .util import determine_shape, fbp_filtering_in_phase_retrieval + +LOG = logging.getLogger(__name__) + +def create_ffc_abs_flt_pipeline( + args: argparse.Namespace, + graph: Ufo.TaskGraph, + processing_node: int) -> Ufo.TaskNode: + assert args.darks is not None + assert args.flats is not None + assert args.projections is not None + current: Ufo.TaskNode = create_flat_correct_pipeline(args, graph, processing_node=processing_node) + if args.projection_filter != 'none' and not fbp_filtering_in_phase_retrieval(args): + pf_first, pf_last = create_projection_filtering_pipeline( + args, graph, processing_node=processing_node) + if current: + graph.connect_nodes(current, pf_first) + current = pf_last + return current + + +def setup_single_gpu_graph( + args: argparse.Namespace, + graph: Ufo.TaskGraph, + gpu_index: int=0) -> None: + determine_shape(args=args, store=True) + assert args.output is not None + sink = get_writer(params=args) + flt_node = create_ffc_abs_flt_pipeline(args=args, graph=graph, processing_node=gpu_index) + backproject = get_task('rgba-backproject', processing_node=gpu_index) + backproject.props.burst = args.burst + backproject.props.region = args.region + # Following is required when we want to perform the cropping after back-projection. For our + # testing we are enabling the parameter --projection-crop-after filter whereas default is + # backproject. Hence, we can keep the adjustment of center_position_x disabled as use the value + # provided in the command as is. + # args.center_position_x = [pos + padding / 2 for pos in args.center_position_x] + backproject.props.center_position_x = args.center_position_x + backproject.props.center_position_z = args.center_position_z + backproject.props.num_projections = args.number + backproject.props.overall_angle = args.overall_angle + backproject.props.addressing_mode = args.rgbabp_padding_mode + graph.connect_nodes(flt_node, backproject) + graph.connect_nodes(backproject, sink) + +def run_rgba_bp(args: argparse.Namespace) -> None: + """ + Simplified version of genreco for parallel beam tomographic reconstruction on a single GPU. + + Required parameters in args: + - burst: Number of projections processed per kernel invocation + - number: Total number of projections (num_projections) + - center_position_x: Axis of rotation (list or single value) + - center_position_z: Z position of the 0th slice (list or single value) + - region: Z-region for reconstruction (from, to, step) + - projections: Path to projection files + - output: Output file path + - width, height: Projection dimensions (determined automatically if not set) + - Other defaults assumed: parallel beam, no rotations, etc. + + tofu rgbabp --projections radios --flats flats --darks darks/ --output slices.tiff --burst 24 \ + --number 3001 --overall-angle -180 --center-position-x 588.2 --center-position-z 606 \ + --region=-400,400,10 --absorptivity --projection-crop-after filter --verbose + """ + st = time.time() + scheduler = Ufo.FixedScheduler() + graph = Ufo.TaskGraph() + _ = setup_single_gpu_graph(args=args, graph=graph) + thread = Thread(target=scheduler.run, args=(graph,), daemon=True) + thread.start() + thread.join() + duration = time.time() - st + LOG.debug('Duration: %.2f s', duration) From 2c7824cc07b90be3b50c568f0ec7661167521c38 Mon Sep 17 00:00:00 2001 From: Chandan Sarkar Date: Tue, 10 Feb 2026 13:02:25 +0100 Subject: [PATCH 2/2] feat: Add notes for test --- tofu/config.py | 1 - tofu/rgbareco.py | 72 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/tofu/config.py b/tofu/config.py index 0757f47..fbd8a58 100644 --- a/tofu/config.py +++ b/tofu/config.py @@ -890,7 +890,6 @@ LAMINO_PARAMS = PREPROC_PARAMS + ('laminographic-reconstruction',) GEN_RECO_PARAMS = PREPROC_PARAMS + ('general-reconstruction',) RGBA_BP_PARAMS = ('preprocess', 'flat-correction', 'retrieve-phase', 'rgba-backproject') -FFC_FLT_PARAMS = ('preprocess', 'flat-correction') NICE_NAMES = ('General', 'Input', 'Flat field correction', 'Phase retrieval', 'Sinogram generation', 'General reconstruction', 'Tomographic reconstruction', diff --git a/tofu/rgbareco.py b/tofu/rgbareco.py index 6474f17..9c1a6d2 100644 --- a/tofu/rgbareco.py +++ b/tofu/rgbareco.py @@ -9,13 +9,22 @@ LOG = logging.getLogger(__name__) -def create_ffc_abs_flt_pipeline( +def create_ffc_flt_pipeline( args: argparse.Namespace, graph: Ufo.TaskGraph, processing_node: int) -> Ufo.TaskNode: - assert args.darks is not None - assert args.flats is not None - assert args.projections is not None + """ + Configures computational graph for flat-field correction and filtering. + + :param args: parameters for nodes. + :type args: argparse.Namespace + :param graph: Ufo task graph + :type graph: Ufo.TaskGraph + :param gpu_index: index of the gpu node + :type processing_node: int + :return: final task node after connecting flat-field to filtering + :rtype: Ufo.TaskNode + """ current: Ufo.TaskNode = create_flat_correct_pipeline(args, graph, processing_node=processing_node) if args.projection_filter != 'none' and not fbp_filtering_in_phase_retrieval(args): pf_first, pf_last = create_projection_filtering_pipeline( @@ -30,11 +39,20 @@ def setup_single_gpu_graph( args: argparse.Namespace, graph: Ufo.TaskGraph, gpu_index: int=0) -> None: + """ + Configures full computational graph for reconstruction. + + :param args: parameters for nodes. + :type args: argparse.Namespace + :param graph: Ufo task graph. + :type graph: Ufo.TaskGraph + :param gpu_index: index of the gpu node + :type gpu_index: int + """ determine_shape(args=args, store=True) - assert args.output is not None - sink = get_writer(params=args) - flt_node = create_ffc_abs_flt_pipeline(args=args, graph=graph, processing_node=gpu_index) - backproject = get_task('rgba-backproject', processing_node=gpu_index) + sink: Ufo.TaskNode = get_writer(params=args) + source: Ufo.TaskNode = create_ffc_flt_pipeline(args=args, graph=graph, processing_node=gpu_index) + backproject: Ufo.TaskNode = get_task('rgba-backproject', processing_node=gpu_index) backproject.props.burst = args.burst backproject.props.region = args.region # Following is required when we want to perform the cropping after back-projection. For our @@ -47,28 +65,44 @@ def setup_single_gpu_graph( backproject.props.num_projections = args.number backproject.props.overall_angle = args.overall_angle backproject.props.addressing_mode = args.rgbabp_padding_mode - graph.connect_nodes(flt_node, backproject) + graph.connect_nodes(source, backproject) graph.connect_nodes(backproject, sink) def run_rgba_bp(args: argparse.Namespace) -> None: """ - Simplified version of genreco for parallel beam tomographic reconstruction on a single GPU. + Implements optimized backprojection leveraging RGBA color channels in texture memory. Required parameters in args: - - burst: Number of projections processed per kernel invocation - - number: Total number of projections (num_projections) - - center_position_x: Axis of rotation (list or single value) - - center_position_z: Z position of the 0th slice (list or single value) - - region: Z-region for reconstruction (from, to, step) - - projections: Path to projection files - - output: Output file path - - width, height: Projection dimensions (determined automatically if not set) - - Other defaults assumed: parallel beam, no rotations, etc. + For testing it is easier to focus on absorption reconstruction. Hence, we focus on the minimum + required parameters for flat-field correction with absorptivity, filtering and back-projection. + + - projections: location of the projections. + - flats: location of the flat fields. + - darks: location of the dark fields. + - output: location to write the final output slices. + - burst: number of projections to be processed per kernel invocation. + - number: number of projections to be processed. + - overall-angle: angles of rotation. + - center_position_x: axis of rotation + - center_position_z: z-position of the 0th slice. + - region: z-region for reconstruction (from, to, step) + - absorptivity: indicates to compute absorption (Beer-Lambert) from transmission. + - projection-crop-after: indicates to crop the projection (we use filter for efficiency, + default backproject) + Example Command: tofu rgbabp --projections radios --flats flats --darks darks/ --output slices.tiff --burst 24 \ --number 3001 --overall-angle -180 --center-position-x 588.2 --center-position-z 606 \ --region=-400,400,10 --absorptivity --projection-crop-after filter --verbose """ + try: + assert args.darks + assert args.flats + assert args.projections + assert args.output + except AssertionError: + raise RuntimeError('test version: darks, flats, projections, output are needed') + print(f"###################################################Called#####") st = time.time() scheduler = Ufo.FixedScheduler() graph = Ufo.TaskGraph()