Files
radar_system/Makefile
T
aweandClaude Fable 5 b8cb572bc4 sar3d: port the Multi_antenna_3D_clean_v1 notebook algorithm
The old sar3d core was a port of the superseded Horns_3D notebook and
produced wrong volumes. The compute core now follows the reference
notebook exactly:

- segmented_interleaved frame timing rebuilt from per-trace capture
  stamps (uniform tx_per_segment split as the no-stamps fallback), with
  the coarse inter-segment dtau and the intra-sweep dtau applied as one
  phase ramp before the IFFT;
- ground scene coordinates with the ground_depth_m lift (scene Y=0 is
  the ground), full-region grid instead of a window around a center
  depth, geo_ref compensation anchored on the ground plane, power 0.3;
- notebook finalization in C++: |acc/cnt|, depth mask, normalization
  without the Y-edge planes, separable Gaussian over (Z, X), level;
- CUDA kernel computes geometry in double: float32 loses ~3e-4 of an
  A-scan sample in tau and drifts ~1e-4 from the float64 reference.

Live-config keys follow: the center_depth/half_width/grid_n window knobs
are replaced by ground/voxel/y-range/margin/sigma/power/tx_per_segment.

Parity against the notebook npz exports (frames 0034-0036 of the
20260922_mine_fast_true dataset): max|diff| <= 1.6e-6 off the depth-mask
knife edge, identical argmax, level rel diff <= 7e-7. Verified via
make sar3d_parity --dump-dir + scripts/sar3d_volume_compare.py; the
executable spec lives in scripts/sar3d_reference_check.py (bit-tight
against the npz, including the animation shifted-grid arithmetic).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-25 16:42:01 +03:00

205 lines
10 KiB
Makefile

CXX := g++
CXXFLAGS := -std=c++20 -O2 -Wall -Wextra -Wpedantic -pthread -MMD -MP
LDFLAGS := -pthread -lrt
ORCH_LDFLAGS := $(LDFLAGS) -lusb-1.0
VISA_CXXFLAGS ?= -I/usr/include/ni-visa
VISA_LDFLAGS := $(LDFLAGS) -lvisa
BUILD_DIR := build
BIN_DIR := $(BUILD_DIR)/bin
# --- gpr_cpp: vendored C++/CUDA 3D SAR library (originally from VNA_AlGo).
# Built automatically by the $(GPR_CPP_LIB) rule below via its own CMake
# (build_cmake.sh). CUDA headers and libnvrtc come from the pip nvidia-*
# packages inside this repo's venv (same detection as build_cmake.sh); when
# those are absent (e.g. Jetson, where CUDA comes with JetPack) the system
# toolkit at /usr/local/cuda is used. libcuda is always the system driver.
GPR_CPP_DIR ?= data_acq_and_processing/gpr_cpp
GPR_CPP_LIB ?= $(GPR_CPP_DIR)/build/libgpr_core_cpp.a
# Absolute so the derived -rpath (libnvrtc dir) survives running the binary
# from outside the repo root.
GPR_VENV ?= $(CURDIR)/.venv
GPR_CUDA_INC_DIR ?= $(dir $(firstword $(wildcard $(GPR_VENV)/lib/python*/site-packages/nvidia/*/include/nvrtc.h)))
# cuda.h can live in a DIFFERENT pip package than nvrtc.h (Jetson: cuda_runtime
# vs cuda_nvrtc); the unified cu13 package on dev machines has both in one dir.
GPR_CUDA_RT_INC_DIR ?= $(dir $(firstword $(wildcard $(GPR_VENV)/lib/python*/site-packages/nvidia/*/include/cuda.h)))
GPR_NVRTC_LIB ?= $(firstword $(wildcard $(GPR_VENV)/lib/python*/site-packages/nvidia/*/lib/libnvrtc.so*))
# System-CUDA fallback. An empty GPR_CUDA_INC_DIR would also corrupt the compile
# command: a bare `-I` swallows the next argument.
ifeq ($(strip $(GPR_CUDA_INC_DIR)),)
GPR_CUDA_INC_DIR := /usr/local/cuda/include
endif
ifeq ($(strip $(GPR_CUDA_RT_INC_DIR)),)
GPR_CUDA_RT_INC_DIR := /usr/local/cuda/include
endif
ifeq ($(strip $(GPR_NVRTC_LIB)),)
GPR_NVRTC_LIB := $(firstword $(wildcard /usr/local/cuda/lib64/libnvrtc.so*))
endif
# Deduplicated CUDA include flags (a single dir on dev machines).
GPR_CUDA_INC_FLAGS := $(addprefix -I,$(sort $(GPR_CUDA_INC_DIR) $(GPR_CUDA_RT_INC_DIR)))
GPR_CUDA_LIB_DIR := $(dir $(GPR_NVRTC_LIB))
# libnvrtc from pip ships only as libnvrtc.so.N (no .so symlink), so link the
# full path; the rpath makes the binary find it without LD_LIBRARY_PATH.
DATA_PROCESSOR_LDFLAGS := $(LDFLAGS) $(GPR_CPP_LIB) $(GPR_NVRTC_LIB) -lcuda -Wl,-rpath,$(GPR_CUDA_LIB_DIR)
INCLUDES := \
-I$(GPR_CPP_DIR)/include \
$(GPR_CUDA_INC_FLAGS) \
-Idata_acq_and_processing/common_cpp/ipc/include \
-Idata_acq_and_processing/common_cpp/config/include \
-Idata_acq_and_processing/third_party \
-Idata_acq_and_processing/third_party/eigen \
-Idata_acq_and_processing/sweep_orchestrator/include \
-Idata_acq_and_processing/sweep_orchestrator/device_drivers/interfaces \
-Idata_acq_and_processing/sweep_orchestrator/device_drivers/radar \
-Idata_acq_and_processing/sweep_orchestrator/device_drivers/switches \
-Idata_acq_and_processing/preprocessing/calibration_master/include \
-Idata_acq_and_processing/preprocessing/reference_master/include \
-Idata_acq_and_processing/preprocessing/data_preprocessor/include \
-Idata_acq_and_processing/processing/processors/include \
-Idata_acq_and_processing/processing/data_processor/include \
-Idata_acq_and_processing/processing/locator/include
COMMON_SOURCES := \
data_acq_and_processing/common_cpp/ipc/src/shm_ring.cpp \
data_acq_and_processing/common_cpp/ipc/src/shared_types.cpp \
data_acq_and_processing/common_cpp/config/src/run_config.cpp
ORCH_SOURCES := \
data_acq_and_processing/sweep_orchestrator/device_drivers/radar/librevna/librevna_minimal_driver_lifecycle.cpp \
data_acq_and_processing/sweep_orchestrator/device_drivers/radar/librevna/librevna_minimal_driver_transport.cpp \
data_acq_and_processing/sweep_orchestrator/device_drivers/radar/librevna/librevna_minimal_driver_protocol.cpp \
data_acq_and_processing/sweep_orchestrator/device_drivers/radar/remote_compact_m_k209/remote_compact_m_k209_driver.cpp \
data_acq_and_processing/sweep_orchestrator/device_drivers/switches/h7992_minimal_driver.cpp \
data_acq_and_processing/sweep_orchestrator/device_drivers/switches/hmc349a_minimal_driver.cpp \
data_acq_and_processing/sweep_orchestrator/src/sweep_orchestrator.cpp \
data_acq_and_processing/sweep_orchestrator/src/main.cpp
PREPROC_SOURCES := \
data_acq_and_processing/preprocessing/calibration_master/src/calibration_master.cpp \
data_acq_and_processing/preprocessing/calibration_master/src/channel_bundle_support.cpp \
data_acq_and_processing/preprocessing/calibration_master/src/through_calibrator.cpp \
data_acq_and_processing/preprocessing/reference_master/src/reference_master.cpp \
data_acq_and_processing/preprocessing/data_preprocessor/src/data_preprocessor.cpp \
data_acq_and_processing/preprocessing/data_preprocessor/src/main.cpp
LOCATOR_SOURCES := \
data_acq_and_processing/processing/locator/src/payload_builder.cpp \
data_acq_and_processing/processing/locator/src/tcp_server.cpp
PROCESSOR_SOURCES := \
data_acq_and_processing/processing/processors/src/bscan_processor.cpp \
data_acq_and_processing/processing/processors/src/gpr_processor.cpp \
data_acq_and_processing/processing/processors/src/passthrough_processor.cpp \
data_acq_and_processing/processing/processors/src/sar3d_processor.cpp \
data_acq_and_processing/processing/data_processor/src/processing_live_config.cpp \
data_acq_and_processing/processing/data_processor/src/data_processor.cpp \
data_acq_and_processing/processing/data_processor/src/main.cpp \
$(LOCATOR_SOURCES)
# Kamil ADC collector: standalone L-Card E-502 acquisition binary. It loads the
# proprietary libx502api/libe502api at runtime via dlopen, so it builds anywhere
# from the vendored headers alone and only needs those libraries present at run
# time. Built on demand (not part of `all`) for radar.model == kamil_adc.
KAMIL_COLLECTOR_DIR := data_acq_and_processing/kamil_adc_collector
# The collector now also drives the RF switches itself (switch-aware mode), so it
# reuses the orchestrator's switch drivers and the shared run_config loader, which
# pull in the common config/ipc/locator headers and the vendored nlohmann/json.
KAMIL_COLLECTOR_INCLUDES := \
-I$(KAMIL_COLLECTOR_DIR)/include \
-I$(KAMIL_COLLECTOR_DIR)/vendor/lcard \
-Idata_acq_and_processing/common_cpp/ipc/include \
-Idata_acq_and_processing/common_cpp/config/include \
-Idata_acq_and_processing/sweep_orchestrator/device_drivers/interfaces \
-Idata_acq_and_processing/sweep_orchestrator/device_drivers/switches \
-Idata_acq_and_processing/processing/locator/include \
-Idata_acq_and_processing/third_party
KAMIL_COLLECTOR_LDFLAGS := -pthread -ldl -lutil
# Collector sources, including the shared drivers/config loader it reuses. These
# are compiled into a private build/kamil/ tree (see rule below) so they never
# collide with the orchestrator's objects, which use different compile flags.
KAMIL_COLLECTOR_SOURCES := \
$(KAMIL_COLLECTOR_DIR)/src/main.cpp \
$(KAMIL_COLLECTOR_DIR)/src/tty_protocol_writer.cpp \
$(KAMIL_COLLECTOR_DIR)/src/capture_file_writer.cpp \
data_acq_and_processing/common_cpp/config/src/run_config.cpp \
data_acq_and_processing/common_cpp/ipc/src/shared_types.cpp \
data_acq_and_processing/sweep_orchestrator/device_drivers/switches/h7992_minimal_driver.cpp \
data_acq_and_processing/sweep_orchestrator/device_drivers/switches/hmc349a_minimal_driver.cpp
SWEEP_ORCH_OBJS := $(addprefix $(BUILD_DIR)/,$(COMMON_SOURCES:.cpp=.o) $(ORCH_SOURCES:.cpp=.o))
PREPROCESSOR_OBJS := $(addprefix $(BUILD_DIR)/,$(COMMON_SOURCES:.cpp=.o) $(PREPROC_SOURCES:.cpp=.o))
DATA_PROCESSOR_OBJS := $(addprefix $(BUILD_DIR)/,$(COMMON_SOURCES:.cpp=.o) $(PROCESSOR_SOURCES:.cpp=.o))
KAMIL_BUILD_DIR := $(BUILD_DIR)/kamil
KAMIL_COLLECTOR_OBJS := $(addprefix $(KAMIL_BUILD_DIR)/,$(notdir $(KAMIL_COLLECTOR_SOURCES:.cpp=.o)))
DEPFILES := $(sort $(SWEEP_ORCH_OBJS:.o=.d) $(PREPROCESSOR_OBJS:.o=.d) $(DATA_PROCESSOR_OBJS:.o=.d) $(KAMIL_COLLECTOR_OBJS:.o=.d))
TARGETS := \
$(BIN_DIR)/sweep_orchestrator \
$(BIN_DIR)/data_preprocessor \
$(BIN_DIR)/data_processor
.PHONY: all clean kamil_adc_collector sar3d_parity
all: $(TARGETS)
kamil_adc_collector: $(BIN_DIR)/kamil_adc_collector
# On-demand parity check of the sar3d processor against the reference notebook
# volumes (scripts/sar3d_volume_compare.py diffs the dumped volume against a
# *_volume.npz export). Not part of `all`.
SAR3D_PARITY_OBJS := $(addprefix $(BUILD_DIR)/, \
data_acq_and_processing/processing/tools/sar3d_parity_main.o \
data_acq_and_processing/processing/processors/src/sar3d_processor.o \
data_acq_and_processing/common_cpp/config/src/run_config.o \
data_acq_and_processing/common_cpp/ipc/src/shared_types.o)
sar3d_parity: $(BIN_DIR)/sar3d_parity
$(BIN_DIR)/sar3d_parity: $(SAR3D_PARITY_OBJS) $(GPR_CPP_LIB)
@mkdir -p $(BIN_DIR)
$(CXX) $(SAR3D_PARITY_OBJS) -o $@ $(DATA_PROCESSOR_LDFLAGS)
$(BIN_DIR)/sweep_orchestrator: $(SWEEP_ORCH_OBJS)
@mkdir -p $(BIN_DIR)
$(CXX) $(SWEEP_ORCH_OBJS) -o $@ $(ORCH_LDFLAGS)
$(BIN_DIR)/data_preprocessor: $(PREPROCESSOR_OBJS)
@mkdir -p $(BIN_DIR)
$(CXX) $(PREPROCESSOR_OBJS) -o $@ $(LDFLAGS)
$(BIN_DIR)/data_processor: $(DATA_PROCESSOR_OBJS) $(GPR_CPP_LIB)
@mkdir -p $(BIN_DIR)
$(CXX) $(DATA_PROCESSOR_OBJS) -o $@ $(DATA_PROCESSOR_LDFLAGS)
# Vendored gpr_cpp static lib: delegate to its CMake build (also produces the
# standalone_bp example used by sar3d_parity cross-checks).
GPR_CPP_SRC_FILES := $(wildcard $(GPR_CPP_DIR)/src/*) $(wildcard $(GPR_CPP_DIR)/include/gpr/*) \
$(wildcard $(GPR_CPP_DIR)/examples/*) $(GPR_CPP_DIR)/CMakeLists.txt $(GPR_CPP_DIR)/build_cmake.sh
$(GPR_CPP_LIB): $(GPR_CPP_SRC_FILES)
GPR_BUILD_PYTHON=OFF VENV=$(abspath $(GPR_VENV)) bash $(GPR_CPP_DIR)/build_cmake.sh
# The collector compiles into its own build/kamil/ tree with collector-only
# includes (no VISA), so the shared driver/config sources it reuses never collide
# with the orchestrator's objects of the same name. Basenames are unique, so a
# vpath lets one pattern rule find every source.
vpath %.cpp $(sort $(dir $(KAMIL_COLLECTOR_SOURCES)))
$(KAMIL_BUILD_DIR)/%.o: %.cpp
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) $(KAMIL_COLLECTOR_INCLUDES) -c $< -o $@
$(BIN_DIR)/kamil_adc_collector: $(KAMIL_COLLECTOR_OBJS)
@mkdir -p $(BIN_DIR)
$(CXX) $(KAMIL_COLLECTOR_OBJS) -o $@ $(KAMIL_COLLECTOR_LDFLAGS)
$(BUILD_DIR)/%.o: %.cpp
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) $(VISA_CXXFLAGS) $(INCLUDES) -c $< -o $@
-include $(DEPFILES)
clean:
rm -rf $(BUILD_DIR) $(GPR_CPP_DIR)/build