#beamforming #synthetic-aperture #ultrasound #holoscan #ros2 #cuda #jetson #ios #region-calibration
Sitka logo

Sitka

A deterministic, testable B-Mode ultrasound pipeline targeting the NVIDIA Jetson Orin Nano — synthetic channel data, delay-and-sum beamforming (CPU & CUDA), calibrated image reconstruction, and live display across Pipeline, Core, ROS Nodes, and Holoscan Operators with native iOS & macOS DDS viewport applications.

Platformsaarch64 Jetson Orin Nano · arm64 M3 Pro MacBookPro · macOS Tahoe 26.5 · iOS 26.4.2
LanguageC++17 · Python 3.10+ · Swift 5.9 · CUDA 12.6
Holoscan SDK4.2.0
ROS 2Humble (native, /opt/ros/humble)
DDSFastDDS 3.5.0
JetPack / L4T6 · R36.4.7
BuildCMake ≥ 3.30 · colcon · xcodebuild
Unit Tests54 C++ (verify CPU and GPU parity, CUDA) · 25 Python
NVIDIAJetson Orin Nano · Western Digital Black 1TB NVMe · Samsung PRO Plus microSD 1TB
Pipeline Demo
Sitka pipeline demo — end-to-end B-Mode ultrasound

End-to-End B-Mode ultrasound: synthetic channel data → DAS beamforming → live HolovizOp display → DDS viewport


Overview

Sitka is a research and engineering scaffold for the core computational stages of a B-Mode ultrasound imaging pipeline. It is not a clinical system; it is a deterministically testable platform for validating beamforming algorithms, exercising production-shaped data contracts, and demonstrating real-time image display on embedded GPU hardware.

The primary acquisition source is acoustic-data-generator, a Python probe simulator modelling a 2D linear array with single-element synthetic-aperture transmit. A forward acoustic model produces RF or IQ channel data from configurable point-scatterer phantoms, emitting flat binary payloads with standalone JSON sidecar metadata. The C++ processing core (us_core) is middleware-neutral — the same library runs from a CLI tool, a ROS 2 node graph, or a Holoscan operator graph, producing identical numerical output in all three.

Every reconstructed B-Mode frame carries a RegionCalibration record mapping display pixels to calibrated physical distances (cm/px), derived directly from the imaging grid geometry. When depth changes via the ImGui slider or the /us/parameters DDS topic, the calibration updates live — no restart, no hardcoded values.

Note — The pipeline also supports sharding frames directly from the NV-Raw2Insights-US dataset (Simson & Huver, NVIDIA, 2026) — running published FSA IQ channel data through us_core and reconstructing B-Mode images. This was used to validate basic beamformer capability against an outside, independently-generated source rather than data synthesized by acoustic-data-generator.

B-Mode frame reconstructed by us_core from a sharded NV-Raw2Insights-US IQ sample. Data: Simson & Huver (2026), NVIDIA Corporation, CC BY 4.0.

NV-Raw2Insights-US B-Mode example shard reconstructed by us_core

Pipeline Architecture

┌──────────────────────────────────────┐ │ Acoustic Data Generator │ └──────────────────┬───────────────────┘ │ ┌──────────────────▼─────────────────────┐ │ Beamformer, Image Reconstructor [Core] │ └──────┬─────────────┬────────────┬──────┘ │ │ │ ┌──────▼────┐ ┌─────▼───────┐ ┌─▼──────────┐ │ Pipeline │ │ ROS Nodes │ │ Holoscan │ └───────────┘ └──────┬──────┘ └───┬────────┘ └─────┬───────┘ ┌───────────────────────────▼────────────┐ │ Live Display [HoloViz] │ └──────────────────────────┬─────────────┘ │ ┌──────────────────────────▼────────────┐ │ Viewports [iOS, macOS] │ └───────────────────────────────────────┘

Components

ComponentPackage / BinaryRole
Acoustic Data Generatoracoustic-data-generatorPython Synthetic Aperature forward model based on linear array geometry. Produces deterministic RF/IQ fixtures (.bin + _meta.json)
Coreus_core (C++17)FixtureLoader · TableBuilder · DAS Beamformer (CPU + CUDA) · ImageReconstructor · PostProcessor · RegionCalibration · PngWriter. This is a middleware-neutral static library.
Pipelineusbeam_cliBatch file-in / file-out; full pipeline to PNG; golden reference comparison; CI validation; no live display
ROS Nodesus_nodes + acoustic_generator_node.pyROS 2 Humble adapter nodes with file-backed FrameRef transport; live B-Mode on depth-change demand (~1.4 s/frame)
Holoscanusbeam_holoscanHoloscan 4.2.0 operator graph. Contains RF B-Mode and IQ coherent compounding paths. Has switchable CPU/CUDA beamforming (--cpu-das)
Live Displayus_displayFrameSubscriberOpHolovizOp; ImGui depth slider; DepthParameterPublisherOp; Holoscan + rclcpp on Jetson
ViewportsViewportsThese are iOS and macOS SwiftUI, Metal B-Mode viewers with a depth slider. Based on FastDDS 3.5.0 bridge but has no direct ROS2 dependency

Data Flows

As the puzzle pieces settle in, a side objective is having a platform in place to revise and refine guided by past classical algorithm experience on imaging pipelines yet approaching with modern machine learning techniques where it makes sense. Take note below at the Core versus Holoscan and related components at CPU and GPU intersection.

ConnectionCarriesTransport
Acoustic Data Generator → CoreRF/IQ channel data.bin float32 [tx, samples, rx] + _meta.json
Core → PipelineB-Mode PNG + bmode_meta.json with RegionCalibrationFilesystem
Core → ROS NodesFrames + calibration metadataFile-backed frame reference URIs that uniquely conveys one of RF, IQ, Envelope then exiting as B-Mode - never a mixture of type/format
Core → HoloscanBeamformed framesDLPack CPU / GPU tensors
ROS Nodes → Live DisplayB-Mode stream/us/framesensor_msgs/Image mono8
Holoscan → Live DisplayB-Mode stream/us/framesensor_msgs/Image mono8
Live Display → ViewportsB-Mode streamDDS rt/us/frame [FastDDS 3.5.0]
Viewports → Live DisplayDepth command, e.g. DDS rt/us/parameters [FastDDS 3.5.0]
us_core is the same static library across all three execution paths. Output is bit-identical between (manual) command line and Holoscan (compare_holoscan_output.py; max|Δ|=0). The command line path currently driving the pipeline and core is for batch validation and does not connect to Live Display - yet, anyway. As a side point, DLPack tensor approach is thea zero-copy memory handoff standard that keeps beamformed frames GPU-resident between pipeline stages.

RegionCalibration

Every B-Mode frame produced by us_core carries a RegionCalibration record mapping pixels to physical space, derived at reconstruction time from the actual BeamformGrid geometry:

physical_delta_x  =  (x_max_m − x_min_m) / width_px  × 100   [cm/px]
physical_delta_y  =  (z_max_m − z_min_m) / height_px × 100   [cm/px]

z_min_m  =  0.0                           (probe face)
z_max_m  =  depth_m                       (from AcquisitionParameters)
x_min_m  =  element_x_m[0]   − pitch_m / 2
x_max_m  =  element_x_m[N−1] + pitch_m / 2

A depth change via the ImGui slider or /us/parameters DDS topic propagates automatically to a fresh RegionCalibration in every output frame. The bmode_meta.json sidecar records calibration alongside acquisition parameters for every output PNG.


Performance Baseline (Jetson Orin Nano)

StageCPUGPU
DAS — 16-el / 1-TX, 256×51219.7 ms5.9 ms
DAS + Envelope - NVIDIA edge device-resident5.0 ms
IQ Compound — 180-el / 180-TX, 256×512530 ms (target ≤ 150 ms)
PNG write, 256×512~20 ms~20 ms
IQ compound ≤ 150 ms target not yet reached. Current 3.2× gain from [tx,ax,rx] → [tx,rx,ax] layout transposition (IQTransposeGPU). Next: warp-level reduction or frequency-domain compounding.

DDS Topic Contract

Four topics under /us/ govern all inter-process communication. The same namespace is mirrored by namespace us in the C++ core headers.

TopicDirectionContentStatus
/us/frameGenerator → Displaysensor_msgs/Image mono8; one B-Mode frame per publishFunctional
/us/parametersDisplay → GeneratorFloat64 depth in SI meters; future: full AcquisitionParametersFunctional
/us/contextGenerator → DisplayConfirmed params + MI/TI safety indices; always broadcastUnder Construction
/us/commandController → AllCQRS command bus: Freeze · CaptureStart · CaptureStop · ModeChange · SystemFaultUnder Construction

The Viewports application(s) subscribe to FastDDS wire-level topics rt/us/frame and rt/us/parameters (ROS 2 wire-name prefix rt/). No ROS 2 dependency on the companion side.


References