From e1e7fbe2fb80eec3d977333899ac95f581b246b3 Mon Sep 17 00:00:00 2001 From: zmv Date: Wed, 23 Sep 2026 19:51:56 +0800 Subject: [PATCH] Port complete CAN-aware risk pipeline to C++ --- CMakeLists.txt | 30 +- README.md | 153 +- config/camera.json | 6 + include/bounded_queue.hpp | 42 + include/camera.hpp | 32 + include/camera_pipeline.hpp | 48 + {native/include => include}/can_comm.h | 0 include/can_state.hpp | 22 + {native/include => include}/common.hpp | 9 + include/config.hpp | 5 + {native/include => include}/detector.hpp | 1 + include/door_risk.hpp | 35 + include/door_zone.hpp | 22 + include/frame_result.hpp | 96 + .../include => include}/gpu_preprocess.hpp | 0 include/image.hpp | 25 + include/latest_result.hpp | 20 + include/pipeline_types.hpp | 100 + include/pose_engine.hpp | 108 + include/records.hpp | 38 + include/result_conversion.hpp | 31 + include/serialization.hpp | 6 + include/stop_signals.hpp | 36 + {native/include => include}/trt_engine.hpp | 3 + .../include => include}/uyvy_converter.hpp | 0 {native/include => include}/v4l2_capture.h | 0 include/visualizer.hpp | 11 + {native/include => include}/zcan.h | 0 native/include/image.hpp | 11 - native/include/pipeline_types.hpp | 31 - native/include/pose_engine.hpp | 49 - native/native_api.cpp | 115 - native/pose_engine.cu | 175 - orin/README.md | 33 +- orin/SHA256SUMS | 40 +- orin/build/camera_risk | Bin 0 -> 891888 bytes orin/build/libvision_native.so | Bin 166824 -> 0 bytes orin/config/camera.json | 6 + orin/pipeline/door_mask_risk.py | 488 - orin/pipeline/door_zone_risk.py | 358 - orin/pipeline/latest_result.py | 64 - orin/pipeline/native_backend.py | 66 - orin/pipeline/pose_engine.py | 148 - orin/pipeline/pose_fusion.py | 530 - orin/pipeline/pose_geometry.py | 28 - orin/pipeline/pose_occlusion.py | 513 - orin/pipeline/pose_tracking.py | 304 - orin/pipeline/visualization.py | 116 - orin/run.sh | 4 +- orin/run_camera.py | 300 - pipeline/door_mask_risk.py | 488 - pipeline/door_zone_risk.py | 358 - pipeline/latest_result.py | 64 - pipeline/native_backend.py | 66 - pipeline/pose_engine.py | 148 - pipeline/pose_fusion.py | 530 - pipeline/pose_geometry.py | 28 - pipeline/pose_occlusion.py | 513 - pipeline/pose_tracking.py | 304 - pipeline/visualization.py | 116 - run_camera.py | 300 - run_camera.sh | 3 +- scripts/package_orin.sh | 38 +- src/camera.cpp | 66 + src/camera_pipeline.cpp | 213 + {native => src}/can_comm.cpp | 21 +- src/can_state.cpp | 39 + src/config.cpp | 29 + {native => src}/detector.cpp | 11 + src/door_risk.cpp | 137 + src/door_zone.cpp | 94 + {native => src}/gpu_preprocess.cu | 0 src/main.cpp | 69 + src/pose_engine.cu | 875 + src/serialization.cpp | 38 + {native => src}/trt_engine.cpp | 19 + {native => src}/uyvy_converter.cu | 0 {native => src}/v4l2_capture.cpp | 0 src/visualizer.cpp | 99 + third_party/nlohmann/LICENSE.MIT | 21 + third_party/nlohmann/json.hpp | 24765 ++++++++++++++++ 81 files changed, 27329 insertions(+), 6381 deletions(-) create mode 100644 config/camera.json create mode 100644 include/bounded_queue.hpp create mode 100644 include/camera.hpp create mode 100644 include/camera_pipeline.hpp rename {native/include => include}/can_comm.h (100%) create mode 100644 include/can_state.hpp rename {native/include => include}/common.hpp (89%) create mode 100644 include/config.hpp rename {native/include => include}/detector.hpp (84%) create mode 100644 include/door_risk.hpp create mode 100644 include/door_zone.hpp create mode 100644 include/frame_result.hpp rename {native/include => include}/gpu_preprocess.hpp (100%) create mode 100644 include/image.hpp create mode 100644 include/latest_result.hpp create mode 100644 include/pipeline_types.hpp create mode 100644 include/pose_engine.hpp create mode 100644 include/records.hpp create mode 100644 include/result_conversion.hpp create mode 100644 include/serialization.hpp create mode 100644 include/stop_signals.hpp rename {native/include => include}/trt_engine.hpp (89%) rename {native/include => include}/uyvy_converter.hpp (100%) rename {native/include => include}/v4l2_capture.h (100%) create mode 100644 include/visualizer.hpp rename {native/include => include}/zcan.h (100%) delete mode 100644 native/include/image.hpp delete mode 100644 native/include/pipeline_types.hpp delete mode 100644 native/include/pose_engine.hpp delete mode 100644 native/native_api.cpp delete mode 100644 native/pose_engine.cu create mode 100755 orin/build/camera_risk delete mode 100755 orin/build/libvision_native.so create mode 100644 orin/config/camera.json delete mode 100644 orin/pipeline/door_mask_risk.py delete mode 100644 orin/pipeline/door_zone_risk.py delete mode 100644 orin/pipeline/latest_result.py delete mode 100644 orin/pipeline/native_backend.py delete mode 100644 orin/pipeline/pose_engine.py delete mode 100644 orin/pipeline/pose_fusion.py delete mode 100644 orin/pipeline/pose_geometry.py delete mode 100644 orin/pipeline/pose_occlusion.py delete mode 100644 orin/pipeline/pose_tracking.py delete mode 100644 orin/pipeline/visualization.py delete mode 100644 orin/run_camera.py delete mode 100644 pipeline/door_mask_risk.py delete mode 100644 pipeline/door_zone_risk.py delete mode 100644 pipeline/latest_result.py delete mode 100644 pipeline/native_backend.py delete mode 100644 pipeline/pose_engine.py delete mode 100644 pipeline/pose_fusion.py delete mode 100644 pipeline/pose_geometry.py delete mode 100644 pipeline/pose_occlusion.py delete mode 100644 pipeline/pose_tracking.py delete mode 100644 pipeline/visualization.py delete mode 100644 run_camera.py create mode 100644 src/camera.cpp create mode 100644 src/camera_pipeline.cpp rename {native => src}/can_comm.cpp (91%) create mode 100644 src/can_state.cpp create mode 100644 src/config.cpp rename {native => src}/detector.cpp (83%) create mode 100644 src/door_risk.cpp create mode 100644 src/door_zone.cpp rename {native => src}/gpu_preprocess.cu (100%) create mode 100644 src/main.cpp create mode 100644 src/pose_engine.cu create mode 100644 src/serialization.cpp rename {native => src}/trt_engine.cpp (88%) rename {native => src}/uyvy_converter.cu (100%) rename {native => src}/v4l2_capture.cpp (100%) create mode 100644 src/visualizer.cpp create mode 100644 third_party/nlohmann/LICENSE.MIT create mode 100644 third_party/nlohmann/json.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ff1376a..98e1114 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,18 +1,24 @@ cmake_minimum_required(VERSION 3.24) -project(vision_anti_pinch_0923 LANGUAGES CXX CUDA) +project(vision_anti_pinch LANGUAGES CXX CUDA) set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CUDA_STANDARD 17) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_CUDA_ARCHITECTURES 87) find_package(CUDAToolkit REQUIRED) -find_package(OpenCV REQUIRED COMPONENTS core imgproc) +find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs highgui videoio) find_package(Threads REQUIRED) -find_path(TRT_INCLUDE NvInfer.h PATH_SUFFIXES aarch64-linux-gnu REQUIRED) -find_library(TRT_LIB nvinfer REQUIRED) -add_library(vision_native SHARED native/native_api.cpp native/v4l2_capture.cpp native/can_comm.cpp - native/trt_engine.cpp native/pose_engine.cu - native/gpu_preprocess.cu native/uyvy_converter.cu native/detector.cpp) -target_include_directories(vision_native PRIVATE native/include ${TRT_INCLUDE} ${OpenCV_INCLUDE_DIRS}) -target_link_libraries(vision_native PRIVATE ${TRT_LIB} ${OpenCV_LIBS} Threads::Threads CUDA::cudart CUDA::nppc CUDA::nppig CUDA::nppidei CUDA::nppicc - "${CMAKE_SOURCE_DIR}/orin/lib/libusbcanfd.so" "${CMAKE_SOURCE_DIR}/orin/lib/libusb-1.0.so") -set_target_properties(vision_native PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "$ORIGIN/../lib;$ORIGIN/../orin/lib;/usr/local/cuda/lib64") +find_path(TENSORRT_INCLUDE_DIR NvInfer.h PATH_SUFFIXES aarch64-linux-gnu REQUIRED) +find_library(TENSORRT_LIBRARY nvinfer REQUIRED) +add_library(camera_risk_core + src/trt_engine.cpp src/pose_engine.cu src/gpu_preprocess.cu src/uyvy_converter.cu + src/detector.cpp src/v4l2_capture.cpp src/camera.cpp src/can_comm.cpp src/can_state.cpp + src/door_risk.cpp src/door_zone.cpp src/camera_pipeline.cpp src/config.cpp src/serialization.cpp) +target_include_directories(camera_risk_core PUBLIC include third_party ${TENSORRT_INCLUDE_DIR} ${OpenCV_INCLUDE_DIRS}) +target_link_libraries(camera_risk_core PUBLIC ${TENSORRT_LIBRARY} ${OpenCV_LIBS} Threads::Threads CUDA::cudart CUDA::nppc CUDA::nppig CUDA::nppidei CUDA::nppicc + "${CMAKE_SOURCE_DIR}/orin/lib/libusbcanfd.so" "${CMAKE_SOURCE_DIR}/orin/lib/libusb-1.0.so") +target_compile_options(camera_risk_core PRIVATE $<$:-Wall;-Wextra;-Wpedantic>) +add_library(camera_risk_visualizer src/visualizer.cpp) +target_link_libraries(camera_risk_visualizer PUBLIC camera_risk_core) +add_executable(camera_risk src/main.cpp) +target_link_libraries(camera_risk PRIVATE camera_risk_visualizer) +set_target_properties(camera_risk PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "$ORIGIN/../lib;$ORIGIN/../orin/lib;/usr/local/cuda/lib64") diff --git a/README.md b/README.md index 13de587..63cfecc 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,96 @@ -# Vision Anti-Pinch — use-can +# Vision Anti-Pinch — use-can(纯 C++) -V4L2 摄像头 + CAN 门状态 + YOLO/RTMW TensorRT 推理 + CMS 风险判断。 -默认持续推理,将最新完整结果保存在内存;可视化和文件保存均按需开启。 +本分支将 CMS `run_risk.py` 的处理流程移植到 C++,复用 0920 的 TensorRT 模型、CUDA/NPP 预处理及流水线实现。运行入口、时序融合、前后门归属、CAN 门级风险过滤和结果发布均在 C++ 进程中完成,不调用 Python。 -## Orin 直接运行 +## 编译与运行 -适用:Jetson AGX Orin,JetPack 5.1.2 / L4T R35.4.1、CUDA 11.4、TensorRT 8.5.x、系统 OpenCV 4.5.4;Python 3.8+,numpy、cv2。 -`orin/` 包含编译库、TensorRT 引擎、CAN 库、摄像头驱动、Python 运行代码和标定文件,可以单独复制到其他目录。 +目标环境:Jetson AGX Orin、JetPack 5.1.2 / L4T R35.4.1、CUDA 11.4、TensorRT 8.5.x、OpenCV 4.5.4。 +源码编译另需 CMake 3.24+ 和相应开发库。 + +```bash +./scripts/build.sh # 或 make +./scripts/initialize_camera.sh # 开机后摄像头尚未初始化时执行 +./run_camera.sh # 执行 build/camera_risk +./run_camera.sh --visualize # 可选 OpenCV 本地窗口 +``` + +预编译 Orin 包: ```bash cd orin -./scripts/initialize_camera.sh # 开机后无 /dev/video5 时初始化,需要 sudo -./run.sh # 默认无可视化,结果保存在内存 -# 或开启浏览器预览:http://设备IP:8765 -./run.sh --visualize +./scripts/initialize_camera.sh +./run.sh # 脚本直接 exec C++ ELF 程序 +# 也可以直接执行: +./build/camera_risk --config config/camera.json ``` -每次只运行一个采集进程;Ctrl+C / SIGTERM 停止。关闭浏览器不会停止推理。 -系统 CUDA/TensorRT/OpenCV/Python 运行环境由目标系统提供,不随包重复分发。TensorRT 引擎不能直接用于 Thor 或其他 JetPack/GPU 环境。 +默认无窗口、不保存文件;Ctrl+C / SIGTERM 停止。`--visualize` 使用 Orin 的图形桌面,关闭窗口仅停止显示、推理继续。没有桌面环境时保持无窗口推理。没有 Python HTTP 预览服务。 -## 从源码编译 +## 流水线与结果变量 -在目标 Orin 上执行,依赖 CMake 3.24+、CUDA/NPP、TensorRT、OpenCV 开发库: +``` +V4L2 MMAP 采集 / NPP UYVY 转换(线程 1) + → YOLO TensorRT 检测(线程 2) + → RTMW TensorRT / 隔离推理 / C++ 时序融合(线程 3) + → 前后门多特征归属 / 因果中值 / mask 风险 / CAN 过滤 + → shared_ptr 最新结果变量 +``` + +摄像头使用有界队列并丢弃积压旧帧;可视化独立读取最新结果。 +下游直接链接 `camera_risk_core`,在自己的线程中读取: + +```cpp +#include "camera_pipeline.hpp" +#include "config.hpp" + +cms::CameraPipeline pipeline(cms::loadConfig("config/camera.json")); +pipeline.start(); +// 下游自己的循环中读取;首帧前 result 为空。 +auto result = pipeline.latest(); +if (result) { + const auto& risk = result->risk; + const auto& front = result->door_risks.at("front"); + const auto& rear = result->door_risks.at("rear"); + const auto& people = result->persons; // 每人 selected/smoothed/temporal 各 133 点 + const auto& assignments = result->door_assignments; + const auto& can = result->can; + // risk.risk_type / risk.risk_level / risk.risk_score(std::optional) +} +// 退出时: +pipeline.requestStop(); +pipeline.join(); +``` + +`latest()` 返回拥有数据的只读快照,读者互不消费结果,旧引用不会被新帧修改。慢读者可以跳过中间帧;异常通过 `join()` 传播。停止后的快照只代表最后结果,持续控制应检查运行状态和时间戳。 +公共结构定义见 `include/frame_result.hpp`,接口见 `include/camera_pipeline.hpp`。 + +## CAN 与风险 + +- 0x391 的 data[0..3]:左前、右前、左后、右后。 +- 默认左侧,`--can-side right` 切到右侧;值为 0 时对应门活跃。 +- 缺失或过期(默认 1000 ms)报文输出 `CAN_UNKNOWN`,分数为空,不能解释为 clear。 +- `--no-can --active-door both` 显式模拟门状态;也可选择 front/rear/none。 +- 优先使用有效手部凸包,无有效手部时用人体框兜底;transition 保留 unknown 语义。 +- 最终使用 `risk` 或 `door_risks`;`ungated_risk` 仅用于诊断。当前只接收门状态,不发送执行器指令。 +- mask 和前后门参数来自研发机位,默认 calibration_verified=false;装车后需要重新标定。 + +配置位于 `config/camera.json`,模型位于 `orin/models/engines/`。详细参数: ```bash -./scripts/build.sh # 或 make -./scripts/initialize_camera.sh # 摄像头尚未初始化时执行 -./run_camera.sh # 使用新编译的 build/libvision_native.so +./run_camera.sh --help +./run_camera.sh --duration 30 +./run_camera.sh --save-results /path/results.jsonl ``` -源码入口复用 `orin/models/engines/` 和 `orin/lib/`,不依赖旧版工程。 -修改源码后刷新直接运行包: +显式保存时每帧写一行 JSON,核心字段保留 frame_index、track_ids、boxes、skeletons、door_assignments、door_risks、risk、ungated_risk、can;文件由用户指定路径管理。`--video` 可使用同一 C++ 流程处理视频文件,尺寸需匹配配置。 -```bash -./scripts/package_orin.sh # 或 make package -cd orin && sha256sum -c SHA256SUMS -``` +## 目录与打包 -打包会替换运行代码,清除已删除的旧 Python 模块,并更新文件校验值。 +- `src/`:C++ / CUDA 源文件及 main.cpp。 +- `include/`:头文件及下游数据结构。 +- `third_party/nlohmann/`:C++ JSON 依赖及许可证。 +- `config/`、`annotations/`:运行配置与标定。 +- `scripts/`:编译、摄像头初始化、打包。 +- `orin/`:可单独复制的 ELF 程序、模型、驱动、CAN 库和配置。 -## 下游读取结果 - -在同一个 Python 进程中创建流水线,然后读取单个最新只读快照: - -```python -from run_camera import CameraPipeline, parse_args - -with CameraPipeline(parse_args([])) as pipeline: - result = pipeline.wait_for_result(timeout=10) - # 下游自己的循环/线程中读取;首帧前为 None: - result = pipeline.latest_result # 等价于 pipeline.latest() - if result is not None: - print(result['frame_index'], result['risk']) - door_risks = result['door_risks'] - skeletons = result['skeletons'] -``` - -结果字段:`frame_index`、`track_ids`、`boxes`、`skeletons`(每人 133 点)、`door_assignments`、`active_door`、`door_risks`、`risk`、`ungated_risk`、`can`、`capture`、`capture_to_result_ms`、`calibration_verified`。 - -- 读取不消费结果;多个读者可以持有同一个不可变快照,旧快照不会被下一帧改写。 -- 只保留最新结果,慢读者可以跳过帧;`wait_for_result(last_frame_index, timeout)` 可等待更新。 -- `request_stop()` 请求停止,`join()` 等待退出并传播异常;`with` 自动回收资源。 -- 停止后仍保留最后快照;下游应检查运行状态和帧时间,避免使用过期结果。 -- `result_to_dict(result)` 生成可修改、可序列化的副本。变量属于当前进程,不是跨进程共享内存。 - -## CAN、风险与输出选项 - -`0x391` 的 data[0..3] 依次为左前、右前、左后、右后。默认 `--can-side left`,右侧使用 `--can-side right`。值为 0 表示对应门活跃;报文缺失或超过默认 1000 ms 时,输出 `CAN_UNKNOWN`、分数 null。当前接口不发送防夹执行器指令。 - -优先用融合后的有效手部区域判断风险,无有效手部时使用人体框兜底;最终读取 `risk` / `door_risks`。`ungated_risk` 仅供诊断,`unknown` 不能当作 clear。 -标定文件来自研发机位,默认 `calibration_verified=false`,换机位须重新标定。 - -| 参数 | 作用 | -|---|---| -| `--visualize` | 开启独立绘制线程及 HTTP 预览;默认不绘制、不编码、不监听端口 | -| `--save-results` | 保存逐帧 JSONL 及 latest.json,默认不保存 | -| `--save-snapshots` | 每约 10 秒保存叠加图像 | -| `--write-video` | 保存叠加视频,可配合 `--duration` 限时运行 | -| `--output PATH` | 指定保存目录,本身不启用保存 | -| `--no-can --active-door both` | 显式模拟门状态 | - -运行 `./run_camera.sh --help` 或 `orin/run.sh --help` 查看全部选项。JSONL 每 32 MiB 轮换,保留两个历史文件;视频不轮换。开启可视化后可读取 `/result.json` 和 `/frame.jpg`。 - -## 目录 - -- `native/`:实际参与编译的摄像头/CAN、CUDA/NPP、TensorRT 源码及头文件。 -- `pipeline/`:运行时姿态融合、跟踪、遮挡处理、风险、可视化和结果变量。 -- `annotations/`:源码运行时的标定文件。 -- `scripts/`:构建、打包、摄像头初始化。 -- `orin/`:可单独复制并直接执行的运行包。 +修改源码后,在 Orin 重新编译并执行 `./scripts/package_orin.sh`,或执行 `make package`。运行、打包均不使用 Python。打包后可在 orin 目录运行 `sha256sum -c SHA256SUMS`。 diff --git a/config/camera.json b/config/camera.json new file mode 100644 index 0000000..9723b91 --- /dev/null +++ b/config/camera.json @@ -0,0 +1,6 @@ +{ + "camera": {"device":"/dev/video5","width":1920,"height":1536,"fps":30,"buffers":4,"timeout_ms":2000}, + "inference": {"engine_directory":"../orin/models/engines","detector_confidence":0.30,"max_people":3,"queue_capacity":2}, + "door": {"mask":"../annotations/constant_door_mask.json","zone_config":"../annotations/door_zone_multifeature.json","warning_relative":0.30,"release_relative":0.45,"calibration_verified":false}, + "can": {"enabled":true,"side":"left","timeout_ms":1000} +} diff --git a/include/bounded_queue.hpp b/include/bounded_queue.hpp new file mode 100644 index 0000000..27369b9 --- /dev/null +++ b/include/bounded_queue.hpp @@ -0,0 +1,42 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace cms { +// FIFO with bounded ownership. Normal close drains; cancellation may discard. +template class BoundedQueue { +public: + explicit BoundedQueue(std::size_t capacity):capacity_(capacity) { + if(!capacity)throw std::invalid_argument("Queue capacity must be positive"); + } + bool push(T item) { + std::unique_lock lock(mutex_); + changed_.wait(lock,[&]{return closed_||items_.size() lock(mutex_); + if(closed_)return false; + if(items_.size()>=capacity_)items_.pop_front(); + items_.push_back(std::move(item));changed_.notify_all();return true; + } + std::optional pop() { + std::unique_lock lock(mutex_); + changed_.wait(lock,[&]{return closed_||!items_.empty();}); + if(items_.empty())return {}; + T item=std::move(items_.front());items_.pop_front();changed_.notify_all();return item; + } + void close(bool discard=false) { + std::lock_guard lock(mutex_); + closed_=true;if(discard)items_.clear();changed_.notify_all(); + } +private: + const std::size_t capacity_; + std::mutex mutex_;std::condition_variable changed_; + std::deque items_;bool closed_{}; +}; +} diff --git a/include/camera.hpp b/include/camera.hpp new file mode 100644 index 0000000..b115595 --- /dev/null +++ b/include/camera.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "image.hpp" +#include +#include +#include +#include + +namespace cms { +struct CameraConfig { + std::string device{"/dev/video5"}; + std::string video; + int width{1920},height{1536},fps{30},buffers{4},timeout_ms{2000}; +}; +struct CapturedFrame { + CaptureMetadata metadata; + std::shared_ptr image; +}; +// The capture thread exclusively owns read(); requestStop() is thread safe. +class Camera { +public: + explicit Camera(CameraConfig config); + ~Camera(); + Camera(Camera const&)=delete; + Camera& operator=(Camera const&)=delete; + std::optional read(); + void requestStop() noexcept; +private: + struct Impl; + std::unique_ptr impl_; +}; +std::uint64_t monotonicNs(); +} diff --git a/include/camera_pipeline.hpp b/include/camera_pipeline.hpp new file mode 100644 index 0000000..cacc815 --- /dev/null +++ b/include/camera_pipeline.hpp @@ -0,0 +1,48 @@ +#pragma once +#include "camera.hpp" +#include "frame_result.hpp" +#include +#include + +namespace cms { +struct PipelineConfig { + CameraConfig camera; + std::filesystem::path engines,door_mask,door_zone_config; + bool can_enabled{true}; + std::string can_side{"left"},active_door{"can"}; + double can_timeout_ms{1000}; + std::uint64_t max_frames{}; + std::filesystem::path results_path,pose_records_path; + double detector_confidence{0.30}; + int max_people{3}; + double warning_relative{0.30},release_relative{0.45}; + std::size_t queue_capacity{2}; + bool door_mask_calibration_verified{false}; +}; +struct PipelineStats { + std::uint64_t captured{},published{},driver_sequence_gaps{},discarded_error_frames{}; + double mean_capture_to_result_ms{}; +}; +// One lifecycle per instance. start() warms engines before starting capture. +// Runs continuously until requestStop() or a worker failure. +// latest() reads the immutable result variable without removing or acknowledging it. +// No result queue, consumer registration or frame-count termination. +// Stop is safe from any thread; +// destruction closes queues, interrupts capture and joins every worker. +class CameraPipeline { +public: + explicit CameraPipeline(PipelineConfig config); + ~CameraPipeline(); + CameraPipeline(CameraPipeline const&)=delete; + CameraPipeline& operator=(CameraPipeline const&)=delete; + void start(); + FrameResultPtr latest() const; + bool running() const noexcept; + void requestStop() noexcept; + void join(); + PipelineStats stats() const; +private: + struct Impl; + std::unique_ptr impl_; +}; +} diff --git a/native/include/can_comm.h b/include/can_comm.h similarity index 100% rename from native/include/can_comm.h rename to include/can_comm.h diff --git a/include/can_state.hpp b/include/can_state.hpp new file mode 100644 index 0000000..2471bc7 --- /dev/null +++ b/include/can_state.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include +namespace cms { +struct CanSnapshot { + std::array positions{{-1,-1,-1,-1}}; // LF, RF, LR, RR + double age_ms{-1}; + bool valid{false}; + std::string source{"hardware"},side{"left"}; +}; +class CanInput { +public: + explicit CanInput(bool enabled); + ~CanInput(); + CanInput(CanInput const&)=delete; + CanInput& operator=(CanInput const&)=delete; + CanSnapshot snapshot(std::string const& side,double timeoutMs) const; +private: + bool opened_{}; +}; +std::string activeDoor(CanSnapshot const& state); +} diff --git a/native/include/common.hpp b/include/common.hpp similarity index 89% rename from native/include/common.hpp rename to include/common.hpp index a59285b..ec49854 100644 --- a/native/include/common.hpp +++ b/include/common.hpp @@ -61,6 +61,15 @@ inline std::vector readBinary(std::filesystem::path const& path) { return bytes; } +inline void writeBinary(std::filesystem::path const& path, void const* data, std::size_t size) { + std::filesystem::create_directories(path.parent_path()); + std::ofstream stream(path, std::ios::binary); + if (!stream) { + throw std::runtime_error("Cannot write " + path.string()); + } + stream.write(static_cast(data), static_cast(size)); +} + inline std::size_t volume(nvinfer1::Dims const& dims) { std::size_t value = 1; for (int index = 0; index < dims.nbDims; ++index) { diff --git a/include/config.hpp b/include/config.hpp new file mode 100644 index 0000000..66fdda5 --- /dev/null +++ b/include/config.hpp @@ -0,0 +1,5 @@ +#pragma once +#include "camera_pipeline.hpp" +namespace cms { +PipelineConfig loadConfig(std::filesystem::path const& path); +} diff --git a/native/include/detector.hpp b/include/detector.hpp similarity index 84% rename from native/include/detector.hpp rename to include/detector.hpp index 8457757..1af96f4 100644 --- a/native/include/detector.hpp +++ b/include/detector.hpp @@ -14,4 +14,5 @@ private: std::unique_ptr preprocess_; std::vector outputHost_; }; +cv::Mat maskOtherPeople(cv::Mat const& frame,std::vector const& boxes,size_t target); } diff --git a/include/door_risk.hpp b/include/door_risk.hpp new file mode 100644 index 0000000..e11ddb5 --- /dev/null +++ b/include/door_risk.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include +#include +#include +using Json = nlohmann::ordered_json; +namespace cms { +struct DoorOptions { + std::filesystem::path constant, directory; + std::string format{"auto"}; + std::set labels{"door", "车门"}; + int offset{}; + bool require{}; + double warning{0.30}, release{0.45}; +}; +class DoorRisk { +public: + DoorRisk(DoorOptions options, cv::Size size); + void initialize() { load(0); } + Json evaluate(int frame, Json& record); + cv::Mat const& mask() const { return mask_; } +private: + void load(int frame); + cv::Mat rasterize(std::filesystem::path const& path) const; + DoorOptions options_; + cv::Size size_; + std::string format_, status_; + std::filesystem::path loadedPath_; + std::map paths_; + cv::Mat mask_, distance_; + bool cached_{}; +}; +double rounded(double value, int digits); +} diff --git a/include/door_zone.hpp b/include/door_zone.hpp new file mode 100644 index 0000000..274d855 --- /dev/null +++ b/include/door_zone.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "door_risk.hpp" +#include +#include + +namespace cms { +class DoorZoneAssigner { +public: + explicit DoorZoneAssigner(std::filesystem::path const& configuration); + Json update(int frame, Json const& record, cv::Size size); +private: + std::string classify(double probability) const; + Json features_; + double frontMin_{},rearMax_{},minimumHeight_{}; + int minimumPoseFeatures_{},historyFrames_{},staleFrames_{}; + std::unordered_map> history_; + std::unordered_map lastSeen_; +}; +struct DoorDecisions { Json doors; Json selected; }; +DoorDecisions buildDoorRisks(Json const& raw, Json const& assignments, + std::string const& activeDoor, bool canValid); +} diff --git a/include/frame_result.hpp b/include/frame_result.hpp new file mode 100644 index 0000000..1e8f37b --- /dev/null +++ b/include/frame_result.hpp @@ -0,0 +1,96 @@ +#pragma once +// Public downstream contract. Owns its data; no GPU, JSON or network handles. +#include "image.hpp" +#include "can_state.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace cms { +struct DoorMaskResult { + std::string status; + int annotation_index{}; + std::optional path; + std::string format; + int area_px{}; +}; +struct RegionRisk { + std::string status; + std::optional risk_score; + std::string risk_level; + std::string decision; + std::string source; + std::string door_zone; + int track_id{}; + std::string side; + std::optional state; + // Unavailable evidence has no geometric measurements, matching the reference. + std::optional region_area_px, door_overlap_px; + std::optional region_overlap_ratio, min_distance_px; + std::optional region_scale_px, distance_relative; +}; +struct RiskResult { + std::string risk_type; + std::optional door_id; + std::string status; + std::optional risk_score; + std::string risk_level; + std::string decision; + std::optional source; + std::optional track_id; + std::optional side; + DoorMaskResult door_mask; + double warning_distance_relative{}, release_distance_relative{}; + std::vector regions; +}; +struct PosePoint { double x{},y{},score{}; }; +struct HandState { + std::string state, source; + bool limb_consensus{}, distance_eligible{}, alarm_eligible{}; +}; +struct PersonResult { + int track_id{}; + double confidence{}; + std::array box{}; + std::array selected{}, smoothed{}, temporal{}; + std::array hands; // left, right +}; +struct HandRegion { + int person_id{},track_id{}; + std::string side; + std::array keypoints{}; +}; +struct StageTiming { + double detector_ms{}, pose_ms{}, fusion_risk_ms{}, capture_to_result_ms{}; +}; +struct DoorAssignment { + int track_id{}, person_index{}, pose_feature_count{}, history_frames{}; + std::string door_zone, raw_door_zone, evidence_source; + double front_probability{}, smoothed_front_probability{}, confidence{}; + std::map features, feature_frontness; +}; +struct FrameResult { + int frame_index{}; + std::vector track_ids; + std::vector> boxes; + RiskResult risk; + RiskResult ungated_risk; + std::map door_risks; + std::vector door_assignments; + std::string active_door; + CanSnapshot can; + CaptureMetadata capture; + StageTiming timing; + std::vector persons; + std::vector hand_regions; + // Both images belong to this result. The constant mask is shared across frames. + std::shared_ptr image; + std::shared_ptr door_mask_pixels; + bool door_mask_calibration_verified{}; +}; +using FrameResultPtr = std::shared_ptr; +} diff --git a/native/include/gpu_preprocess.hpp b/include/gpu_preprocess.hpp similarity index 100% rename from native/include/gpu_preprocess.hpp rename to include/gpu_preprocess.hpp diff --git a/include/image.hpp b/include/image.hpp new file mode 100644 index 0000000..e69a87c --- /dev/null +++ b/include/image.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include + +namespace cms { +// Immutable when published. Stride is explicit; no borrowed camera/GPU memory. +struct BgrImage { + int width{}, height{}, stride{}; + std::vector pixels; +}; +struct BinaryMask { + int width{}, height{}, stride{}; + std::vector pixels; +}; +struct CaptureMetadata { + std::uint64_t frame_index{}; + std::uint32_t driver_sequence{}; + std::uint64_t driver_timestamp_ns{}; + std::uint64_t received_monotonic_ns{}; + std::uint64_t capture_monotonic_ns{}; + bool driver_timestamp_monotonic{}; + std::uint32_t skipped_driver_frames{}; + std::uint32_t discarded_error_frames{}; +}; +} diff --git a/include/latest_result.hpp b/include/latest_result.hpp new file mode 100644 index 0000000..d6e7623 --- /dev/null +++ b/include/latest_result.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "frame_result.hpp" +#include +#include + +namespace cms { +// One immutable result variable. Readers neither remove nor acknowledge results. +class LatestResult { +public: + void publish(FrameResultPtr result) { + if(!result)throw std::invalid_argument("Cannot publish a null result"); + std::atomic_store_explicit(&latest_,std::move(result),std::memory_order_release); + } + FrameResultPtr load() const { + return std::atomic_load_explicit(&latest_,std::memory_order_acquire); + } +private: + FrameResultPtr latest_; +}; +} diff --git a/include/pipeline_types.hpp b/include/pipeline_types.hpp new file mode 100644 index 0000000..331e693 --- /dev/null +++ b/include/pipeline_types.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace fangjia { + +struct Box { + float x1{}; + float y1{}; + float x2{}; + float y2{}; + float score{}; + int trackId{}; +}; + +struct Keypoint { + float x{}; + float y{}; + float score{}; +}; + +struct PersonPose { + Box box; + std::array keypoints{}; +}; + +enum class LimbState { + Observed, + Provisional, + Recovered, + Reassigned, + Interpolated, + PredictedShort, + ReachableMedium, + ReachableLong, +}; + +inline char const* limbStateName(LimbState state) { + switch (state) { + case LimbState::Observed: return "observed"; + case LimbState::Provisional: return "provisional"; + case LimbState::Recovered: return "recovered"; + case LimbState::Reassigned: return "reassigned"; + case LimbState::Interpolated: return "interpolated"; + case LimbState::PredictedShort: return "predicted_short"; + case LimbState::ReachableMedium: return "reachable_medium"; + case LimbState::ReachableLong: return "reachable_long"; + } + return "unknown"; +} + +struct LimbObservation { + int trackId{}; + bool left{}; + LimbState state{LimbState::ReachableLong}; + Keypoint elbow{}; + Keypoint wrist{}; + std::array hand{}; + Box personBox{}; + bool handValid{}; + bool alarmEligible{}; + float handQuality{}; +}; + +// Exact pre-V2 hand evidence consumed by the Python risk stage. These points +// come from raw/isolated fusion before temporal reconstruction. +struct GlobalHand { + int personId{}; + int trackId{}; + bool left{}; + std::string source; + std::string state; + bool distanceEligible{}; + bool alarmEligible{}; + float score{}; + float spanPx{}; + std::array keypoints{}; + std::vector> aliases; +}; + +struct FusionMeta { + bool limbConsensus{}, handValid{}, distanceEligible{}, alarmEligible{}; + double handScore{}; + std::string handSource, handState; +}; + +struct PoseFrameResult { + std::vector> metadata; + std::vector selectedPeople; + std::vector smoothedPeople; + std::vector v2People; + std::vector limbs; + std::vector globalHands; +}; + +} // namespace fangjia diff --git a/include/pose_engine.hpp b/include/pose_engine.hpp new file mode 100644 index 0000000..ee707e7 --- /dev/null +++ b/include/pose_engine.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include "pipeline_types.hpp" +#include "trt_engine.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace fangjia { + +class BoxTracker { +public: + std::vector update(std::vector& boxes, int frameIndex, int width, int height); + +private: + struct Track { Box box; int lastFrame{}; }; + int nextId_{1}; + std::unordered_map tracks_; +}; + +class RtmwEngine { +public: + RtmwEngine( + std::filesystem::path const& enginePath, + TrtLogger& logger, + int maxSourceWidth, + int maxSourceHeight, + int maxPeople = 3); + ~RtmwEngine(); + + RtmwEngine(RtmwEngine const&) = delete; + RtmwEngine& operator=(RtmwEngine const&) = delete; + + std::vector infer( + cv::Mat const& frame, + std::vector const& boxes, + cudaStream_t stream, + float* preprocessMs = nullptr, + float* inferenceMs = nullptr, + float* postprocessMs = nullptr); + + void warmup(cudaStream_t stream); + +private: + TrtEngine engine_; + int maxSourceWidth_{}; + int maxSourceHeight_{}; + int maxPeople_{}; + std::uint8_t* sourceDevice_{}; + std::uint8_t* cropsDevice_{}; + float* networkDevice_{}; + std::vector centersHost_; + std::vector scalesHost_; + std::vector simccXHost_; + std::vector simccYHost_; +}; + +class PoseFusionTracker { +public: + PoseFrameResult update( + std::vector const& raw, + std::vector const& isolated, + int frameIndex); + +private: + struct Vec2 { double x{}; double y{}; }; + struct SideFusion { + std::array limbPrevious{}; + std::array limbVelocity{}; + std::array handPrevious{}; + std::array handVelocity{}; + bool hasLimbPrevious{}; + bool hasLimbVelocity{}; + bool hasHandPrevious{}; + bool hasHandVelocity{}; + std::string limbSource; + std::string handSource; + }; + struct Endpoint { + bool hasWrist{}; + Vec2 wrist{}; + Vec2 velocity{}; + std::array handShape{}; + bool hasHandShape{}; + int lastObservedFrame{-1}; + int missedFrames{}; + bool hasLastElbow{}; + Vec2 lastElbow{}; + bool hasLastDirection{}; + Vec2 lastDirection{}; + }; + struct PersonState { + std::array fusion; + std::array endpoints; + std::array>, 2> boneSamples; + std::array smoothed{}; + bool hasSmoothed{}; + int lastFrame{-1}; + }; + std::unordered_map states_; +}; + +} // namespace fangjia diff --git a/include/records.hpp b/include/records.hpp new file mode 100644 index 0000000..e9b79ac --- /dev/null +++ b/include/records.hpp @@ -0,0 +1,38 @@ +#pragma once +#include "door_risk.hpp" +#include "pipeline_types.hpp" +namespace cms { +inline Json xy(std::vector const& people, int digits=3) { + Json a=Json::array(); for(auto const& p:people) { Json k=Json::array();for(auto const& v:p.keypoints) k.push_back({rounded(v.x,digits),rounded(v.y,digits)});a.push_back(k); } return a; +} +inline Json scores(std::vector const& people) { + Json a=Json::array();for(auto const& p:people) {Json k=Json::array();for(auto const& v:p.keypoints) k.push_back(v.score);a.push_back(k);}return a; +} +inline Json makeRecord(int frame,std::vector const& raw,std::vector const& isolated,fangjia::PoseFrameResult const& fused, bool diagnostics=true, bool visual=true) { + Json r={{"frame_index",frame},{"track_ids",Json::array()},{"boxes",Json::array()},{"det_confidences",Json::array()}}; + for(auto const&p:raw) {auto const&b=p.box;r["track_ids"].push_back(b.trackId);r["boxes"].push_back({rounded(b.x1,3),rounded(b.y1,3),rounded(b.x2,3),rounded(b.y2,3)});r["det_confidences"].push_back(rounded(b.score,4));} + if(diagnostics) { + r["raw_keypoints"]=xy(raw,9);r["raw_scores"]=scores(raw);r["isolated_keypoints"]=xy(isolated,9);r["isolated_scores"]=scores(isolated); + r["inference_boxes"]=Json::array();for(auto const&p:raw)r["inference_boxes"].push_back({p.box.x1,p.box.y1,p.box.x2,p.box.y2}); + } + if(visual||diagnostics) { + r["temporal_selected_keypoints"]=xy(fused.selectedPeople);r["temporal_keypoints"]=xy(fused.smoothedPeople);r["v2_keypoints"]=xy(fused.v2People); + r["temporal_metadata"]=Json::array();r["v2_metadata"]=Json::array(); + for(size_t p=0;p inline std::optional optionalField(Json const& j,char const* key) { + auto it=j.find(key);return it==j.end()||it->is_null()?std::nullopt:std::optional(it->get()); +} +inline RiskResult parseRisk(Json const& j) { + RiskResult r;r.status=j.at("status");r.risk_score=optionalField(j,"risk_score");r.risk_level=j.at("risk_level");r.decision=j.at("decision"); + r.risk_type=j.value("risk_type",std::string());r.door_id=optionalField(j,"door_id"); + r.source=optionalField(j,"source");r.track_id=optionalField(j,"track_id");r.side=optionalField(j,"side"); + if(j.contains("door_mask")) {auto const& d=j.at("door_mask");r.door_mask={d.at("status"),d.at("annotation_index"),optionalField(d,"path"),d.at("format"),d.at("area_px")};} + r.warning_distance_relative=j.value("warning_distance_relative",.30);r.release_distance_relative=j.value("release_distance_relative",.45); + for(auto const& x:j.at("regions")) { + RegionRisk v;v.status=x.at("status");v.risk_score=optionalField(x,"risk_score");v.risk_level=x.at("risk_level");v.decision=x.at("decision");v.source=x.at("source"); + v.track_id=x.at("track_id");v.side=x.at("side");v.state=optionalField(x,"state");v.door_zone=x.value("door_zone",std::string()); + v.region_area_px=optionalField(x,"region_area_px");v.door_overlap_px=optionalField(x,"door_overlap_px"); + v.region_overlap_ratio=optionalField(x,"region_overlap_ratio");v.min_distance_px=optionalField(x,"min_distance_px"); + v.region_scale_px=optionalField(x,"region_scale_px");v.distance_relative=optionalField(x,"distance_relative");r.regions.push_back(std::move(v)); + } + return r; +} +inline DoorAssignment parseAssignment(Json const& j) { + DoorAssignment a;a.track_id=j.at("track_id");a.person_index=j.at("person_index");a.door_zone=j.at("door_zone");a.raw_door_zone=j.at("raw_door_zone"); + a.front_probability=j.at("front_probability");a.smoothed_front_probability=j.at("smoothed_front_probability");a.confidence=j.at("confidence");a.evidence_source=j.at("evidence_source"); + a.pose_feature_count=j.at("pose_feature_count");a.history_frames=j.at("history_frames");a.features=j.at("features").get>();a.feature_frontness=j.at("feature_frontness").get>();return a; +} +inline FrameResult makeFrameResult(Json const& record,Json const& raw,CaptureMetadata capture) { + FrameResult r;r.frame_index=record.at("frame_index");r.track_ids=record.at("track_ids").get>();r.boxes=record.at("boxes").get>>();r.capture=capture;r.ungated_risk=parseRisk(raw);return r; +} +} diff --git a/include/serialization.hpp b/include/serialization.hpp new file mode 100644 index 0000000..663cffe --- /dev/null +++ b/include/serialization.hpp @@ -0,0 +1,6 @@ +#pragma once +#include "frame_result.hpp" +#include "door_risk.hpp" +namespace cms { +Json serializeResult(FrameResult const& result); +} diff --git a/include/stop_signals.hpp b/include/stop_signals.hpp new file mode 100644 index 0000000..bd403ff --- /dev/null +++ b/include/stop_signals.hpp @@ -0,0 +1,36 @@ +#pragma once +#include "camera_pipeline.hpp" +#include +#include +#include +#include +#include + +namespace cms { +// Construct before starting workers so all threads inherit the signal mask. +// Stop requests run in an ordinary thread, never an asynchronous signal handler. +class StopSignals { +public: + explicit StopSignals(CameraPipeline& pipeline):pipeline_(pipeline) { + sigemptyset(&signals_);sigaddset(&signals_,SIGINT);sigaddset(&signals_,SIGTERM); + if(pthread_sigmask(SIG_BLOCK,&signals_,&old_)!=0)throw std::runtime_error("Cannot block termination signals"); + try { + thread_=std::thread([this]{ + while(!done_) { + timespec timeout{0,100000000}; + int signal=sigtimedwait(&signals_,nullptr,&timeout); + if(signal==SIGINT||signal==SIGTERM){pipeline_.requestStop();break;} + } + }); + }catch(...){pthread_sigmask(SIG_SETMASK,&old_,nullptr);throw;} + } + ~StopSignals(){done_=true;if(thread_.joinable())thread_.join();pthread_sigmask(SIG_SETMASK,&old_,nullptr);} + StopSignals(StopSignals const&)=delete; + StopSignals& operator=(StopSignals const&)=delete; +private: + CameraPipeline& pipeline_; + sigset_t signals_{},old_{}; + std::atomic done_{false}; + std::thread thread_; +}; +} diff --git a/native/include/trt_engine.hpp b/include/trt_engine.hpp similarity index 89% rename from native/include/trt_engine.hpp rename to include/trt_engine.hpp index de091fa..ade9299 100644 --- a/native/include/trt_engine.hpp +++ b/include/trt_engine.hpp @@ -16,6 +16,8 @@ public: void setInputShape(std::string const& name, nvinfer1::Dims const& shape); void prepare(); + void bindExternal(std::string const& name, void* pointer, std::size_t bytes); + void useOwned(std::string const& name); bool enqueue(cudaStream_t stream); void* device(std::string const& name); @@ -31,6 +33,7 @@ private: void* active{}; std::size_t capacity{}; std::size_t required{}; + bool external{}; }; Buffer& buffer(std::string const& name); diff --git a/native/include/uyvy_converter.hpp b/include/uyvy_converter.hpp similarity index 100% rename from native/include/uyvy_converter.hpp rename to include/uyvy_converter.hpp diff --git a/native/include/v4l2_capture.h b/include/v4l2_capture.h similarity index 100% rename from native/include/v4l2_capture.h rename to include/v4l2_capture.h diff --git a/include/visualizer.hpp b/include/visualizer.hpp new file mode 100644 index 0000000..59494b2 --- /dev/null +++ b/include/visualizer.hpp @@ -0,0 +1,11 @@ +#pragma once +#include "frame_result.hpp" +#include + +namespace cms { +class CameraPipeline; +// Pure drawing from the immutable result. Never re-runs inference. +cv::Mat renderResult(FrameResult const& result); +// Optional observer: closing the window returns without stopping the pipeline. +void runLocalVisualization(CameraPipeline const& pipeline); +} diff --git a/native/include/zcan.h b/include/zcan.h similarity index 100% rename from native/include/zcan.h rename to include/zcan.h diff --git a/native/include/image.hpp b/native/include/image.hpp deleted file mode 100644 index 1c57700..0000000 --- a/native/include/image.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include -#include - -namespace cms { -// Immutable when published. Stride is explicit; no borrowed camera/GPU memory. -struct BgrImage { - int width{}, height{}, stride{}; - std::vector pixels; -}; -} // namespace cms diff --git a/native/include/pipeline_types.hpp b/native/include/pipeline_types.hpp deleted file mode 100644 index eab0349..0000000 --- a/native/include/pipeline_types.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace fangjia { - -struct Box { - float x1{}; - float y1{}; - float x2{}; - float y2{}; - float score{}; - int trackId{}; -}; - -struct Keypoint { - float x{}; - float y{}; - float score{}; -}; - -struct PersonPose { - Box box; - std::array keypoints{}; -}; - -} // namespace fangjia diff --git a/native/include/pose_engine.hpp b/native/include/pose_engine.hpp deleted file mode 100644 index 19b61f6..0000000 --- a/native/include/pose_engine.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "pipeline_types.hpp" -#include "trt_engine.hpp" - -#include - -#include - -namespace fangjia { - -class RtmwEngine { -public: - RtmwEngine( - std::filesystem::path const& enginePath, - TrtLogger& logger, - int maxSourceWidth, - int maxSourceHeight, - int maxPeople = 3); - ~RtmwEngine(); - - RtmwEngine(RtmwEngine const&) = delete; - RtmwEngine& operator=(RtmwEngine const&) = delete; - - std::vector infer( - cv::Mat const& frame, - std::vector const& boxes, - cudaStream_t stream, - float* preprocessMs = nullptr, - float* inferenceMs = nullptr, - float* postprocessMs = nullptr); - - void warmup(cudaStream_t stream); - -private: - TrtEngine engine_; - int maxSourceWidth_{}; - int maxSourceHeight_{}; - int maxPeople_{}; - std::uint8_t* sourceDevice_{}; - std::uint8_t* cropsDevice_{}; - float* networkDevice_{}; - std::vector centersHost_; - std::vector scalesHost_; - std::vector simccXHost_; - std::vector simccYHost_; -}; - -} // namespace fangjia diff --git a/native/native_api.cpp b/native/native_api.cpp deleted file mode 100644 index f256516..0000000 --- a/native/native_api.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Baseline V4L2/CAN + 0920 TensorRT/NPP, with a narrow ctypes ABI. -#include "v4l2_capture.h" -#include "can_comm.h" -#include "detector.hpp" -#include "uyvy_converter.hpp" -#include -#include -#include -#include -#include -#include -#include - -namespace { -using Clock=std::chrono::steady_clock; -double nowMs(){return std::chrono::duration(Clock::now().time_since_epoch()).count();} -thread_local std::string error; -std::mutex canMutex; -int positions[4]={-1,-1,-1,-1}; -double canTime=0; -void receiveCan(int64_t, uint32_t id, const uint8_t* data, uint32_t len){ - if(id!=0x391||len<4)return; - std::lock_guard lock(canMutex); - // Baseline comments specify bytes 1.0..4.7 (not overlapping bits 0,1,2,3). - for(int i=0;i<4;++i)positions[i]=data[i]; - canTime=nowMs(); -} -struct Engine { - fangjia::TrtLogger logger; - std::unique_ptr detector; - std::unique_ptr pose; - cudaStream_t stream{}; - int width,height,maxPeople; - Engine(const char* dir,int w,int h,int n,float confidence):width(w),height(h),maxPeople(n){ - detector=std::make_unique(std::filesystem::path(dir)/"yolo11s.engine",logger,confidence,n); - pose=std::make_unique(std::filesystem::path(dir)/"rtmw_l_384.engine",logger,w,h,n); - CUDA_CHECK(cudaStreamCreateWithFlags(&stream,cudaStreamNonBlocking)); - try {pose->warmup(stream);detector->infer(cv::Mat(h,w,CV_8UC3,cv::Scalar()),stream);}catch(...){cudaStreamDestroy(stream);throw;} - } - ~Engine(){if(stream)cudaStreamDestroy(stream);} -}; -struct Camera { - V4l2Capture capture; - std::unique_ptr converter; - std::thread worker; - std::mutex mutex; - std::condition_variable ready; - std::atomic stop{false}; - std::shared_ptr latest; - uint64_t generation=0, consumed=0, sequence=0; - double timestamp=0,driverTimestamp=0; - int width,height; - bool canEnabled=false; - std::string failure; - Camera(const char* device,int w,int h,int fps,bool can):width(w),height(h){ - if(!capture.open(device)||!capture.setFormat(w,h,V4L2_PIX_FMT_UYVY))throw std::runtime_error("Camera open/format failed"); - if(capture.width()!=uint32_t(w)||capture.height()!=uint32_t(h))throw std::runtime_error("Camera negotiated unexpected dimensions"); - capture.setFps(fps); - if(!capture.initMmap(4)||!capture.startStreaming())throw std::runtime_error("Camera streaming failed"); - converter=std::make_unique(w,h); - if(can){CanComm::set_msg_callback(receiveCan);canEnabled=CanComm::can_start();} - try {worker=std::thread([this]{loop();});}catch(...){if(canEnabled)CanComm::can_stop();throw;} - } - ~Camera(){stop=true;ready.notify_all();if(worker.joinable())worker.join();capture.close();if(canEnabled)CanComm::can_stop();} - void loop(){ - try { - int misses=0; - while(!stop){ - V4l2Capture::Frame frame; - if(!capture.getFrame(frame,1000)){if(++misses>=5)throw std::runtime_error("Camera timed out for 5 seconds");continue;} - double received=nowMs(); - misses=0; - auto stride=frame.bytes_per_line?frame.bytes_per_line:width*2; - if(frame.size image; - try {image=converter->convert(frame.data,stride);}catch(...){capture.releaseFrame(frame);throw;} - auto seq=frame.sequence;double driverTs=frame.timestamp_us/1000.0; - capture.releaseFrame(frame); - {std::lock_guard lock(mutex);latest=std::move(image);sequence=seq;timestamp=received;driverTimestamp=driverTs;++generation;} - ready.notify_one(); - } - }catch(std::exception const&e){std::lock_guard lock(mutex);failure=e.what();stop=true;ready.notify_all();} - } -}; -} -extern "C" { -const char* vap_error(){return error.c_str();} -void* vap_engine(const char* dir,int w,int h,int n,float confidence){try {return new Engine(dir,w,h,n,confidence);}catch(std::exception const&e){error=e.what();return nullptr;}} -void vap_engine_close(void* p){delete static_cast(p);} -int vap_detect(void* p,unsigned char* pixels,int w,int h,float* out){try { - auto& e=*static_cast(p);auto boxes=e.detector->infer(cv::Mat(h,w,CV_8UC3,pixels),e.stream); - for(size_t i=0;i(p); - if(count<0||count>e.maxPeople||w>e.width||h>e.height)throw std::runtime_error("Pose input exceeds configured capacity"); - std::vector input;for(int i=0;iinfer(cv::Mat(h,w,CV_8UC3,pixels),input,e.stream); - for(size_t i=0;i(p);} -int vap_read(void* p,unsigned char* out,double* meta){try { - auto& c=*static_cast(p);std::shared_ptr image; - {std::unique_lock lock(c.mutex); - if(!c.ready.wait_for(lock,std::chrono::seconds(6),[&]{return c.generation>c.consumed||c.stop;}))throw std::runtime_error("No camera frame"); - if(!c.failure.empty())throw std::runtime_error(c.failure); - if(c.stop)return 0; - image=c.latest;meta[0]=c.sequence;meta[1]=c.timestamp;meta[2]=c.generation-c.consumed-1;meta[3]=c.driverTimestamp;c.consumed=c.generation;} - std::memcpy(out,image->pixels.data(),image->pixels.size());return 1; -}catch(std::exception const&e){error=e.what();return -1;}} -void vap_can(double* out){std::lock_guard lock(canMutex);for(int i=0;i<4;++i)out[i]=positions[i];out[4]=canTime?nowMs()-canTime:-1;} -} diff --git a/native/pose_engine.cu b/native/pose_engine.cu deleted file mode 100644 index abf7167..0000000 --- a/native/pose_engine.cu +++ /dev/null @@ -1,175 +0,0 @@ -#include "pose_engine.hpp" -#include "common.hpp" -#include "gpu_preprocess.hpp" - -#include - -#include -#include -#include - -namespace fangjia { -namespace { - -constexpr int kInputWidth = 288; -constexpr int kInputHeight = 384; -constexpr int kKeypoints = 133; -constexpr int kSimccX = 576; -constexpr int kSimccY = 768; - -__global__ void poseNormalizeKernel( - float* output, std::uint8_t const* input, int people, int inputStep) { - int index = blockIdx.x * blockDim.x + threadIdx.x; - int pixels = kInputWidth * kInputHeight; - int total = people * pixels; - if (index >= total) return; - int person = index / pixels; - int pixel = index - person * pixels; - int y = pixel / kInputWidth; - int x = pixel - y * kInputWidth; - auto const* value = input + person * inputStep * kInputHeight + y * inputStep + x * 3; - float* destination = output + static_cast(person) * 3 * pixels; - destination[pixel] = static_cast((static_cast(value[0]) - 123.675) / 58.395); - destination[pixels + pixel] = static_cast((static_cast(value[1]) - 116.28) / 57.12); - destination[2 * pixels + pixel] = static_cast((static_cast(value[2]) - 103.53) / 57.375); -} - -} // namespace - -RtmwEngine::RtmwEngine( - std::filesystem::path const& enginePath, TrtLogger& logger, - int maxSourceWidth, int maxSourceHeight, int maxPeople) - : engine_(enginePath, logger), maxSourceWidth_(maxSourceWidth), - maxSourceHeight_(maxSourceHeight), maxPeople_(maxPeople), - centersHost_(maxPeople * 2), scalesHost_(maxPeople * 2), - simccXHost_(static_cast(maxPeople) * kKeypoints * kSimccX), - simccYHost_(static_cast(maxPeople) * kKeypoints * kSimccY) { - engine_.setInputShape("input", nvinfer1::Dims4{1,3,kInputHeight,kInputWidth}); - engine_.prepare(); - auto xs=engine_.shape("simcc_x"),ys=engine_.shape("simcc_y"); - if(xs.nbDims!=3||ys.nbDims!=3||xs.d[0]!=1||ys.d[0]!=1||xs.d[1]!=kKeypoints||ys.d[1]!=kKeypoints||xs.d[2]!=kSimccX||ys.d[2]!=kSimccY) - throw std::runtime_error("RTMW engine I/O shape does not match the 133-point model"); - for(auto name:{"input","simcc_x","simcc_y"})if(engine_.dataType(name)!=nvinfer1::DataType::kFLOAT) - throw std::runtime_error("RTMW requires float32 I/O bindings"); - std::size_t const values = static_cast(maxPeople) * 3 * kInputWidth * kInputHeight; - CUDA_CHECK(cudaMalloc( - &cropsDevice_, static_cast(maxPeople_) * kInputWidth * kInputHeight * 3)); - CUDA_CHECK(cudaMalloc(&networkDevice_, values * sizeof(float))); - CUDA_CHECK(cudaMalloc(&sourceDevice_, static_cast(maxSourceWidth_)*maxSourceHeight_*3)); -} - -RtmwEngine::~RtmwEngine() { - if (sourceDevice_) cudaFree(sourceDevice_); - if (cropsDevice_) cudaFree(cropsDevice_); - if (networkDevice_) cudaFree(networkDevice_); -} - -void RtmwEngine::warmup(cudaStream_t stream) { - std::vector boxes{{0, 0, static_cast(maxSourceWidth_), static_cast(maxSourceHeight_), 1, 1}}; - cv::Mat black(maxSourceHeight_, maxSourceWidth_, CV_8UC3, cv::Scalar(0, 0, 0)); - for (int i = 0; i < 2; ++i) infer(black, boxes, stream); -} - -std::vector RtmwEngine::infer( - cv::Mat const& frame, std::vector const& boxes, cudaStream_t stream, - float* preprocessMs, float* inferenceMs, float* postprocessMs) { - int batch = std::min(static_cast(boxes.size()), maxPeople_); - if (batch <= 0) return {}; - auto const preprocessStarted = std::chrono::steady_clock::now(); - int const cropStep = kInputWidth * 3; - int const pixels = kInputWidth * kInputHeight; - if (frame.cols>maxSourceWidth_ || frame.rows>maxSourceHeight_) throw std::runtime_error("Pose input exceeds allocation"); - auto npp = cms::nppContext(stream); - CUDA_CHECK(cudaMemcpy2DAsync(sourceDevice_, frame.cols*3, frame.data, frame.step, frame.cols*3, frame.rows, cudaMemcpyHostToDevice, stream)); - for (int i = 0; i < batch; ++i) { - auto const& box = boxes[i]; - double width = (static_cast(box.x2) - static_cast(box.x1)) * 1.25; - double height = (static_cast(box.y2) - static_cast(box.y1)) * 1.25; - constexpr double aspect = static_cast(kInputWidth) / kInputHeight; - if (width > height * aspect) height = width / aspect; - else width = height * aspect; - centersHost_[i * 2] = 0.5 * (static_cast(box.x1) + static_cast(box.x2)); - centersHost_[i * 2 + 1] = 0.5 * (static_cast(box.y1) + static_cast(box.y2)); - scalesHost_[i * 2] = width; - scalesHost_[i * 2 + 1] = height; - - cv::Point2f source[3]{ - {static_cast(centersHost_[i * 2]), static_cast(centersHost_[i * 2 + 1])}, - {static_cast(centersHost_[i * 2]), static_cast(centersHost_[i * 2 + 1] - width * 0.5)}, - {static_cast(centersHost_[i * 2] - width * 0.5), static_cast(centersHost_[i * 2 + 1] - width * 0.5)}}; - cv::Point2f destination[3]{ - {kInputWidth * 0.5F, kInputHeight * 0.5F}, - {kInputWidth * 0.5F, kInputHeight * 0.5F - kInputWidth * 0.5F}, - {0.0F, kInputHeight * 0.5F - kInputWidth * 0.5F}}; - cv::Mat affine = cv::getAffineTransform(source, destination); - auto* cropDevice=cropsDevice_+static_cast(i)*cropStep*kInputHeight; - Npp8u fill[3]={0,0,0}; - cms::nppCheck(nppiSet_8u_C3R_Ctx(fill,cropDevice,cropStep,{kInputWidth,kInputHeight},npp)); - double coeff[2][3]; - for(int row=0;row<2;++row) for(int col=0;col<3;++col) coeff[row][col]=affine.at(row,col); - cms::nppCheck(nppiWarpAffine_8u_C3R_Ctx(sourceDevice_,{frame.cols,frame.rows},frame.cols*3,{0,0,frame.cols,frame.rows},cropDevice,cropStep,{0,0,kInputWidth,kInputHeight},coeff,NPPI_INTER_LINEAR,npp)); - } - int const totalPixels = batch * pixels; - poseNormalizeKernel<<<(totalPixels + 255) / 256, 256, 0, stream>>>( - networkDevice_, cropsDevice_, batch, cropStep); - CUDA_CHECK(cudaGetLastError()); - CUDA_CHECK(cudaStreamSynchronize(stream)); - if (preprocessMs) { - *preprocessMs = static_cast(std::chrono::duration( - std::chrono::steady_clock::now() - preprocessStarted).count()); - } - - // RTMPose's Python API invokes the ONNX/TensorRT session once per person. - // Preserve that execution contract instead of batching people together. - engine_.setInputShape("input", nvinfer1::Dims4{1, 3, kInputHeight, kInputWidth}); - engine_.prepare(); - CudaEventTimer timer; - float inferTime = 0.0F; - std::size_t const inputValues = static_cast(3) * pixels; - std::size_t const xValues = static_cast(kKeypoints) * kSimccX; - std::size_t const yValues = static_cast(kKeypoints) * kSimccY; - for (int person = 0; person < batch; ++person) { - CUDA_CHECK(cudaMemcpyAsync( - engine_.device("input"), networkDevice_ + static_cast(person) * inputValues, - inputValues * sizeof(float), cudaMemcpyDeviceToDevice, stream)); - timer.start(stream); - if (!engine_.enqueue(stream)) throw std::runtime_error("RTMW enqueue failed"); - inferTime += timer.stop(stream); - CUDA_CHECK(cudaMemcpyAsync( - simccXHost_.data() + static_cast(person) * xValues, - engine_.device("simcc_x"), xValues * sizeof(float), cudaMemcpyDeviceToHost, stream)); - CUDA_CHECK(cudaMemcpyAsync( - simccYHost_.data() + static_cast(person) * yValues, - engine_.device("simcc_y"), yValues * sizeof(float), cudaMemcpyDeviceToHost, stream)); - } - if (inferenceMs) *inferenceMs = inferTime; - - auto const postprocessStarted = std::chrono::steady_clock::now(); - CUDA_CHECK(cudaStreamSynchronize(stream)); - std::vector result(batch); - for (int person = 0; person < batch; ++person) { - result[person].box = boxes[person]; - for (int keypoint = 0; keypoint < kKeypoints; ++keypoint) { - auto xBegin = simccXHost_.begin() + (person * kKeypoints + keypoint) * kSimccX; - auto yBegin = simccYHost_.begin() + (person * kKeypoints + keypoint) * kSimccY; - auto xIt = std::max_element(xBegin, xBegin + kSimccX); - auto yIt = std::max_element(yBegin, yBegin + kSimccY); - float score = 0.5F * (*xIt + *yIt); - if(!std::isfinite(score))throw std::runtime_error("Non-finite RTMW output"); - float localX = score > 0.0F ? static_cast(xIt - xBegin) * 0.5F : -0.5F; - float localY = score > 0.0F ? static_cast(yIt - yBegin) * 0.5F : -0.5F; - double scaleX = scalesHost_[person * 2]; - double scaleY = scalesHost_[person * 2 + 1]; - result[person].keypoints[keypoint] = { - static_cast(static_cast(localX) / kInputWidth * scaleX + centersHost_[person * 2] - scaleX * 0.5), - static_cast(static_cast(localY) / kInputHeight * scaleY + centersHost_[person * 2 + 1] - scaleY * 0.5), - score}; - } - } - float post = static_cast(std::chrono::duration( - std::chrono::steady_clock::now() - postprocessStarted).count()); - if (postprocessMs) *postprocessMs = post; - return result; -} - -} // namespace fangjia diff --git a/orin/README.md b/orin/README.md index 2bd42df..022b229 100644 --- a/orin/README.md +++ b/orin/README.md @@ -1,33 +1,22 @@ -# Orin 直接运行包 +# Orin 纯 C++ 运行包 -适用 Jetson AGX Orin / JetPack 5.1.2 (R35.4.1)、CUDA 11.4、TensorRT 8.5.x、OpenCV 4.5.4。 -系统需要 Python 3.8+、numpy、cv2。无需重新编译,可单独复制整个目录。 +适用 AGX Orin / JetPack 5.1.2 (R35.4.1)、CUDA 11.4、TensorRT 8.5.x、OpenCV 4.5.4。不依赖 Python。 ```bash sha256sum -c SHA256SUMS ./scripts/initialize_camera.sh # 开机后无 /dev/video5 时执行,需要 sudo -./run.sh # 默认 CAN 模式,结果保存在内存 -./run.sh --visualize # 可选浏览器预览,http://设备IP:8765 +./run.sh # exec build/camera_risk,默认无可视化 +./run.sh --visualize # 可选 Orin 本地图形窗口 +# 直接执行 ELF: +./build/camera_risk --config config/camera.json ``` -只运行一个摄像头采集进程,Ctrl+C / SIGTERM 停止。 +只运行一个摄像头进程。Ctrl+C / SIGTERM 停止;关闭可视化窗口不停止推理。无图形桌面时继续无窗口运行。 -- data[0..3]:左前、右前、左后、右后;`--can-side left/right` 选择摄像头侧。 -- 未收到有效 CAN 或超时:CAN_UNKNOWN,score=null;不发送执行器控制报文。 -- `--save-results` 保存 JSONL;`--save-snapshots` 保存快照;`--write-video` 保存叠加视频。默认均关闭。 -- `--output PATH` 指定输出目录;`--duration 30` 限时运行。完整参数见 `./run.sh --help`。 -- 标定参数仍需匹配实际机位,默认 calibration_verified=false。 +CAN 0x391 data[0..3] 为左前、右前、左后、右后。默认左侧,`--can-side right` 切换右侧。缺失或超时报文输出 CAN_UNKNOWN,风险分数为空。 -同进程下游示例(先停止其他采集进程): +`--no-can --active-door both` 模拟门状态;`--duration 30` 限时运行;`--save-results FILE.jsonl` 显式保存结果,默认不写文件。完整参数见 `./run.sh --help`。 -```python -from run_camera import CameraPipeline, parse_args -with CameraPipeline(parse_args([])) as pipeline: - result = pipeline.wait_for_result(timeout=10) - result = pipeline.latest_result # 等价于 pipeline.latest() - if result is not None: - print(result['risk'], result['door_risks'], result['skeletons']) -``` +最新结果保存在进程内的 `shared_ptr`;C++ 下游调用 `pipeline.latest()` 读取。接口头文件位于仓库 include/,不是 Python 变量或跨进程共享内存。 -变量是线程安全的最新只读快照,读取不会消费结果,不是跨进程共享变量。 -系统 CUDA/TensorRT/OpenCV 运行库由 JetPack 提供。引擎和驱动不能直接用于其他 GPU 或不匹配的内核。 +模型、驱动和编译产物绑定目标 GPU、TensorRT 与内核环境。标定文件需匹配实际机位,默认 calibration_verified=false。系统 CUDA/TensorRT/OpenCV 库由 JetPack 提供。 diff --git a/orin/SHA256SUMS b/orin/SHA256SUMS index 57833d0..744e5e2 100644 --- a/orin/SHA256SUMS +++ b/orin/SHA256SUMS @@ -1,25 +1,15 @@ -b6295cf4b0705c5c3d83c9ba35e41b2a0b865ea67742c09cfd1690f263f556d0 README.md -9ba768d559fd3b64e833b50c62c70f2adfd6c169620aea5fba32cfb1b4c5b0e9 annotations/constant_door_mask.json -3544fb1c7b1b27b39db27d8f849d5e8652f5d09a950a59c5dd0c7703086b4f6b annotations/door_zone_multifeature.json -6d5e874fd27fcfd75d303d7e35d9672028af71041f52dcc82a1e83676faefa65 bin/drivers/max9295.ko -8a78d04ef8be44e9b62a5e4ee315d3d6d679e34aedff02a234bbf4d40e222933 bin/drivers/max9296.ko -3da628587477cc35d5e3f8aee86e718e2ddf16808e893e00ba6e164b2307e843 bin/drivers/sgx-yuv-gmsl2.ko -5ba7e4f0371a411cc1a6dfd1275e3b824032a14d07a9ccd7ca3d5a6630b322ef build/libvision_native.so -8dae8c557deadbe6bd7241c0dcd7025cb007077ee39a79b115e953a2b22cd9c1 lib/libusb-1.0.so -ab8b7f7249d5fb73d1d33d14fa7532a522088ffb6c25f37ba334daf4c6b4d313 lib/libusbcanfd.so -ebcd09e8c2e35008adbb0c1d222e580b17e0df1c8093df3481ba3374cd9a8174 models/engines/rtmw_l_384.engine -f6a65b926ac1144484f4aa93cd26d7997d32d62310ab93fe867f2bd4412222e9 models/engines/yolo11s.engine -6d6d14a972bc4e5e9f8256514169e73ba0392a301cacd1221eaa26146aad00d2 pipeline/door_mask_risk.py -46d70e1e90a1f36d544b7492ef1fbf08821377a1c460a6683f42d4d7006055c7 pipeline/door_zone_risk.py -bcf2cc99dd27c8749801a5b743656767d9d64ef03d568d227cb654c573d70b00 pipeline/latest_result.py -294cdc11aad0e5475275fd8dece2057e229b9bdedba69c26afa62176fd51a4e9 pipeline/native_backend.py -8b8c5b8c9c76c9bd2e1efa6cba60be337a18c9366e8f748038465e9e2fba2672 pipeline/pose_engine.py -93d3323ea431ed1d3c8397cfed7d800d65ec6d7700690487335861945040a486 pipeline/pose_fusion.py -2ebf58114835ba7c4779d4b66aeff65b8a94107128989c0336de556a4cf95fda pipeline/pose_geometry.py -f32fa5bebf893a3dc6b9ccfaa09a8e3a153ad5b36dde5373fdc561f10f9e526b pipeline/pose_occlusion.py -1a3f1f55d7b8a3f0ac96e69bf77f02f82017e8970adef96181d2bfade26e5bd5 pipeline/pose_tracking.py -dcf3cce5562ea52400ffa9842fdddaadac1efd672dabb0ed12ce34f20f779f37 pipeline/visualization.py -8a10e035334ed97b7480f558aff7b0464c255ac6138dd99f55a08ade68853e0e run.sh -23e9af5e82999889fe77c762a77c77036d1b1b699525ff2bd5ca8a418183ba50 run_camera.py -11b5fbc3c17c0610ec0514a0a26cf5d92588d15c0e50e7086d9db5a5c0306e99 scripts/initialize_camera.sh -bce5a22d49633454ed2c6bc92b608edf3b718af2424c283ed8f56ba93987200a scripts/initialize_camera_impl.sh +1cff77e5547d14cd188b3bb38ba9332e9ae5904646f9248c2a3904a199318738 ./README.md +9ba768d559fd3b64e833b50c62c70f2adfd6c169620aea5fba32cfb1b4c5b0e9 ./annotations/constant_door_mask.json +3544fb1c7b1b27b39db27d8f849d5e8652f5d09a950a59c5dd0c7703086b4f6b ./annotations/door_zone_multifeature.json +6d5e874fd27fcfd75d303d7e35d9672028af71041f52dcc82a1e83676faefa65 ./bin/drivers/max9295.ko +8a78d04ef8be44e9b62a5e4ee315d3d6d679e34aedff02a234bbf4d40e222933 ./bin/drivers/max9296.ko +3da628587477cc35d5e3f8aee86e718e2ddf16808e893e00ba6e164b2307e843 ./bin/drivers/sgx-yuv-gmsl2.ko +d37bb5880f921ef8d5f937e5bf9dba38539929d95430f4fb8f575a2ede691a78 ./build/camera_risk +718c43eba7c9a86178b272404b14e201445a3998dae81cd32b840f83018ba2b5 ./config/camera.json +8dae8c557deadbe6bd7241c0dcd7025cb007077ee39a79b115e953a2b22cd9c1 ./lib/libusb-1.0.so +ab8b7f7249d5fb73d1d33d14fa7532a522088ffb6c25f37ba334daf4c6b4d313 ./lib/libusbcanfd.so +ebcd09e8c2e35008adbb0c1d222e580b17e0df1c8093df3481ba3374cd9a8174 ./models/engines/rtmw_l_384.engine +f6a65b926ac1144484f4aa93cd26d7997d32d62310ab93fe867f2bd4412222e9 ./models/engines/yolo11s.engine +4e3bd58314a9d70b6b243f14c0e26ffd1b1c6dcf8bcbf4c44629f1767f72a5ec ./run.sh +11b5fbc3c17c0610ec0514a0a26cf5d92588d15c0e50e7086d9db5a5c0306e99 ./scripts/initialize_camera.sh +bce5a22d49633454ed2c6bc92b608edf3b718af2424c283ed8f56ba93987200a ./scripts/initialize_camera_impl.sh diff --git a/orin/build/camera_risk b/orin/build/camera_risk new file mode 100755 index 0000000000000000000000000000000000000000..fac012a3d0c7bf43d3da707f8e27120183b82bae GIT binary patch literal 891888 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∾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%0DOHVmv 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 z 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%0DG0gi4;K+e5%^h#b!*Adtpf)DS zH&F8uGJGJfc+54>L-p#s)$5V}yXQD|fj8fcf-CWqVn}Xi3nW6= zTaJBDnvPv|YO0a;JfEF-@W$!Uh;+jnnv6`Dd0e&ppjl{k9sTWD!hM21+KFbOQEX6u8X0KRfv&N*S%Mye?=#38 z(I*2wMTZQ!X+Zh}qpPtuul@vMB{|r6UJo3xt4_6bDh~c4#zPGGuRi4I!|m{Y=}g84 zWBPjfu%@>U$5*!!Hx@=dA1@6l86_R`aZS>?p%(kFwVC%pZh`Y%3r?msK< z7o+_;v=iP0LrZ9Mx8M;h4la+c_R0r7aNrpPJRS1Q0b6BkL_&H8-hHoHHZ(R@5P9hF zXHK%g7YAki@pj*dV|Qs!)GvGC>K{A4+J);jzAN8c{)_RY2j9|d1M}zaPIK@eTnQI< zQ`d9ID)A|O>D_O90{!UaS@#@^zwmu**9dC|DSow*^5QGO;_<9xqp3(>7JZe zebo(5di;hB(N8ACnG+TAn}J^n`HizdR#!|?tQ@)v9_4<)&&V$)|667FmGW)Dz=%~N zO=3+Q_m2~MX!or_hg#_1>3V!|@I;v-C)}8{zO@mD7Lql zUcMWjp2_;RN0$wvTS?$EHTWEd!tds9<}=2^ZAOSE_eRqW?c*Cj=3G!fQ|7xPWAg%^ zsi2+Pk)4f~20rs7^`8%&)vu>`-aY4MR<29-9Ot8TX_oi~mLr46+$9+_IuhQ}jG@dq|-&7VTE zW`2zM^TmxfG#PV1?TX6wu&H<{4sU9YnC!K+;ME(awa4x`PIu4woz*2x@C9RJLxy{M zE6qNMI`PLH!IZ}Rzrd-VN1Doj&DAx6`|FH1KkVWWXMT%a!dj&0&waVrOr`0+@-90J z`)+a!ng8cgowe8Awv)$MStEYxO^Z*pX}WKuH!U{BrpYJOn-)FRrgiw!IHyE?P^@Kd zzRb}!O>^zuwB%7X?J<8^>`0sTh(9fSgiTxFPs@JXrfKh5UwxBp+7J9`vBPcJz5cZD zB%8L-pO!t;rrqgJOCDm=?(nC@4z_8x`qQElZCcz-s|;7!_nZBBvSFKcy+18kVbiYh zr?I{z+ul5WAv9IIQ7dP?iGP^y^x+};FHf6%IGgrqe_C{`O*_M%h7VqF*89^kqix#B{VTMEzWa)b8ueb<$W|WO>ru=+{2k^@4L?` zUq@MUL+x(WldU(q=eu=uyz11knf*%oc7oEs7nmg*=1a($imwG`#o4p=FXU~-?E$;* zQ)4#(t=~8$zv$PLtGJyt(_DT=#n18I^WWS5@PW4fe^Z_ffmu=X*%8!7F9RJu zZ=xQ-|5fH#y*!ZF&+@<}eCtg!2iP>-=iuLGa(oeeEj_OIROxHgZS}Q#Hi7i_UUl|5 zM`vg1_SD%{pJwn8q0c@A&s3~*_D_BQ?YM_GaRa`kj{Q#EJ{o&oIyp82zae%2c3PYt zh0F;8&tR7lC;064yoF;lKDQgeli(37)!<67M9+3$snA&rN9ZhuNcB^|a6>Pysx2J5 z))k+(MW^(~SL~EM@s;7+>_Qu<3A^eB`q_Xjos|!8qnY&%GNS>x^a;)_Xn4}h>fl{4 zSA@oQ-&9u@(b)u1aPQGp=hBv69od7f#c-CL*dI=VyzI5*T)IY2-xE&HmJ4H(dA9zU zoZ~Foyoy{g_*GdW?V0Jr`R(9Qv=`3Bn;y=`xi~jd9h}#I^V?4n&a10M_Zxe09;ud% zG7$g7qJ8``%=%oMI}Q&y?Y2Hw)|7JC+1@-X052hb_ne2>IkB@p=qBi=_)qcR=b)eYq{DwT_z%t)1?{e8 z{n$r0_+mpbxe^Zd=Re)?P>9zsbr+74UjAeLAh{Pt{ym4@@_6wrU}(JJ)RcIwhViU% z9ftojM&#p|1g}nIOc#vj4Z^YLe;|AtWe!`x99HA;494EgzCJIezHF&eAF+OJpHHg3 zskhH3RD1JlPd+i8r-&cqZ)-r0avsX$7=3HKBbs6kH8N|*Td-UA#um=cR8BIX5g%AF z-Vkm5ydjvQtoz--c&T`pa`@2;@sT~a4<9SQjqoA8FPt<|zJ~Glj~&EayLcFO6#1c} z9p8ra4954@sNXMho5BxmS@pZ|?fi@j3d*$=lzW|W4zG^s=LhJJpNox$Tkw)4ex zj_ursBgQj0I;^0t%PRVCf(ZS>(Cp1+SoIU;dixl2<^PX`M!Q%E_FU?Y0X*n>c>`a(&!rl5oA%AxxdDg z#{CM$!|1Ck`;Ge(D5E-UJ+9A2up(=0omqYN`3;-4BG%2qd*grt&@K#>$I_xIWyFk|26W< zhR%Ug*`0fgBkXujuFOP!70MNjsnOt9az2R;co*JphudW3K6%9Ng&Ni^?U-fF zZd5PdjqS&GH^J{3OJjh^8EgH&U*mrNF1Qvy-l%;1zwEwWCK~YmBmQG`rswnS_wTv? zue$%5S5_Ufs2#qiwm$iVo$*u}5l6J9;|G|wnjmq)YQ>jT$$Ds{3hG+A^Lyk5T-P-iju zIGVmmetkzcKz)JwJweh5stvs0o3R5S(rAb4N!hJ-&FtU1N5nJ!@zwR>Eh>Y z>Mn+l+P#zwxn*^a-cZ>t$du%B`IT2um$#1N`b#%>zVQrmt{^hJ6PnBRYYf(G@gVA<9ZNDL)yO&hk{O&?>Q z^_e#{-UFPB0rg4kFm@gLFc{Q1CH?&P^2Y||y6Z;tN%OTZes1~k4eQ6k2kWOjn&Yiu zT~9vkMsRGvk?L(kcfSeEV?J5!U$ax)y=y#f{gvzywD-Id@9>@N`9=RM;F*uIW_N4U zS+k4PMI)?Rj7%P8rU)03i)((#e3COeHI9nema${N0l1TIS2ovH;F4`=`_8;&Ae%CV zEmE-p9AF>C20J{njy9^HPb2zOxDrlcgW(<8@Xw2roQc+Jzq)uZMcVT4thShs&-ya< z`I$_-TYcI!>fN;7j{_JsuJ4jqpt? zXI1#&&*1Cv+7iChP8L4#+EHHhUuy>96MggZWR7`^+O+tgJ>~Vlf&Rt zYwg+FY9kdUcy;a2{;>3G|Duo9ez@U((0;qyKK@WYF8u9BKl{j;MP1pfz zed|rFU+P`SWmXJ(dS6lBBg#D??gmfTzOHhwy_ z5AQ0rPGeqYrmeYy^U&;?Gtmv9cac zb6RIXKG-}3u|=0qhw_ORgg5ari`o&Z^EtgIBim{VzN%ic@=f^&Te@bm^Y zt{+cT$Qv)FK{nRs_@4C1gy-qAWX{dtsN$XAtazoB4Gzy|gLa)l{u03=o6@75Y|B0A zXDt16&rQ=q&VD@M+TJ^j-8G&zbVs%Ny7m*_wlO4aU;gnm>hIEhn3KWFo#;TxpxlB$ z#KtgHnkkYgl0{lSl0K8X_HutZ=OFI<xYAr_Ap6H~CqdO-XuntcC&T)$4;H`uk>pPEkJ5e&zs-N$ zzGwgZeBk!YM{GZizxuyzU-nPfvwv0t12mINxt9C2yt!l0^+Q}nn&=t-c7A0D8Du0o z8P_q=B|E;n71^;XIIH$Gd@PJ7mDhY(GD3N*%qxhsT>4i>9xAW>?vsfsS7|Da89mLJ zzrE(up^8EJmhfG`b8`yvoQXfNFpt*w{k&U+E-uWYdv?7%YD;VGg?XalZ+8sXJow=X z^SocsR&ey5dA?iNR*`<~D5z%{^NPZ@PV&K{nA^@GeGk$WbB4rITiCk*P4xT(&reXl zp4srf=vX(;PkBDief}}elK;x{5YNMDOV2BKzS7O}0MD)|AEWiPT9^1_!v1Pd!tzS05}||?HhdMUmk#W1AFn~&-9i5 z(E#P6*k#gVg7KUI@DqTi27>WiUmJfYQeJgSS603|u+4F@uZ~|7fzzv_gY}ky;5fop z$2&#f5DZz?BfP%K_9~=@*G>{xg^$mY$IE{jaCy%M`O=fV=Wp;FtBXdWv+`3b$**~C z;EDXoD&&-GJlT^g;UiC`4aFzq)p-v2ch5=n#!F7+oP`hAZ>zl9=LQpH>^<=IdTC#w z;?os}(bE;QcRr{uxhEW3vaKq*I^V!L>N>MJe_Vqf?lxV4L4}A?qOWmz-y2Xg>~h+x@`F%di8mz1AU{yXFQX+8eNw zJxaBFuSe(E^PouIcoFA8mYAL{zBPW6wSWfp4qpj;FDgzu)V-^0Njde;x<9m}{BQf1 zsf(AH#EIaCGq*m{*;kF6eU;%H_1Hw`+^Y%`cw2Ecapc-hn6JjG%<76M^piY_<*j9H zCpH|P1MM63KWkrhEqkAH;6-q?ah88G={fM-1-?EYUHeNKSTi;FruIcPH!ydMVqY|z zgg=dSuhv7+LBL(ZIbHEO;sNG{xZ7t;dc;5Si{{;PN~(uDPuo~y>Oh}se{N-LaAITG z@Kom|rf0^VnIF$BvFlaNUDo5$fgtB_QFcpc5O*h+agGJ&qeJ%)cu8W9E_frq=;jBb zR!_#3naHJ2nyJ{}^L3{r?I&`}P0ucDcg>*|pUD3L@J%|nvGKDJ#z%H8^BrQUy|gJx zn+=S-cRxk4fZ(Tk2pkb~yVe(tYMt2vFH-Fj2vSKGWI2+3O zr05gzR=y!G!(M~P(uy=|$ zVd8~|!BAU4__P67lC&k>1;3HIXS25qp4NNU+&asJDFsE}Un!2jF}0#l9XN!hcbGqm~DN>7Q@s7ioMR1l%QF9(XnLDR`Cp9x}){ z@sjvi{B-@B{rPFB$4?o~%y9WB^QS%esSz7@17!!|r~JnL{Pc6;Lc}|x@FR-;h2!+3 zuKX0)HIf|on?f|u0=c-ghl!OL@uEwwM&iY^BhwBKRRPwPL< zFD}x)#`#ryY5(5B_Onj=kI??@7e{xS;Cpu&bL9^L9kGML;n+blPjSYTU8WE?ahubbEhr7>_hJln}w`(PetSGsui`uQ?)r$C-@PF(+f zhKuzR`X0TPel`^J^XIQP{hUodo8h0<&OoAtK4ngG`V<1@7RGLtxv2UPp#D6tOGZ2k z+>N{&?TIDrCgj%r|31C<49*?I4=o9eclO4&5v$4`gBJG24`r^>!ru4*zVJBvVWaGE zyo9wQ)!)MXL_4Tk<6Lr~miGNR?qXNJrD*#Ma3-GbY<6T@)`dAh8J(jMJEBjvg^}l0 zcT%_7DXiD8JMW_IPr7xBzNVnwLR>Um^U-D59O^EoZpq3@aOcTNgFWDvm6>p#tUT+# zsJ~e>DIzOd&_{}2@?_WmrL*?CJ?D)1iXaDd0>)8KGS{(a2&blD$3wHl+r}qD{SErL0v(}T@|BJ5d zWWRr0``V$i+L{9FbLVn~3BE4Dw%CL1X!Z$S?#OcS-*x{)R`UHWzOMn7vN1gSVjXs# zw|95#3C`YK?M>BQkXrVqc2dqeci?2c*8|f|zQvHMvK`)HjBVfOj6K;3t}VgX<6f9} zpDj`Q;=nTgn_c~7{Dhm$)Bth4ew*Q4>3ihKjPg*DWnIE^eZf(D&! z_d;{m4r4xXKRhGah#zwBMU9#8P9hE=kO+^e4{|TL*&sdSzL5$PoPU<1Ktq;*57^XpZM!KRQm^kd6{Gxq$FXuhl zt~1?&-BIQffqAC74w^>Y`qh><{&hBy#;yHD&GWmV-+10{a&eKxUn*Q!JW$Wk)Dv^- ziBZpc%#Cb&cdETmcMO>nB;TPQvAuf=`f0G=lSTUZ67AT2HU|^`>Sn(b?}BSF>pE68 zA(L9qbmkJ$Pm+_$U;ZxeT3Jz^zSGN#tT0dXx8+LQ@$8!$IJz`GsMlus^b331a;?}b zpQr3Vw%oTn`;YTWmpHmK$=LSeS$1e0d?lVgV~E);+_E>jY|gX!mD(4gb3V5H3x5mv z((@Lc6~m7?OAN6o=cm-qatrOrcdmq-6>g|h6kMP)qtg~}0>cF@7 zLgtt_XJAxUN3dD^^Q>J9^PH2#J&MRZ?M?4IyLR)#3-BJ>BxSKap~^k{|orfqx`#+&k6sGP4VBm z$f36^Z}z3VOCAh9H*SLMtuw2tCEF!S8$z5Vz*1P{*xiP`hNXzBy$EQKr1#xk<7=8i1vhvKMns)B~oxU3J8YcB>wPK_y*WEmYrdBR@CjYcivNPf63$TZ$oH8ci852`s(Fh;^Jp< z$n7KLGW2mHI`<7=K7f91B<&60xtxBwIB1X2*RRsoPW)cpp87Im(^a7o)#qd)u~ti{nv;Q1-DSLnH95>phm#^GVL@$G%>nS^sUVbp!Uzgn9W8gOm`Dm-{Z9f{kZf}rysY|5AU8ue{Axh!Z-SLBC^=;vw3D9_GE9s#{~? z4fk2+PAN@iQm7w#R$snCU!EiFukLrnIO&_}Ry>!UIR_&mebP+cwQiapo+PpF_W;k+ zeCvcqa@bHR*C0Q&jUNl9zsvVOx@CXn{{Nc)U%1bTk5rkT@Gm^P2|o1x5brD9Z#sWM zX`r_y2qRqs#5LAMyVK_c`VM&v*Zm?!WB0t;@OV)}_tr zshoS{ep|%9)>j4V(=Mz7(RPGeK1F#yZL?eRGtK}nIs-xQO17QC`xV|hvLVX5;MqOr z3A>J@wWG1vqBY0z2j>S@MN_YHmK<|t=BenFV<~@pv}b?DC+AIV zY9;=wQhbjs)<`|N$ETx>br$aN(b3TW!Z(dc!Lem$ ze&u%Y2C(al&m=YsK1xfAn_DEeN2LW%xTJQw)`O67GajX$h7f18Lz^Ac=bhh~COtRz zwA8BL1WPZ`P3PH!(Z!-$4tTP_6Fy*cdzQZIybRT&_-553IpWFCkmR3tX90Zn8{}n@ z`JIVXBr>$K1-!mGMd#g)>aGRm6Zzh#zCo`R_S43QPi37jeJgo2p0?QjmZo*y-#y42 zowFd?j;5UO_8aPwo;8ExFL3AFeX@7Nv#qL!wq{dL)&FDe-Q%OGuKxcsGvOl0MUs#J zYHn7G_lgLkMJ5S~m(~kXFU3bDiLF-KdO?ednhCKLMJq?5Sg~b7yv?YOw17&r4}oZF z)wTlOwK9`nMbWARNJfbBdw=#hCpigdYoG7$_5J=aui0l`*Is+=wbovH?Y+s%c}rVw zSpS?lI@~&P+&a{M(Y60Rn~}_`7WQDhGa6CmR0>;>#Wf_gLN)AhN$ z-MWqU+}MXU@3)Euim(kAvwvSo8GJD_MeJW)j83NbvHX|pjjg_zH+t_FB&^DKCilCiOV1I@bCMq5qoT!eh8mcw0-s5qRy%c&Mt6<{X+1rx~hjxNmLIF zV4n$WsSO6gwFhErVvo6GRoz*+7c`W{b|9M89Jq}9I_DH6Kd>+d^e#<7@?*wX^=>Ea4MP*T$yYyj z=79Qt4t-Abr_Xy?nTgo{6wamJ;L_ZCn$MlJ(8Xsf;cCr9h^C_<`=d2 z5fx30Qcn1RSNXWp1p{ z9D7taB`(Ro`djVh(b}>Uz9E@ito1JIfJQg3?mQ8`g+I~I--+|Pl3ZEthlaIgt$ zPB$7|n>QSac~AN;z3-!sjEYUW>=NcNb5?cg?s(O!w;WV=ggIPKyVzKk1(=VbeeXSo zyNI{2+svk&Dri@2=^n2*JbW|1$;>4S-}A}X^x$37v4=VL>$`E{!`i!P=3DPCr3X3p z>x)+{6~AKtwW65c@YucT6Y-y#w-So?VlSS1U7_F33w!q`%3BGeW_)#jPVd_vtIRHm zgzPHfJ=|6F#a;72dvYJ##lc-MxYHfUf>-Y?NJlea;NC!3d{HRnn%Vg(^CJ#T ziBHGj-{R>iZ|?VpC)fFSatZ0hz+`xGuI`L7N|?(`Bm8jklbdS-dSlAoen%+Lx-chE4xfj`&`-vh7KysI;FoOimp$Lg+^%sEiA#*_}y%sVYV`;jvzW*PqF$c5X-nR&f{ zdHu3~UN0eDebm~tls^6mn4W+(S80CGmhzrvXf;3i40%Q4s#j@~;hUD>SApc!q={EK z^!7-%-xapqm$`R!H(x8wh0d0M;XiLNCiVDm1ZY3~kfTS7A7!D*Y!kj#=kULr|C#K& z$lr={a~~lKYmwur^Q`GV(Y`Z$M?No6(VG0k{*5>P}mn#yt$06LazBvh-#l`c0}HdVAEF3z`QVtXs@I&Z`-3^mlJQq|i^ar)~6s z24_z=;(He?^5DP!E?AH^3-JZ|Z4CHRw`%{4vE%+{3;aba>(-i|_qWnx zufIOK=sak)7TQdea9D-dy=%@h<1zU0Vn~ z8`?W<#0^|>x<}m-TM;-=AHuiX9VAcZrP{k_QQf6JX^@O-pMYSGq=L= z!)gtL7vFF8RF`${sV+pWL-(S?iAV8Y4~-bQcKonjSKO?g^*Q~YzFu)P&;a^~^e4sr zMRD*F!Yf`NZ2Yhu>g|VBa-a0QF}vSyf)3F&=T);#s)Yx{P5@u3(+)rxKtfle#%d9xyO??unORMPEeUC--Ti4J-zuKqr)2)T>>g~gek4NF-_{ffrGRM;JTk&!W zejA0~rs%Kw9h974zQ(zWT>L=z&_1Id565+8kN!3Mq84AX7(OVq=nv)G*&>-y4y?Q} z5UyFu8I@C*Ys@*#H^G{|W6!XnLmjNO;SaEhRjRx@E~SJuZ(n6xs!Q3&8ZaPbC9aGV?|0DcVB#)dBZz@+%Z{-l)MA@%kM4OMdaga>+x+EjG#8?Twn0kykxFsa1Rjk!9ul ziX*+>pjk_ux~o!SBwE+lXpH>hQ(pho(c(|{FgDhyy~jp#Ml?Uz!@c}k z;Qd9~L;fCo%j~0az7n5Lga4HD=z?UHz2unuVAwxyCPmQ!y4W81-57D$`g-VW`-TXT62-UfoVJHU_ljAVrP+y`&<=5x*(48PrG`A0kY?@@o= zozGRV7g+K3m-D$R-t5iij(gbQGe2iNB^|2`J;DD*+z6NddT_OT8C=)`Jh=AW16<|6 zweMHJb!s2D<}^8Q%>=Fz^a=T_ufTsL1K*b~Qlkrp^nZ7KuuL#IzLgGsK79Vm@o+?+ z_7;EFtOFMK*8%rPhi4sd%n|(eSqHqTINv%T9?wpHnYa++=)rrIbSoEjod@^AANaz* zAuK(v1)W5CL}Md*1p4b%*H>uE$F4qs-cfRNkKXZj{JNDtb)Tbmq`iFT9nH^V%{s~( zv$Ok*8EYwU1udCH@M%3#OK z$VSzGKH$t3`KA>n_XAGRhQ>?uF^X^XCtKT(FJN8?Sjjr_&VriJb2YT*Ff$XD&EBaN@x0?$`% zF7LMo%=_&= zjr$e!*EjC_&}`p0Y--Rc@1DYU4uDoe{6w>_9S)r^=2~anPOGYvGzf7*2^m=c;IHty#zvsgjW%rh?<#z0Y z)xhI@-@2sXXn14?w&6nlW5`DN4h>-r=kcA( zZv?-6yfplXDhMn8-tPY({$=xQm;Jhw`})9@*8N-2cO=^fQJ2y@yXbACYyZVx=4RsL z!z1}s>hgcr$HeF)Dvw@or?F373Ex+~;fL`LpYhu#A6J}DFNt7_Oe(IX$39ui-B1q@ z_j(@~eh3WfiBntJm)HMh{x`Z|Z(qpUE0V8*_G*={&Hev}`|p)$b-z9TBHaVIoj#Pi zeb{)Y<16IhLTjxlz_H9N=kH4tADY>~Egv5B{|)L;+h2yKZ@O*#kv9BzisF~?1M2y} zZNu|9l5fuaed6TzCHO%s zd)D=X;Leq#&cn{duHV|>?9XZ6M0HH$9sup#R_<-B$j)ca8{JCqXb-4@`5a{*&dRY) zPlX2BRsEYMZL7^qTpO$m*I*OO*5)O)x1ftX!oJFkz$*4uGTBS{ujltC!VCFt>L1Rw zVV|6d)iCy}Z>OimCbgM;I_>opbhnY3+B_*WD>kVF-88;7XlE)1gwr(x!VB4}O)m%T zr{V9Hmam%3-dLP{nxb9op`nundFQ8S!^Lg(chYAOYNvn)oy?E zM!4qh#hL7)bNgH4Q%xs^n}=BAW7<1pPb0>8wRFwYu;5tqGxXOsr=G@O&{iGVgBM;> ztDSv-=S7E)IN|3MUgCt;5;p!I9liZQ*i+o?J+Q7h8_m8z(5jSu&)!x$Ad#&dn8>mp zqBk5{FA9d={e<_DhXQX6=i&1CXAdp4E4xzrlh-VjU9&Lx>W8fNWT#{wCc@orpV!OZ zR=%U7lc&6%U38&210S-hoxK-FM>J>P2P9gtXDoaU9%l4y-shm4bon%6(nMM3Ogwgd z;FC@i=luJISLtu%)G)S$X=%o~c~sAOD$#+ z;9(r%WRGQ(iMvX1XGP38C!GhgxJ&FZ;_jxOrSj2m>CDUnU=hvGXInO#0nkeuJX_y$ z8PED4Z-laMA-k9M9P6;l$QML5(_+Tf8|VGpajwDFyYD#vf_76i)+*?0T6`|;9ARZz z$FaYKu6hw^?QPuYZs;O6DO=J%Y8)6uWB&?&fXcmIW;|FSgkR< zsKOaTXe~OdXAI*N^mj3Qf&UkfcY^U^jUoK5$SN@7HBjTlm};E8aSinu*C^-bzo6Wi z!WV6Kk6BG1e6R4uUixlhMwVp-U-DaYqB!w{JAvWLctX51V1I}XBs$s5zsCz! zQ@8jLc7)ydkz}mqhV&J+u@`%*X`ItM|6*?*U1IQGkQ}&&zCE;uzAaUpZyssB{aSI1 z;e2$M=S;i#$z9Yd*wl8Yb9eaU6YPh_qlpt;df0TZA2XW>;Nm%G$A(W{^Mek9+(`P z^k8Ld(nHADriCLC4=m12JoFIomv)n zrLDf>hjv9d_+0~j^`2}UIM&{{aICk$X3Mz-!IbH8`kJ09KO_7Y(38N+ z`T+gtre)nZim#!3gFS!kI56{Gt7%_jxd^y4p7+w`%uvB25%xMPeC;~$)z0GgDIdKo z{*wXVEoZMo@q~$S;9onmJh73ujYF-B1-|F<_IsRktv{mu<@4Pl&#=zoy>4)Gz{z4Y}Gpq3rU(H;5kGLJgZG&FK zSFD(2KLPx4(gsjo{Jep&XaHxkXsalcXM8m4=r?{aZ)}zf!uJCjlAa?z z1OBIb^UUJ=wws|li~E&EmtR*5Ef@24wdP6{W1E7PJXl5B@XaW=Q@!aUdedYhbHB&$ z?vIAIYMi5#*EnAeo{Fx=|6g=9(8|bPu?#$l{|jHj-EZl)#$qh}+t!8+6`E*u>7&lr z!Sm3I3hkfZMlHyZMCnpU6lM<+r2=VaC{GVe+w9uzKehHrHY*3eBtb0@uly5dcy{TYnLa9Pt6YM z+(o~H`l_{yc$0XX_|swFK8rp2%lOk&;a>U?d253OTVjll;Z^V~@vFXk>Mzis$ETih z`IKnCFP|!8Too@qCBJe%pR(W;9-msnc*OhgsS_EitZ4fy_*CPNug9nE>BXlC9Xx54gu_t>Hetkvk$*$q9RgX{IBYNU2dHnwXpSpteg7}o^*5glW>HE?zDi`X^j`)<$ z?dUvn1!r=^ukg3pBAz9`W$~+XIorI{=q!av@e}c}H-d%p#lM~+pTG(a1F6%`({6;Gd-F8;aE(sx`lcg?JK(5)V;McN1i5JRftHY6 z3SVno9WcJQ@V05r*(=^Lqg~BC;~&r5+ss-x+Y$d_e~3FzbcW#o_z?P5GxGKoXj?X$TFy3Y zYa3+rstRD*QM*q9`!wsd9A}LxouvtzO+7FyqK_8^G8g#aAbz;pztYq(cGKGOaDe_z zVPA|pF%u_%zkcxKis6xKgGXA|7F`U_&yGmc{Kh&ueoUl`J7nw>{nt6RR6TbQa=+E4 zf>RQ0*pEbOv1-e{uwh(6u<5=5^u193u(6NT1~SuU~uY->MoUvv0hw&u{cw!eJ5^#sPC zxZbiW>67|4Gpha+CbBiuxq5)G8jefE@Fz5PeGUj@UIDgLVdrxcw5Y$~4{02b@$9hIT(Ih3u0e_du}en`Dh z;7#!@9#%-b&$OP_O%qzbE(sNyd)VTSAw!{Y$n`qfGb7QY?_8|K6 zcpx*;gFCdBf%`*vJ#&6RY2;XY6EKS}6`gN|-=ppbbku=etm4SXQpq`jI*qj%jce# zNFgIwb8LxBg}!7LDIRa@tftNiiXJ4R;^0AYY9)14iAE%YiNDCDg}yRaJ<656@CMQN zYAf^Kz$Q8{u!Z_NvI&`ct{*Pq2YEQ}BXeDxcgx%e@QKEH%iJCrRpGCtay@eE_^$3>bP#XyS;XF;u`w3ojGCp)zaUgCCMn{ zZiycT(l!8t(dEFg`DuC4CYPFnvOX8QF%+{rEEf{1^~k zLi^&C2Ox9(I;uNXz2-+mW7SJX-JkhE-Jw9{b$`9Y-Qw>T_wt#3;k)3CRs4pYem&1Q zAk(iydiv#z)o62%r)0R9AGDK2KJKmYf+w23A^*(Wmuy3Juk^z~+C{+8#vJ_x^7}w! zx5b$K5ZT?2@jDRN{X@p4mT|hDaf~7ZwC5*T+(vzBv*vfPRg#&Tng)j3#?S_`=%df! zG#+k|IYtgO1u}R0+a~@b+BP~G^?0;mfx}j0i)f_=IbDyOu7vl@L_SO2WNWBzTp&~L zuZy_xetcHD_`IM8pYY0V+MmaHHG|KRzW6M3@QG|S_@o`luBYgaV^_nT3apZ;Z-V2y zkX^C^tgN=yX>BGM_FurYnsuUIer<>Mo( zuq&&kDvWH}2|cS!rM{6(DicxIl}D2lcIDBr3j5^IG5U7p(Kv-AM}hlDg?+N=P?P4! zrm=*ht(D>U0h}>94f#}Pt&EPC5=I^zT^+1j)dXE=e#qus5FC+Bs@UICU_*cTnA`M(RyFyqEK)ZvCla?UJuq zZ|0}eTRkNl<2}OYt-X1AcfG~O*@M4ky=|&@>6CEs0n~fvm(&|O&fd7i?8@X%m007;8NOD_uQ4v)2BrepdYF?z_|;nnI6OX%JuH1G`$yQsynDIQ=-y|O-h^(plQg~4 z>Xm!xUz9t^SMD=>$-Hv^^DoLB>nm4i+H>xxT;?wat@MTSP_GMiES2V*oYAn9^ zN$;^(_6^@yT+IPaSg;h*C*}{;*!MS!@(Zm$Yj%x zB^xTkWwfz#OLoN|(L8(DBl%CWJ`x>iyrnA`I;z=ye4h2-C+#%RpIc7j{Z-|j@Re(# zobDY+y{o-A_NN%b#$Y7eNxPHfQ*@m*{ds=6-y(WF|M>mK`2VKjIDf9N;^NoxPd&m|{ZfE$6)@P|E*2387z6IWg;wLM6jChdbcNV+LA;jNHJa=Y0 zWw42)%0k$xv|E~Z~1EJ>_B1i z0@7QU-yNilr5@y}Dfh}A%gJ9`rHZmNT^tH`m7Yq*00+uC18SPfcVICw&jdL91R2@Q|ySz8! zb%pAG#$SJIqWwJeHyu^CDh>=fBT)e^+F56$$_9ij+H6D5KbiU?9^CZ<96cYMt{r+e zuoI`cAMn?mnrQ!)y5rR+gd1CPfPMdjwVyS<*^?WUq44VO`|L8Ft%`Wrs;m~q6uXXe z@v-WkPZu}+lP+Ev$gKBZKo1uTH~C?(!uA5;Z*D(3D*UModiD*%A@x~hj?OQAdpK>x zJlIYrU)!K?wkDAIy;ncqs{hCS`isN%HPpW;G}vyM>)1WtB_5k(=WD}6R!PD*jm9BIS^~GO^bhT2S>Qetpn!|Q0xKUk~evP`OP}f||V|bHv ztImC_hVHS5Pq1Zg@ZbtC7VY3^&$@T6(Y=opY{BwyBe;q7!ygjZJpHI2blzQeYJy$v ztNUN|c{KH&?c+C3m3}p!I+}EkPmP1$be^px3UIBVB!8Mc?IP zE&2Qe;W3gsE`PhvZ7=2W#kApzJ@pm(>bsBnkOMDU_^;my-#ZVv<9+{kHTV!q@x4*ZRUg^Mz*-cJ}8Bk~zR9ncU3y{pnJ|Z4RA3tFTMwYZMOk*f9T~ zZ)jci8rfY|=^I*)Q^yk~?AS1uE9}z!GKF2be@tQ4jN7quseD4;tQl4QL4{c-%C4xi zrTWH(sk9{~++)LBtZ&zbd5^*^jt%oJ!m{TLp>L8CUjObO{vdP>#mV19|A$KVpq~nh zhyTLVIW@eEzQt&NuBjLMEbZ4S?Dpq+h28#Kt1$i9Cc7PFuGTmGQJE_hhG(S+U#2j9 z)BSwPcd1EZ-ALNSgf$1WzW;&3%#Xj9kRO}0V6%X2rt-t1w!x!Reulo`Q7V6~!tU68 zPhofLrV)M#dQ}~st^2!5cWvFL5Y}C#4cHl!XFI%?J!@E7pC6hVCn! zyf~V zeex9&C*NNK2R!RE;SOBuenR;f4JTcGLH>FM6Q}o_6{kD2Oqz?s9MW`l%*45S1lddA zVZ;gNikI((Y{NWEv8Yk;K0ItA?zTR0pAdI*pSTZ+D{KqcE5UmqdlJ%HL<5_M7v7DH z$CXzbiSy*uJA~z9Bse_Z*B#XD^udv786>@PihG z%X;R*YJImjbKx0<-MO$zVRtS(L0EdZfkS;2o}gvDxA#FY?@IQG+eDmT@^JYe<==A4 zi{4a^{1^PV{WW>Un|e+*chYHY+#?w1t89{S=0=0QsW%Qy+9vGu>6e6ckFoflHy>_M zx;r0!p|DG5^@KGy1jGNh{~n&MCv6pR_XR`7*C-!bruLM)eWt5O*ZAS{@6MYk4$X)j zp_w0(o})grRGL0Sf%78bH19To69b>i+p667H;ET4ddExuZDwtCqN%qS9GpYGcNGWk zQM&dp)Yh5Ax4LCsRyt#7(&2GRKbiCw+;neDrx5p|8+W+i^wIVt(q2_PYQr7dD}hb; z5Zpf|P5<}tt#-V+uX4ZpiRb#t)`$jxNxqnZ=Nr`jBe$-3YNsBasr)YP_9LD3kZFUv z7fpXny4&A;(l2wt#8*eG-m3mkUzpT3xC+qB_!o>ylw+W}+ zb*}1}jBj7m`053c8>vHl@G<1~K9pCweDzG(NP&HrS5LISKG+M#3+#iuaIC;SzzbUi zw%!rVh6>g}bIW$~Gin>)_!(^s8vBEE(l7Hf+CG!_Kk*-E*Sf2XbzUftxsE4@Ks_RVNcv9drNH9r})%pe`6EtuvEGt@U~MnuWdynEh3~-`n2g z_#B8Iv|}U4vgeW2p2s=C%uIh<#P8#8D{_RrALXO)KJ{UnyHApJ_eRd3Y&r31zSA?A zXZUW8e<_l%s6((ugZVE8^q$AS*cNM7#lj`Pg>Px5CLEYP65qGjgun{Ti*3-2>;ONr zM$WHC=e?UetMM01;oIh{6+%h%vvf-ADed#*SVa?qv7%1yvo-q_(Gj{wBA>gxDmGX- z_{$8p=F6T?k4;iGSgi-Qu{UEyS>Ligzfw4 zu_Np^+I={2_gb*A2+ULC_@;3(~ zU88ybAz%e~vn_v%{AIy!S9AYJ*BI8wVXxY8)j?&{kWmnLC$g7H|@>>2eE+t7v*mn?DT&I{cjm;XPdZneh%et4rZ?Qw@1AA zeRuz@q4qdazEA%zrT;2l8_3jo{cjm$r)PNmzmoprXRQ7=(f`HtKSlrN2QmrO*Rr<* z&so6Jwm0^^y~Eih)VDC0Inxgh@gYAv(V_M*m2b_izu+MN|ktVSIt~hD;W})booi`)$(G^~BSsHx#ZxpTUOls=}eu z!|mWk@?f35n@%&fsJ|*)Mm|@TzhJ(<8*XP@mG_VOZuySM+oG`6P&JNTwp!nz?}Xc- zN#%W7-%Y2Qyib`hdD(N24!1(ztXtZdKgzpI-`LusrI!7e!u6+w+nL)+OX$1INqbP? z8tTEWuX>j1yM;9{{789!W71DH^(%+Wd2%;#Rcoz!-Zl_jMd8bttE`4pLD2a6MB&GxHKk>tp{+_2{k07br<$hV#}LFO zAMDb-!)f%P^q7iF$vkUCF*Y{cVQ=B{srtvY=vMNT<^u@K<1D`|F&8qQC6>@g{31l1c2(dRgZST)LDm{Ndofi2XQ*1)PO?evacaTh71E7*thQ;n~zLn2PWLZth=>KYlQ=oc?%cqRcy4z^?x4 zY*F@y4(#Q~M~zkNO6-{6tz7tpCvTVzeF(4cX1$%~?LWsD2gx$Yrwid#+K2YWDkz(^ zI~U)l{#NLwxE`NB=6gjHTQ)S+&i+}7G;gl|ov>_0+N)lpJ4bKk9gXETt-l7n^E!U{ z*0A}7p>cMKx%d70L+t|E9m!mh{w5ea+4v&$MEOo36UF;(V7z35mp*vwXO6vg7WK`l zIMl$-Iif4JAtP1qtP%I#D}Qv^RT}L|{fzoj19;z4bBet3 z6W>YPwi)ie401A7N?nqH$ejDVx`w0oC8$#yY9LU_| zmE~Jyr&2Z$aqeheI#TUfsCW1c@Gfzw6`a4<-pG025Ns*}ly|R3(?EZcjp}}q#nNNZ-H5eG{ zPj>J}`K)AHvt=h~L;T;s_cZaYY@6=Y@5;6Qe%PAF*&`??nHCd0lm(GTqa43lcv4gU z^047Y;G!+GkKswuweAoukW;3uKe@Q584=E&70gWZ+T&aO`EWnqcoiRQ@A#DV$Yp5TCUGe6wx2Z=VR69MYUaPU>!gLBQ?F+W~|nZ@c%2TBe0p z;?F0!F8PxE0rR6~nvo&96n15DMqyVD?@$=oYu+Jd9&gpRWG`d%iAn2`pIc0tBR@AQ z?30(9^o_jKoJ|uhc-A_ab;7a$`m^+hPS!4(r`zHGQRa<*UcX9y|Gd8084E}MwkFyO z84K}@f3h#9HJj+3`C#@qwLd2x{tdjf($W=3h)z`ZcIJnt4!Ty%q~?s=pylI_!{#{^XNI+NmHNUv9zao6lD%HsvYK$bOp_&>@ai;#(fQA z>#W)Hl6POI`q;xDkLJcK%2fh`H#Z`L{d1$0@!b2ln>b@NEzE-l>@mVOP1}3V-!^|( zLi1%DWxV-vq{8leIZR=9z8tJ@S&#mCpuS!GvrJ*1zFDMiSKr)MVc&cy)VHf&=9@4& zB>1t4EqfnRj&H_A-$VG8{4T~I z{xu>$#UZ)W2qJa9ykhn#FH|_d)R0 z*f*&)t=h=$w9MSwPQGoIx$w6QaP)!@a}e9YZ~SeLc80$Xv5Lr49;o$%eH?{h95J$6ii<#?cP8f=m4EV87be-%fFk{Y|w)UbQ1UN&Yp@ zb=ooOfBa0^uwSJn(^h35^Pt+A_Kz_>eIS)%Z~doeA>fRY z%3Ku4Y}|`+qQ35Nn#DM=FL?#+NdB01kTYhS=1_+9`)GeVq`l;Cr?{W}2JN)WZFp)6 zvPX2CLKd0vVm+>QW+8)WkU!_rhUzzZhAV$ef1V6x0{%8gTkdazdoow6jkV6a`4HPn z3OVDAmD)JV$hnYRNjt}T?TA0SdZcNmC75}?#y|ImhJWR6r@5c~0PS?^yl>NgWU!}C zzCqa%;Fk?CjZXPW@Pf`;D6g~mOE{an6J0^)^R2_+RnSl_a22tR98R8D+@Gp79DA74 zQ=Es6@Lr+Tk3Z%t$E^Oj^Jo2&cb$<7SCcoFxBot;ooI-+y9R4*!oEx`?+&vrT+M!0 zBkyAb-1XJfteLcS(i>-LGgcbux=!zZ%`Musbu_dim}obyySOVX`z~~It>NxLFE0w^ zn{%A0U+|9ENKZ!chk2__muwOcUbcM;MN1^|qH*Y6R`zI~btuybE zf0BD2BPVCDCs6L;i?#9b^iAhSrORx@&bpEM!o=mr#79OYb?#2^HUrcbDKBVCUe%mnS1K=t+7iZE2~(8rO~Tuu@h(;Uvr^G|kwb!Q-d>5J?D+ZIPMcbyhVXpbbyod+G@aK~8{#*VUbt!O59OMI|3Jx+T? z)<|PVc?~;C+}Kgj{jsH-1D(gsJq^>sW-YSRvflwW8V~Fk-Q%&3@C&{?d%_0c58Ozf zdztVD_(^F!bsTsQ&V;Mx9S*&Hp}9nyM{mtvSknh)9eRsf4sA8_e=+f*w^YE1OLaN) zRvZjWpGUYZ)I)FB4h+4`DlM>Qjo^(p7x!8(HUhWsooyNs-XL0s&P(2rjUqqU#JBio z73E8cOYBqKZ_VG62+Ia)?s8Ur+!1ouWXdbthd%#OxM%v@NPLWOmi;7lo;CeyU=dBU zGA>s$ze5^F>e9NKGsO*pB>*3gPWusjHpWx6*{1$qL*8Put>|2|- z>;3HCf|+v|<0|nqt;dMpi!lXHc+)x2EY>8nQc7)?L?ZQ`l#N|5V?u4gO<=eQTZ%^zE*B{$a}X z*a%upImbq@QDNVj=WTtvYo0e0uJ5s_zoPFl$EJ?cYcCu6U-XSnllu6A!af`NAN7qN zjnY~a_Sw)^>)W-VKcg@{12O1nmBM9&Sra^=aLb7f4`R;zPTw_r!yg}4I7FEBitL7u z=(`F2R2j)UtT6JXd8uW;1}?>u>+WVuS$B<$4YhOMD3`4Iv@pv0iSg4tB>55MEn`|x z%w8w^teXi(g8_Ss#`7YLXJPWcS?h_Ww!>>ar<`a`bftY$(VMaVL0`HHKs2|2u<>h! z7Y48$Z98~ySE^-+AbAo(-;AzJx8JVvy+k$F&# zy;?R?%{$p%wFZ^%^rhtOp0C932R&EOM`PEB53|R9L|tw8I8up50J3Uu$4Rm+UOs zGnI`gbu;Ud`GL$2)E0ImgKzZp7_@BmLg52L{cVxf>~9PFz2vs_9(w#uw6_Ue-fJ@y zWv{J;y#RRPYJAq)Du;wmQCsk8(-!)?X-m5KxL~F|>Kk9u9&p-PV8)lTzQ1+b(mtnj z`9N&A*?0bRf9R1qi#`8)c>a-U2l_SbOva9?KDoA{H~sApH{ah*^LQI4HbW<#ohV@K zW7@&zqCqmM7=L=LQ?!O3sdku0;-O0wcdBefyuShe)SO~m7L*i!!+rxD!kZ_WBjc!} zocSWSG*>iFG+(^AvJ4nK9{L#J$?PBb?X2qCcKWRK%>$&%Zlt=F^J~;MyfjYQZxn`) zs;qQmV2-8P&#;MftjA;YamMVEAV$@0J}Q@gA;s%~sUey8ZS zCPvFbK*_czd;OJARs6z^k4eDoE7 zu4ZAc5uNp=tvG$Siu&5%#|893H09A*F5l=Tjt?z%O3#izgm}*`IY?pGE;&G9SAORx z?33S?zFqmsa^!pWEbw9BhwlgXZKfb#m^D`vK!2yNx-&W}(L??7Pvy$NQ(ycDFYBnU**nj= z5j&@)wTg4+WI1ONzw|sSeZp_f)_K+m!18G)cc()?&N>?Zdd?}n&Du=s%jSEWb;L)K zg}!xbq=9wouUI2+mM(QK|EycBrL0>O$2!8g--(Og!@thtHF1`XaNz##vvdnsxAuA~ zI$d@${uIRN93A_+=X{$s;T`@p>jF0}^*T1&NY5JS`Fyh`Shfayh_88T*0+c^IxV{Q zPw{O;H&{P)hx4y@%nJFQ$9IJB)%lH5`e|U`KD9Q={)74IeQ)I33%}zFzv&CVM%b%s zn6IvviI;pZ`UG?#U7?Ec^Jw$}gKk2BX=4cswnh&&=AHISr0;;5<{oAQ;w||`q`}(&{ z-){dtQ`ps&|EaL6D{oP_<;3pu9PjJ9=0pSkCWS+!O>p{_(l@%Y`u2Cix3X3gjc6>T z18ZzvC;lelo$+=2h1Q#TP7g~*5sv<9>N(BS^CDrL-#9&w^BKX>TguU8mqCltjlYkc zBmU|6;Frn&xKDdeQ(n4w18b`MSdQ(#qhbYV-MrW3uaB#3mv$c|?D_foY5hUs^=6ms z0ip?=8+aS|rn~*R9QdqI#N3e0pWf`3)azD^!MYPC?fy^E1l)yib|BKJ7fDGA<2VLjGDe|4#RR zPM`dXOmEzwsB|^a(s9IldfG&V-EkhTushC2DeRlShwIy&zlSL7o4*I?+nv7$ zDD0cR#rk&V?|y{Wx_#N;;^D16{Xfm%Y+CnySHmguhMU*J*(-g@l&g#jcMkd2yZOCw z`D>s29T&`!%L8 z-|MhJDu0#kw`Lw@ay8%bk{$eu51M&GzgN-bE$(>u;ayI=e+(bjw>yT95
!#EpR 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π=x^xX7%xI0{i7=X=}P_->3;?pAJ3uv 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

e}kh&;Iw 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

bPoT98t-HyNQ<>CnwjOpo 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 ftK{$_e2D*q< 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

  • $?pcM`c-+={ytg0fm36N-+NcT`#vK-Mn&bRjIn$SFp^0o{?ny? z7ha_c1vv4Sc-3DKWc20L@5ih3nEEi8ETq1#r_x6a<<;+XtJEdG)1~TE{Hy#EhVnD= zcV|?p>QVSc{^_%$#J`fi`rUe!{z3^_5-`&HjIsP4p<(KG?#H*uf-*7kf5cE;{Z5Ta z)%SN56(j%jc{4r#<6jf?t914jRZ^tJ`oCl--z9%Q#F5E0opOuYR{@{)#An zSyd!cRbHi3SYQ59TvT`|F3NYQfo)Mf+fcqm{vK6Jrl8|C)UWtfVEP$Bef|4o`Teqd zqe3*)ufU}!Ol?u}Q@@WkE6ZO}h%z91JduC11oVvj zAmws^S4dt7LzxA#>56xPtLw~rMftzLclq>}s#n428;EA*2ObyYD+~n;S@!<{wHFSN diff --git a/orin/config/camera.json b/orin/config/camera.json new file mode 100644 index 0000000..5818e3a --- /dev/null +++ b/orin/config/camera.json @@ -0,0 +1,6 @@ +{ + "camera": {"device":"/dev/video5","width":1920,"height":1536,"fps":30,"buffers":4,"timeout_ms":2000}, + "inference": {"engine_directory":"../models/engines","detector_confidence":0.30,"max_people":3,"queue_capacity":2}, + "door": {"mask":"../annotations/constant_door_mask.json","zone_config":"../annotations/door_zone_multifeature.json","warning_relative":0.30,"release_relative":0.45,"calibration_verified":false}, + "can": {"enabled":true,"side":"left","timeout_ms":1000} +} diff --git a/orin/pipeline/door_mask_risk.py b/orin/pipeline/door_mask_risk.py deleted file mode 100644 index 49eeec5..0000000 --- a/orin/pipeline/door_mask_risk.py +++ /dev/null @@ -1,488 +0,0 @@ -"""Per-frame door-mask loading and YOLO/RTMW-only 2-D risk scoring.""" - -from __future__ import annotations - -import json -import math -import re -import time -from collections import Counter -from dataclasses import dataclass -from pathlib import Path - -import cv2 -import numpy as np - - -RASTER_SUFFIXES = {".png", ".bmp", ".tif", ".tiff"} -DEFAULT_DOOR_LABELS = ("door", "车门") - - -@dataclass -class LoadedDoorMask: - status: str - annotation_index: int - path: Path | None - mask: np.ndarray | None - source_format: str | None - - def metadata(self) -> dict: - return { - "status": self.status, - "annotation_index": self.annotation_index, - "path": str(self.path.resolve()) if self.path is not None else None, - "format": self.source_format, - "area_px": int(np.count_nonzero(self.mask)) if self.mask is not None else 0, - } - - -def _frame_index_from_path(path: Path) -> int | None: - matches = re.findall(r"\d+", path.stem) - return int(matches[-1]) if matches else None - - -class DoorMaskSequence: - """Index a directory of X-AnyLabeling JSON polygons or raster masks.""" - - def __init__( - self, - directory: Path | None = None, - mask_format: str = "auto", - labels: tuple[str, ...] = DEFAULT_DOOR_LABELS, - frame_offset: int = 0, - require_every_frame: bool = False, - constant_mask: Path | None = None, - ): - if (directory is None) == (constant_mask is None): - raise ValueError("Provide exactly one of directory or constant_mask") - self.constant_path = ( - Path(constant_mask).expanduser().resolve() - if constant_mask is not None - else None - ) - self.directory = ( - self.constant_path.parent if self.constant_path is not None else Path(directory) - ) - if self.constant_path is not None and not self.constant_path.is_file(): - raise ValueError(f"Constant door mask does not exist: {self.constant_path}") - if self.constant_path is None and not self.directory.is_dir(): - raise ValueError(f"Door mask directory does not exist: {self.directory}") - self.labels = {label.casefold() for label in labels} - self.frame_offset = int(frame_offset) - self.require_every_frame = require_every_frame - - if self.constant_path is not None: - if mask_format == "auto": - selected_format = ( - "xanylabeling" - if self.constant_path.suffix.lower() == ".json" - else "raster" - ) - else: - selected_format = mask_format - self.source_format = selected_format - self.paths = {0: self.constant_path} - return - - json_paths = sorted(self.directory.rglob("*.json")) - raster_paths = sorted( - path - for path in self.directory.rglob("*") - if path.is_file() and path.suffix.lower() in RASTER_SUFFIXES - ) - if mask_format == "auto": - selected_format = "xanylabeling" if json_paths else "raster" - else: - selected_format = mask_format - candidates = json_paths if selected_format == "xanylabeling" else raster_paths - if not candidates: - expected = "JSON" if selected_format == "xanylabeling" else "PNG/BMP/TIFF" - raise ValueError(f"No {expected} door masks found in {self.directory}") - - self.source_format = selected_format - self.paths: dict[int, Path] = {} - for path in candidates: - index = _frame_index_from_path(path) - if index is None and selected_format == "xanylabeling": - data = json.loads(path.read_text(encoding="utf-8-sig")) - explicit = data.get("frame_index", data.get("frameIndex")) - index = int(explicit) if explicit is not None else None - if index is None: - continue - if index in self.paths: - raise ValueError( - f"Multiple door masks map to annotation frame {index}: " - f"{self.paths[index]} and {path}" - ) - self.paths[index] = path - if not self.paths: - raise ValueError( - "Door mask filenames need a frame number, for example 000000.json" - ) - - def load(self, video_frame_index: int, frame_shape: tuple[int, int]) -> LoadedDoorMask: - if self.constant_path is not None: - annotation_index = video_frame_index - path = self.constant_path - else: - annotation_index = video_frame_index + self.frame_offset - path = self.paths.get(annotation_index) - if path is None: - if self.require_every_frame: - raise RuntimeError( - f"No door mask for video frame {video_frame_index} " - f"(expected annotation index {annotation_index})" - ) - return LoadedDoorMask("missing", annotation_index, None, None, self.source_format) - - if self.source_format == "xanylabeling": - mask = self._load_xanylabeling(path, frame_shape) - else: - mask = self._load_raster(path, frame_shape) - status = "loaded" if np.any(mask) else "empty" - return LoadedDoorMask(status, annotation_index, path, mask, self.source_format) - - @staticmethod - def _load_raster(path: Path, frame_shape: tuple[int, int]) -> np.ndarray: - image = cv2.imread(str(path), cv2.IMREAD_UNCHANGED) - if image is None: - raise RuntimeError(f"Cannot read door mask: {path}") - if image.ndim == 3: - image = np.max(image[:, :, :3], axis=2) - height, width = frame_shape - if image.shape != (height, width): - image = cv2.resize(image, (width, height), interpolation=cv2.INTER_NEAREST) - return image > 0 - - def _load_xanylabeling( - self, path: Path, frame_shape: tuple[int, int] - ) -> np.ndarray: - data = json.loads(path.read_text(encoding="utf-8-sig")) - height, width = frame_shape - source_width = float(data.get("imageWidth") or width) - source_height = float(data.get("imageHeight") or height) - scale = np.asarray( - [width / max(source_width, 1.0), height / max(source_height, 1.0)], - dtype=np.float32, - ) - mask = np.zeros((height, width), dtype=np.uint8) - for shape in data.get("shapes", []): - if str(shape.get("label", "")).casefold() not in self.labels: - continue - points = np.asarray(shape.get("points", []), dtype=np.float32).reshape(-1, 2) - if len(points) < 2: - continue - points = np.rint(points * scale).astype(np.int32) - shape_type = str(shape.get("shape_type", "polygon")).lower() - if shape_type == "rectangle" and len(points) >= 2: - x1, y1 = points[0] - x2, y2 = points[1] - cv2.rectangle(mask, (x1, y1), (x2, y2), 1, -1) - elif shape_type == "circle" and len(points) >= 2: - radius = int(round(float(np.linalg.norm(points[1] - points[0])))) - cv2.circle(mask, tuple(points[0]), radius, 1, -1) - elif len(points) >= 3: - cv2.fillPoly(mask, [points], 1) - return mask > 0 - - -def _fit_mask(mask: np.ndarray, frame_shape: tuple[int, int]) -> np.ndarray: - height, width = frame_shape - result = np.asarray(mask) > 0 - if result.shape != (height, width): - result = cv2.resize( - result.astype(np.uint8), (width, height), interpolation=cv2.INTER_NEAREST - ) > 0 - return result - - -def _box_mask(box: list[float] | None, frame_shape: tuple[int, int]) -> np.ndarray | None: - if box is None or len(box) < 4: - return None - height, width = frame_shape - x1, y1, x2, y2 = [int(round(float(value))) for value in box[:4]] - x1, x2 = sorted((max(0, min(width - 1, x1)), max(0, min(width - 1, x2)))) - y1, y2 = sorted((max(0, min(height - 1, y1)), max(0, min(height - 1, y2)))) - if x2 <= x1 or y2 <= y1: - return None - mask = np.zeros((height, width), dtype=bool) - mask[y1 : y2 + 1, x1 : x2 + 1] = True - return mask - - -def _hand_mask(points: list, frame_shape: tuple[int, int]) -> np.ndarray | None: - hand = np.asarray(points, dtype=np.float32).reshape(-1, 2) - if len(hand) < 3 or not np.isfinite(hand).all(): - return None - hull = cv2.convexHull(np.rint(hand).astype(np.int32)) - mask = np.zeros(frame_shape, dtype=np.uint8) - cv2.fillConvexPoly(mask, hull, 1) - return mask > 0 if np.any(mask) else None - - -def _decision(distance_relative: float, overlap_px: int, warning: float, release: float) -> tuple[str, str]: - if overlap_px > 0: - return "danger", "DANGER_INTERSECTION" - if distance_relative <= warning: - return "warning", "WARNING_NEAR" - if distance_relative <= release: - return "watch", "WATCH" - return "clear", "CLEAR" - - -class DoorRiskEvaluator: - """Score RTMW hand regions, with one YOLO body-box fallback per person.""" - - def __init__( - self, - sequence: DoorMaskSequence, - warning_relative: float = 0.30, - release_relative: float = 0.45, - ): - if warning_relative < 0 or release_relative <= warning_relative: - raise ValueError("Risk thresholds require 0 <= warning < release") - self.sequence = sequence - self.warning_relative = float(warning_relative) - self.release_relative = float(release_relative) - self.mask_status_counts: Counter = Counter() - self.level_counts: Counter = Counter() - self.source_counts: Counter = Counter() - self.maximum_score = 0.0 - self.timing_seconds: Counter = Counter() - self.timing_counts: Counter = Counter() - self._constant_cache_shape: tuple[int, int] | None = None - self._constant_loaded: LoadedDoorMask | None = None - self._constant_distance_map: np.ndarray | None = None - self.distance_map_cache_builds = 0 - - def _record_timing(self, name: str, started: float) -> None: - self.timing_seconds[name] += time.perf_counter() - started - self.timing_counts[name] += 1 - - def _measure_region( - self, - region: np.ndarray, - door_mask: np.ndarray, - distance_map: np.ndarray, - source: str, - track_id: int, - side: str, - state: str | None, - ) -> dict: - area_px = int(np.count_nonzero(region)) - overlap_px = int(np.count_nonzero(region & door_mask)) - distances = distance_map[region] - distance_px = float(np.min(distances)) if len(distances) else math.inf - scale_px = max(math.sqrt(area_px), 1.0) - distance_relative = distance_px / scale_px - score = float(np.clip(1.0 - distance_relative / self.release_relative, 0.0, 1.0)) - if overlap_px: - score = 1.0 - level, decision = _decision( - distance_relative, - overlap_px, - self.warning_relative, - self.release_relative, - ) - result = { - "status": "calculated", - "risk_score": round(score, 5), - "risk_level": level, - "decision": decision, - "source": source, - "track_id": int(track_id), - "side": side, - "state": state, - "region_area_px": area_px, - "door_overlap_px": overlap_px, - "region_overlap_ratio": round(overlap_px / max(area_px, 1), 6), - "min_distance_px": round(distance_px, 3), - "region_scale_px": round(scale_px, 3), - "distance_relative": round(distance_relative, 6), - } - self.level_counts[level] += 1 - self.source_counts[source] += 1 - self.maximum_score = max(self.maximum_score, score) - return result - - @staticmethod - def _unavailable(source: str, track_id: int, side: str, status: str) -> dict: - return { - "status": status, - "risk_score": None, - "risk_level": "unavailable", - "decision": "EVIDENCE_UNAVAILABLE", - "source": source, - "track_id": int(track_id), - "side": side, - } - - def evaluate( - self, - frame_index: int, - record: dict, - frame_shape: tuple[int, int], - ) -> tuple[dict, np.ndarray | None]: - total_started = time.perf_counter() - stage_started = time.perf_counter() - use_constant_cache = self.sequence.constant_path is not None - if use_constant_cache and self._constant_cache_shape == frame_shape: - cached = self._constant_loaded - assert cached is not None - loaded = LoadedDoorMask( - cached.status, - frame_index, - cached.path, - cached.mask, - cached.source_format, - ) - else: - loaded = self.sequence.load(frame_index, frame_shape) - if use_constant_cache: - self._constant_cache_shape = frame_shape - self._constant_loaded = loaded - self._constant_distance_map = None - self._record_timing("door_mask_load", stage_started) - self.mask_status_counts[loaded.status] += 1 - mask_ready = loaded.status == "loaded" and loaded.mask is not None - door_mask = loaded.mask if mask_ready else None - distance_map = None - if door_mask is not None and use_constant_cache and self._constant_distance_map is not None: - distance_map = self._constant_distance_map - elif door_mask is not None: - stage_started = time.perf_counter() - distance_map = cv2.distanceTransform( - (~door_mask).astype(np.uint8), cv2.DIST_L2, cv2.DIST_MASK_PRECISE - ) - self._record_timing("distance_transform", stage_started) - if use_constant_cache: - self._constant_distance_map = distance_map - self.distance_map_cache_builds += 1 - - stage_started = time.perf_counter() - regions: list[tuple[dict, np.ndarray, str, int, str, str | None]] = [] - track_ids = [int(value) for value in record.get("track_ids", [])] - person_positions_with_hands: set[int] = set() - for hand in record.get("global_hands", []): - position = int(hand.get("person_id", 0)) - 1 - track_id = track_ids[position] if 0 <= position < len(track_ids) else position + 1 - side = str(hand.get("side", "unknown")) - region = _hand_mask(hand.get("keypoints", []), frame_shape) - if region is None: - continue - person_positions_with_hands.add(position) - regions.append( - ( - hand, - region, - "RTMW_HAND_KEYPOINTS", - track_id, - side, - str(hand.get("state", "unknown")), - ) - ) - - boxes = record.get("boxes", []) - body_fallbacks = [] - for position, box in enumerate(boxes): - if position in person_positions_with_hands: - continue - region = _box_mask(box, frame_shape) - if region is None: - continue - track_id = track_ids[position] if position < len(track_ids) else position + 1 - fallback = { - "track_id": track_id, - "side": "body", - "state": "rtmw_hand_unavailable", - "body_zone_box": box, - } - body_fallbacks.append(fallback) - regions.append( - ( - fallback, - region, - "YOLO_BODY_BOX_FALLBACK", - track_id, - "body", - "rtmw_hand_unavailable", - ) - ) - record["body_fallbacks"] = body_fallbacks - self._record_timing("region_build", stage_started) - - stage_started = time.perf_counter() - measurements = [] - unavailable_status = f"door_mask_{loaded.status}" - for owner, region, source, track_id, side, state in regions: - if door_mask is None or distance_map is None: - measurement = self._unavailable( - source, track_id, side, unavailable_status - ) - else: - measurement = self._measure_region( - region, door_mask, distance_map, source, track_id, side, state - ) - owner["risk"] = measurement - measurements.append(measurement) - self._record_timing("region_measure", stage_started) - - calculated = [item for item in measurements if item["risk_score"] is not None] - nearest = max(calculated, key=lambda item: item["risk_score"]) if calculated else None - if loaded.status != "loaded": - status = unavailable_status - elif not calculated: - status = "person_region_missing" - else: - status = "calculated" - frame_risk = { - "status": status, - "risk_score": nearest["risk_score"] if nearest else None, - "risk_level": nearest["risk_level"] if nearest else "unavailable", - "decision": nearest["decision"] if nearest else "EVIDENCE_UNAVAILABLE", - "source": nearest["source"] if nearest else None, - "track_id": nearest["track_id"] if nearest else None, - "side": nearest["side"] if nearest else None, - "door_mask": loaded.metadata(), - "warning_distance_relative": self.warning_relative, - "release_distance_relative": self.release_relative, - "regions": measurements, - } - self._record_timing("risk_total", total_started) - return frame_risk, loaded.mask - - def summary(self) -> dict: - return { - "mask_directory": str(self.sequence.directory.resolve()), - "constant_mask": ( - str(self.sequence.constant_path) - if self.sequence.constant_path is not None - else None - ), - "constant_for_all_frames": self.sequence.constant_path is not None, - "mask_format": self.sequence.source_format, - "door_labels": sorted(self.sequence.labels), - "frame_offset": self.sequence.frame_offset, - "warning_distance_relative": self.warning_relative, - "release_distance_relative": self.release_relative, - "mask_status_counts": dict(self.mask_status_counts), - "risk_level_counts": dict(self.level_counts), - "source_counts": dict(self.source_counts), - "maximum_risk_score": round(self.maximum_score, 5), - "constant_distance_map_cached": self.sequence.constant_path is not None, - "distance_map_cache_builds": self.distance_map_cache_builds, - "timing_ms": { - name: { - "count": int(self.timing_counts[name]), - "total": round(float(seconds) * 1000.0, 3), - "mean": round( - float(seconds) * 1000.0 - / max(int(self.timing_counts[name]), 1), - 3, - ), - } - for name, seconds in self.timing_seconds.items() - }, - "score_semantics": "2-D image overlap/proximity research score; not millimeters or safety certification", - "region_policy": "RTMW alarm-eligible hand hulls; YOLO body box only when a person has no usable RTMW hand region", - } diff --git a/orin/pipeline/door_zone_risk.py b/orin/pipeline/door_zone_risk.py deleted file mode 100644 index 972cfb7..0000000 --- a/orin/pipeline/door_zone_risk.py +++ /dev/null @@ -1,358 +0,0 @@ -"""Multi-feature person-to-door assignment and active-CAN risk gating.""" - -from __future__ import annotations - -import json -import math -from collections import Counter, defaultdict, deque -from dataclasses import dataclass -from pathlib import Path -from statistics import median - - -DOORS = ("front", "rear") -POSE_FEATURES = { - "shoulder_center_x", - "shoulder_center_y", - "shoulder_width", - "torso_height", - "hip_center_y", -} - - -@dataclass(frozen=True) -class FeatureCalibration: - rear: float - front: float - weight: float - - def frontness(self, value: float) -> float: - span = self.front - self.rear - if span == 0: - return 0.5 - return min(1.0, max(0.0, (value - self.rear) / span)) - - -@dataclass(frozen=True) -class DoorZoneConfig: - features: dict[str, FeatureCalibration] - front_probability_min: float - rear_probability_max: float - minimum_person_height: float - minimum_pose_features: int - history_frames: int - stale_track_frames: int - source_path: Path | None = None - - @classmethod - def load(cls, path: Path) -> "DoorZoneConfig": - source = Path(path).expanduser().resolve() - data = json.loads(source.read_text(encoding="utf-8-sig")) - features = { - name: FeatureCalibration( - rear=float(values["rear"]), - front=float(values["front"]), - weight=float(values["weight"]), - ) - for name, values in data["features"].items() - } - return cls( - features=features, - front_probability_min=float(data.get("front_probability_min", 0.72)), - rear_probability_max=float(data.get("rear_probability_max", 0.28)), - minimum_person_height=float(data.get("minimum_person_height", 0.40)), - minimum_pose_features=int(data.get("minimum_pose_features", 4)), - history_frames=int(data.get("history_frames", 5)), - stale_track_frames=int(data.get("stale_track_frames", 120)), - source_path=source, - ) - - def classify(self, probability: float) -> str: - if probability >= self.front_probability_min: - return "front" - if probability <= self.rear_probability_max: - return "rear" - return "transition" - - -def _point(keypoints: list, index: int) -> tuple[float, float] | None: - if index >= len(keypoints) or len(keypoints[index]) < 2: - return None - x, y = float(keypoints[index][0]), float(keypoints[index][1]) - return (x, y) if math.isfinite(x) and math.isfinite(y) else None - - -def extract_person_features( - record: dict, - position: int, - frame_shape: tuple[int, int], -) -> tuple[dict[str, float], int]: - """Extract fixed-camera pose features, with box geometry as low-weight fallback.""" - - height, width = frame_shape - box = record.get("boxes", [])[position] - x1, y1, x2, y2 = [float(value) for value in box[:4]] - features = { - "box_center_x": ((x1 + x2) * 0.5) / max(width, 1), - "box_height": (y2 - y1) / max(height, 1), - } - - all_keypoints = record.get("temporal_selected_keypoints", []) - if position >= len(all_keypoints): - return features, 0 - keypoints = all_keypoints[position] - left_shoulder = _point(keypoints, 5) - right_shoulder = _point(keypoints, 6) - left_hip = _point(keypoints, 11) - right_hip = _point(keypoints, 12) - if None in (left_shoulder, right_shoulder, left_hip, right_hip): - return features, 0 - - shoulder_x = (left_shoulder[0] + right_shoulder[0]) * 0.5 - shoulder_y = (left_shoulder[1] + right_shoulder[1]) * 0.5 - hip_y = (left_hip[1] + right_hip[1]) * 0.5 - features.update( - { - "shoulder_center_x": shoulder_x / max(width, 1), - "shoulder_center_y": shoulder_y / max(height, 1), - "shoulder_width": math.hypot( - left_shoulder[0] - right_shoulder[0], - left_shoulder[1] - right_shoulder[1], - ) - / max(width, 1), - "torso_height": abs(hip_y - shoulder_y) / max(height, 1), - "hip_center_y": hip_y / max(height, 1), - } - ) - return features, len(POSE_FEATURES) - - -class DoorZoneAssigner: - """Fuse pose-dominant evidence and smooth front probability per track.""" - - def __init__(self, config: DoorZoneConfig): - self.config = config - self.history: dict[int, deque[float]] = defaultdict( - lambda: deque(maxlen=self.config.history_frames) - ) - self.last_seen: dict[int, int] = {} - self.zone_counts: Counter = Counter() - - def _score(self, features: dict[str, float]) -> tuple[float, dict[str, float]]: - weighted_sum = 0.0 - total_weight = 0.0 - contributions = {} - for name, calibration in self.config.features.items(): - if name not in features: - continue - frontness = calibration.frontness(float(features[name])) - contributions[name] = round(frontness, 6) - weighted_sum += frontness * calibration.weight - total_weight += calibration.weight - probability = weighted_sum / total_weight if total_weight else 0.5 - return probability, contributions - - def update( - self, - frame_index: int, - record: dict, - frame_shape: tuple[int, int], - ) -> list[dict]: - track_ids = [int(value) for value in record.get("track_ids", [])] - assignments = [] - for position, _box in enumerate(record.get("boxes", [])): - track_id = track_ids[position] if position < len(track_ids) else position + 1 - features, pose_feature_count = extract_person_features( - record, position, frame_shape - ) - probability, contributions = self._score(features) - enough_pose = pose_feature_count >= self.config.minimum_pose_features - if features["box_height"] < self.config.minimum_person_height: - raw_zone = "unassigned" - door_zone = "unassigned" - elif not enough_pose: - raw_zone = "transition" - door_zone = "transition" - else: - raw_zone = self.config.classify(probability) - values = self.history[track_id] - values.append(probability) - door_zone = self.config.classify(float(median(values))) - - values = self.history.get(track_id, ()) - smoothed_probability = ( - float(median(values)) if values else float(probability) - ) - self.last_seen[track_id] = int(frame_index) - self.zone_counts[door_zone] += 1 - assignments.append( - { - "track_id": track_id, - "person_index": position, - "door_zone": door_zone, - "raw_door_zone": raw_zone, - "front_probability": round(float(probability), 6), - "smoothed_front_probability": round(smoothed_probability, 6), - "confidence": round(abs(smoothed_probability - 0.5) * 2.0, 6), - "evidence_source": "pose_primary_bbox_secondary", - "pose_feature_count": pose_feature_count, - "features": {name: round(value, 6) for name, value in features.items()}, - "feature_frontness": contributions, - "history_frames": len(values), - } - ) - - expired = [ - track_id - for track_id, seen_at in self.last_seen.items() - if frame_index - seen_at > self.config.stale_track_frames - ] - for track_id in expired: - self.history.pop(track_id, None) - self.last_seen.pop(track_id, None) - return assignments - - def summary(self) -> dict: - return { - "policy": "pose-primary weighted front probability with causal median", - "config": str(self.config.source_path) if self.config.source_path else None, - "history_frames": self.config.history_frames, - "front_probability_min": self.config.front_probability_min, - "rear_probability_max": self.config.rear_probability_max, - "minimum_person_height": self.config.minimum_person_height, - "minimum_pose_features": self.config.minimum_pose_features, - "features": { - name: { - "rear": calibration.rear, - "front": calibration.front, - "weight": calibration.weight, - } - for name, calibration in self.config.features.items() - }, - "zone_counts": dict(self.zone_counts), - } - - -def _inactive_risk(door: str) -> dict: - return { - "door_id": door, - "status": "inactive", - "risk_score": None, - "risk_level": "inactive", - "decision": "DOOR_CAN_NOT_ACTIVE", - "risk_type": f"{door.upper()}_INACTIVE", - "source": None, - "track_id": None, - "side": None, - "regions": [], - } - - -def _clear_risk(door: str) -> dict: - return { - "door_id": door, - "status": "calculated", - "risk_score": 0.0, - "risk_level": "clear", - "decision": "CLEAR_NO_PERSON_ASSIGNED_TO_ACTIVE_DOOR", - "risk_type": f"{door.upper()}_CLEAR", - "source": None, - "track_id": None, - "side": None, - "regions": [], - } - - -def _unknown_risk(door: str, transition_regions: list[dict]) -> dict: - return { - "door_id": door, - "status": "door_zone_ambiguous", - "risk_score": None, - "risk_level": "unknown", - "decision": "EVIDENCE_UNAVAILABLE_TRANSITION_ZONE", - "risk_type": f"{door.upper()}_UNKNOWN", - "source": None, - "track_id": None, - "side": None, - "regions": transition_regions, - } - - -def build_door_risks( - raw_risk: dict, - assignments: list[dict], - active_door: str, -) -> tuple[dict[str, dict], dict]: - """Build per-door risk and select the CAN-active door result.""" - - zone_by_track = { - int(assignment["track_id"]): str(assignment["door_zone"]) - for assignment in assignments - } - annotated_regions = [] - for original in raw_risk.get("regions", []): - region = dict(original) - region["door_zone"] = zone_by_track.get(int(region.get("track_id", -1)), "unknown") - annotated_regions.append(region) - - active_doors = set(DOORS) if active_door == "both" else {active_door} - if active_door == "none": - active_doors.clear() - - door_risks: dict[str, dict] = {} - for door in DOORS: - if door not in active_doors: - door_risks[door] = _inactive_risk(door) - continue - exact = [region for region in annotated_regions if region["door_zone"] == door] - transition = [ - region for region in annotated_regions if region["door_zone"] == "transition" - ] - calculated = [region for region in exact if region.get("risk_score") is not None] - if calculated: - nearest = max(calculated, key=lambda item: float(item["risk_score"])) - level = str(nearest["risk_level"]) - if transition and level in ("clear", "watch"): - door_risks[door] = _unknown_risk(door, transition) - else: - door_risks[door] = { - "door_id": door, - "status": "calculated", - "risk_score": nearest["risk_score"], - "risk_level": level, - "decision": nearest["decision"], - "risk_type": f"{door.upper()}_{level.upper()}", - "source": nearest["source"], - "track_id": nearest["track_id"], - "side": nearest["side"], - "regions": exact, - } - elif transition: - door_risks[door] = _unknown_risk(door, transition) - else: - door_risks[door] = _clear_risk(door) - - if len(active_doors) == 1: - selected = dict(door_risks[next(iter(active_doors))]) - elif not active_doors: - selected = { - "door_id": None, - "status": "inactive", - "risk_score": None, - "risk_level": "inactive", - "decision": "NO_DOOR_CAN_ACTIVE", - "risk_type": "NO_ACTIVE_DOOR", - "source": None, - "track_id": None, - "side": None, - "regions": [], - } - else: - severity = {"danger": 4, "warning": 3, "watch": 2, "unknown": 1, "clear": 0} - selected = dict( - max(door_risks.values(), key=lambda item: severity.get(item["risk_level"], -1)) - ) - selected["door_mask"] = raw_risk.get("door_mask") - selected["warning_distance_relative"] = raw_risk.get("warning_distance_relative") - selected["release_distance_relative"] = raw_risk.get("release_distance_relative") - return door_risks, selected diff --git a/orin/pipeline/latest_result.py b/orin/pipeline/latest_result.py deleted file mode 100644 index 1ab68ae..0000000 --- a/orin/pipeline/latest_result.py +++ /dev/null @@ -1,64 +0,0 @@ -"""One owned, immutable latest result, mirroring the 0920 LatestResult contract.""" -from __future__ import annotations - -from collections.abc import Mapping -import threading -from types import MappingProxyType - - -def freeze(value): - if isinstance(value, Mapping): - return MappingProxyType({key: freeze(item) for key, item in value.items()}) - if isinstance(value, (list, tuple)): - return tuple(freeze(item) for item in value) - return value - - -def result_to_dict(value): - """Return a mutable JSON-compatible copy when a consumer needs serialization.""" - if isinstance(value, Mapping): - return {key: result_to_dict(item) for key, item in value.items()} - if isinstance(value, tuple): - return [result_to_dict(item) for item in value] - return value - - -class LatestResult: - """Readers share a stable snapshot; reading never removes or acknowledges it. - - Only the latest result is retained here. Slow readers can skip frames, and - references they already hold remain valid after subsequent publications. - """ - - def __init__(self): - self._condition = threading.Condition() - self._value = None - self._closed = False - - def publish(self, result): - if result is None: - raise ValueError("Cannot publish a null result") - snapshot = freeze(result) - with self._condition: - if self._closed: - raise RuntimeError("Result publisher is closed") - self._value = snapshot - self._condition.notify_all() - - def load(self): - with self._condition: - return self._value - - def wait(self, after_frame_index=-1, timeout=None): - """Wait for a newer result; return None on timeout/end without a new frame.""" - with self._condition: - def has_new_frame(): - return self._value is not None and self._value['frame_index'] > after_frame_index - - self._condition.wait_for(lambda: has_new_frame() or self._closed, timeout) - return self._value if has_new_frame() else None - - def close(self): - with self._condition: - self._closed = True - self._condition.notify_all() diff --git a/orin/pipeline/native_backend.py b/orin/pipeline/native_backend.py deleted file mode 100644 index 6f425c5..0000000 --- a/orin/pipeline/native_backend.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Native Jetson TensorRT adapter; temporal/risk policy stays in upstream Python.""" -from __future__ import annotations -import ctypes as C -from pathlib import Path -import numpy as np - -ROOT = Path(__file__).resolve().parents[1] - -class NativeBackend: - def __init__(self, args): - self.lib = C.CDLL(str(ROOT / 'build/libvision_native.so')) - pointer = C.c_void_p - signatures = { - 'vap_error': ([], C.c_char_p), - 'vap_engine': ([C.c_char_p,C.c_int,C.c_int,C.c_int,C.c_float],pointer), - 'vap_engine_close': ([pointer],None), - 'vap_detect': ([pointer,pointer,C.c_int,C.c_int,pointer],C.c_int), - 'vap_pose': ([pointer,pointer,C.c_int,C.c_int,pointer,C.c_int,pointer],C.c_int), - 'vap_camera': ([C.c_char_p,C.c_int,C.c_int,C.c_int,C.c_int],pointer), - 'vap_camera_close': ([pointer],None), - 'vap_read': ([pointer,pointer,pointer],C.c_int), - 'vap_can': ([pointer],None), - } - for name,(inputs,output) in signatures.items(): - fn=getattr(self.lib,name);fn.argtypes=inputs;fn.restype=output - self.engine = self.lib.vap_engine(str(args.engines.resolve()).encode(),args.width,args.height,args.max_people,args.det_conf) - if not self.engine: self.fail() - self.camera=None - self.args=args - - def fail(self): - raise RuntimeError(self.lib.vap_error().decode()) - - def open_camera(self): - a=self.args - self.camera=self.lib.vap_camera(a.camera.encode(),a.width,a.height,a.fps,not a.no_can) - if not self.camera:self.fail() - - def read(self): - image=np.empty((self.args.height,self.args.width,3),np.uint8) - meta=np.zeros(4,np.float64) - result=self.lib.vap_read(self.camera,image.ctypes.data,meta.ctypes.data) - if result<0:self.fail() - if result==0:return None,None - return image,{'sequence':int(meta[0]),'capture_monotonic_ms':float(meta[1]),'dropped_before_inference':int(meta[2]),'driver_timestamp_ms':float(meta[3]),'latency_clock':'host_monotonic_at_dequeue'} - - def can_snapshot(self): - data=np.zeros(5,np.float64);self.lib.vap_can(data.ctypes.data) - return {'positions':[int(x) for x in data[:4]],'age_ms':float(data[4])} - - def detect(self, frame): - frame=np.ascontiguousarray(frame);out=np.empty((self.args.max_people,5),np.float32) - n=self.lib.vap_detect(self.engine,frame.ctypes.data,frame.shape[1],frame.shape[0],out.ctypes.data) - if n<0:self.fail() - return out[:n,:4].tolist(),out[:n,4].tolist() - - def pose(self,frame,bboxes): - frame=np.ascontiguousarray(frame);boxes=np.ascontiguousarray(bboxes,dtype=np.float32) - out=np.empty((len(boxes),133,3),np.float32) - n=self.lib.vap_pose(self.engine,frame.ctypes.data,frame.shape[1],frame.shape[0],boxes.ctypes.data,len(boxes),out.ctypes.data) - if n<0:self.fail() - return out[:n,:,:2].astype(np.float64),out[:n,:,2].astype(np.float64) - - def close(self): - if self.camera:self.lib.vap_camera_close(self.camera);self.camera=None - if self.engine:self.lib.vap_engine_close(self.engine);self.engine=None diff --git a/orin/pipeline/pose_engine.py b/orin/pipeline/pose_engine.py deleted file mode 100644 index fcc745c..0000000 --- a/orin/pipeline/pose_engine.py +++ /dev/null @@ -1,148 +0,0 @@ -"""Resident native TensorRT inference with causal pose fusion and tracking.""" -from __future__ import annotations -import time -from collections import defaultdict, deque -import numpy as np -from pose_tracking import BoxTracker - -SIDES = ("left", "right") - -class NativePoseEngine: - def __init__(self, args, native): - self.args = args - self.native = native - self.pose = native.pose - self.box_tracker = BoxTracker(max_age=30) - self.people = {} - self.timings = defaultdict(lambda: deque(maxlen=3000)) - self.isolated_inferences = 0 - - def infer(self, frame: np.ndarray, frame_idx: int) -> dict: - from pose_geometry import HAND_SLICES - from pose_fusion import adaptive_smooth, build_global_hands, fuse_person - from pose_occlusion import Measurement, resolve_global_hand_assignments - from pose_tracking import ( - LivePersonState, - mask_other_people, - update_live_v2, - ) - - infer_started = time.perf_counter() - height, width = frame.shape[:2] - started = time.perf_counter() - detection_height = max( - 1, min(height, round(height * self.args.det_roi_y_max_ratio)) - ) - boxes, confidences = self.native.detect(frame[:detection_height]) - self.timings["detector_ms"].append((time.perf_counter() - started) * 1000) - track_ids = self.box_tracker.update(boxes, frame_idx, (height, width)) - - # Retire disappeared people in long-running camera sessions. - for stale_id in list(self.people): - if stale_id not in self.box_tracker.tracks: - del self.people[stale_id] - if boxes: - started = time.perf_counter() - raw_keypoints, raw_scores = self.pose(frame, bboxes=boxes) - self.timings["raw_pose_ms"].append((time.perf_counter() - started) * 1000) - isolated_keypoints = raw_keypoints.copy() - isolated_scores = raw_scores.copy() - started = time.perf_counter() - for person_idx, bbox in enumerate(boxes): - target_area = max((bbox[2] - bbox[0]) * (bbox[3] - bbox[1]), 1.0) - needs_isolation = any( - idx != person_idx - and max((other[2] - other[0]) * (other[3] - other[1]), 1.0) - < 0.5 * target_area - for idx, other in enumerate(boxes) - ) - if not needs_isolation: - continue - masked = mask_other_people(frame, boxes, person_idx) - keypoints, scores = self.pose(masked, bboxes=[bbox]) - isolated_keypoints[person_idx] = keypoints[0] - isolated_scores[person_idx] = scores[0] - self.isolated_inferences += 1 - self.timings["isolated_extra_ms"].append( - (time.perf_counter() - started) * 1000 - ) - else: - raw_keypoints = np.empty((0, 133, 2), dtype=np.float64) - raw_scores = np.empty((0, 133), dtype=np.float64) - isolated_keypoints = raw_keypoints.copy() - isolated_scores = raw_scores.copy() - - selected_list, smoothed_list, metadata_list, states = [], [], [], [] - for person_idx, (bbox, track_id) in enumerate(zip(boxes, track_ids)): - state = self.people.setdefault(track_id, LivePersonState()) - selected, metadata = fuse_person( - raw_keypoints[person_idx], - raw_scores[person_idx], - isolated_keypoints[person_idx], - isolated_scores[person_idx], - bbox, - state.fusion, - ) - selected_list.append(selected) - smoothed_list.append(adaptive_smooth(selected, bbox, state.fusion)) - metadata_list.append(metadata) - states.append(state) - - measurements, endpoint_tracks = {}, {} - for person_idx, (selected, metadata, state) in enumerate( - zip(selected_list, metadata_list, states) - ): - for side, (start, end) in HAND_SLICES.items(): - meta = metadata[side] - measurements[(person_idx, side)] = ( - Measurement( - keypoints=selected[start:end], - precise=bool(meta["distance_eligible"]), - provisional=not bool(meta["distance_eligible"]), - quality=float(meta.get("selected_hand_score", 0.0)), - ) - if meta["alarm_eligible"] - else None - ) - endpoint_tracks[(person_idx, side)] = state.endpoints[side] - measurements, assignments = resolve_global_hand_assignments( - measurements, endpoint_tracks, boxes, 30 - ) - - v2_list, v2_meta_list = [], [] - for person_idx, (bbox, selected, smoothed, metadata, state) in enumerate( - zip(boxes, selected_list, smoothed_list, metadata_list, states) - ): - v2, v2_meta = update_live_v2( - frame_idx, - bbox, - selected, - smoothed, - metadata, - state, - {side: measurements[(person_idx, side)] for side in SIDES}, - {side: assignments.get((person_idx, side)) for side in SIDES}, - 12, - 30, - ) - v2_list.append(v2) - v2_meta_list.append(v2_meta) - - self.timings["pose_pipeline_total_ms"].append( - (time.perf_counter() - infer_started) * 1000 - ) - return { - "frame_index": frame_idx, - "track_ids": track_ids, - "boxes": np.round(np.asarray(boxes), 3).tolist() if boxes else [], - "det_confidences": [round(value, 4) for value in confidences], - "raw_keypoints": np.round(raw_keypoints, 3).tolist(), - "temporal_selected_keypoints": np.round( - np.asarray(selected_list), 3 - ).tolist(), - "temporal_keypoints": np.round(np.asarray(smoothed_list), 3).tolist(), - "temporal_metadata": metadata_list, - "v2_keypoints": np.round(np.asarray(v2_list), 3).tolist(), - "v2_metadata": v2_meta_list, - "global_hands": build_global_hands(selected_list, metadata_list), - } diff --git a/orin/pipeline/pose_fusion.py b/orin/pipeline/pose_fusion.py deleted file mode 100644 index 281b683..0000000 --- a/orin/pipeline/pose_fusion.py +++ /dev/null @@ -1,530 +0,0 @@ -"""Runtime pose fusion helpers.""" -from __future__ import annotations -import math -from dataclasses import dataclass, field -import cv2 -import numpy as np -from pose_geometry import BODY_WRISTS, HAND_EDGES_LOCAL, HAND_SLICES, normalized_keypoints - - -BODY_SIDE_INDICES = {"left": (5, 7, 9), "right": (6, 8, 10)} - - -SIDE_COLORS = {"left": (40, 40, 255), "right": (30, 230, 30)} - - -HAND_COLORS = {"left": (255, 80, 220), "right": (255, 190, 40)} - - -@dataclass -class Candidate: - source: str - keypoints: np.ndarray - scores: np.ndarray - normalized: np.ndarray - limb_score: float - hand_score: float - upper_length: float - forearm_length: float - hand_span: float - wrist_link: float - limb_valid: bool - hand_valid: bool - - -@dataclass -class SideTrack: - limb_previous: np.ndarray | None = None - limb_velocity: np.ndarray | None = None - hand_previous: np.ndarray | None = None - hand_velocity: np.ndarray | None = None - limb_source: str | None = None - hand_source: str | None = None - previous_hand_eligible: bool = False - - -@dataclass -class PersonTrack: - sides: dict[str, SideTrack] = field( - default_factory=lambda: {"left": SideTrack(), "right": SideTrack()} - ) - smoothed: np.ndarray | None = None - - -def bbox_diagonal(bbox: list[float]) -> float: - return max(math.hypot(bbox[2] - bbox[0], bbox[3] - bbox[1]), 1.0) - - -def build_candidate( - source: str, - keypoints: np.ndarray, - scores: np.ndarray, - bbox: list[float], - side: str, -) -> Candidate: - normalized = normalized_keypoints(keypoints, bbox) - shoulder, elbow, wrist = BODY_SIDE_INDICES[side] - hand_start, hand_end = HAND_SLICES[side] - hand = keypoints[hand_start:hand_end] - diagonal = bbox_diagonal(bbox) - upper_length = float(np.linalg.norm(normalized[shoulder] - normalized[elbow])) - forearm_length = float(np.linalg.norm(normalized[elbow] - normalized[wrist])) - hand_span = math.hypot(float(np.ptp(hand[:, 0])), float(np.ptp(hand[:, 1]))) / diagonal - wrist_link = float(np.linalg.norm(keypoints[wrist] - hand[0])) / diagonal - limb_score = float(np.mean(scores[[shoulder, elbow, wrist]])) - hand_score = float(np.mean(scores[hand_start:hand_end])) - limb_valid = 0.025 <= upper_length <= 0.8 and 0.025 <= forearm_length <= 0.8 - hand_valid = 0.008 <= hand_span <= 0.35 and wrist_link <= 0.12 - return Candidate( - source=source, - keypoints=keypoints, - scores=scores, - normalized=normalized, - limb_score=limb_score, - hand_score=hand_score, - upper_length=upper_length, - forearm_length=forearm_length, - hand_span=hand_span, - wrist_link=wrist_link, - limb_valid=limb_valid, - hand_valid=hand_valid, - ) - - -def predicted(previous: np.ndarray | None, velocity: np.ndarray | None) -> np.ndarray | None: - if previous is None: - return None - if velocity is None: - return previous - return previous + 0.65 * velocity - - -def robust_distance(points: np.ndarray, target: np.ndarray | None) -> float: - if target is None: - return 0.0 - distances = np.linalg.norm(points - target, axis=1) - return float(np.median(distances)) - - -def select_limb( - candidates: list[Candidate], - side: str, - track: SideTrack | None, -) -> tuple[Candidate, dict[str, float]]: - indices = np.asarray(BODY_SIDE_INDICES[side]) - target = predicted(track.limb_previous, track.limb_velocity) if track else None - scored: list[tuple[float, Candidate, float]] = [] - for candidate in candidates: - temporal_cost = robust_distance(candidate.normalized[indices], target) - geometry_bonus = 1.2 if candidate.limb_valid else -5.0 - switch_bonus = 0.35 if track and candidate.source == track.limb_source else 0.0 - score = candidate.limb_score + geometry_bonus + switch_bonus - 7.0 * temporal_cost - scored.append((score, candidate, temporal_cost)) - score, selected, temporal_cost = max(scored, key=lambda item: item[0]) - return selected, {"score": score, "temporal_cost": temporal_cost} - - -def hand_link_to_limb( - candidate: Candidate, - selected_limb: Candidate, - bbox: list[float], - side: str, -) -> float: - hand_start, _ = HAND_SLICES[side] - wrist_idx = BODY_WRISTS[side] - return float( - np.linalg.norm(candidate.keypoints[hand_start] - selected_limb.keypoints[wrist_idx]) - ) / bbox_diagonal(bbox) - - -def select_hand( - candidates: list[Candidate], - selected_limb: Candidate, - bbox: list[float], - side: str, - track: SideTrack | None, -) -> tuple[Candidate, bool, dict[str, float]]: - start, end = HAND_SLICES[side] - target = predicted(track.hand_previous, track.hand_velocity) if track else None - scored: list[tuple[float, Candidate, bool, float, float]] = [] - for candidate in candidates: - temporal_cost = robust_distance(candidate.normalized[start:end], target) - link = hand_link_to_limb(candidate, selected_limb, bbox, side) - valid = candidate.hand_valid - geometry_bonus = 3.0 if valid else -7.0 - switch_bonus = 0.45 if track and candidate.source == track.hand_source else 0.0 - limb_source_bonus = 0.15 if candidate.source == selected_limb.source else 0.0 - # A newly visible endpoint may disagree with the currently selected - # (occlusion-driven) limb. Keep this as a soft cost so strong image - # evidence can switch both the hand and its limb together. - link_penalty = 1.5 * min(max(link - 0.12, 0.0), 0.2) - score = ( - candidate.hand_score - + geometry_bonus - + switch_bonus - + limb_source_bonus - - link_penalty - - 8.0 * min(temporal_cost, 0.15) - ) - scored.append((score, candidate, valid, temporal_cost, link)) - score, selected, valid, temporal_cost, link = max(scored, key=lambda item: item[0]) - return selected, valid, { - "score": score, - "temporal_cost": temporal_cost, - "link_to_selected_limb": link, - } - - -def candidate_disagreement( - raw: Candidate, - isolated: Candidate, - side: str, -) -> tuple[float, float]: - shoulder, elbow, wrist = BODY_SIDE_INDICES[side] - start, end = HAND_SLICES[side] - limb = float( - np.median( - np.linalg.norm( - raw.normalized[[shoulder, elbow, wrist]] - - isolated.normalized[[shoulder, elbow, wrist]], - axis=1, - ) - ) - ) - hand_distances = np.linalg.norm( - raw.normalized[start:end] - isolated.normalized[start:end], axis=1 - ) - hand = float(np.percentile(hand_distances, 90)) - return limb, hand - - -def suppress_duplicate_hands( - fused: np.ndarray, - metadata: dict, - bbox: list[float], -) -> None: - left_start, left_end = HAND_SLICES["left"] - right_start, right_end = HAND_SLICES["right"] - left_center = np.mean(fused[left_start:left_end], axis=0) - right_center = np.mean(fused[right_start:right_end], axis=0) - separation = float(np.linalg.norm(left_center - right_center)) / bbox_diagonal(bbox) - if separation >= 0.08: - return - weaker = min( - ("left", "right"), - key=lambda side: metadata[side]["selected_hand_score"], - ) - metadata[weaker]["hand_state"] = "duplicate" - metadata[weaker]["distance_eligible"] = False - metadata[weaker]["alarm_eligible"] = False - metadata[weaker]["duplicate_hand_separation"] = round(separation, 5) - - -def fuse_person( - raw_keypoints: np.ndarray, - raw_scores: np.ndarray, - isolated_keypoints: np.ndarray, - isolated_scores: np.ndarray, - bbox: list[float], - track: PersonTrack | None, -) -> tuple[np.ndarray, dict]: - fused = raw_keypoints.copy() - metadata: dict[str, dict] = {} - - for side in ("left", "right"): - side_track = track.sides[side] if track else None - raw = build_candidate("raw", raw_keypoints, raw_scores, bbox, side) - isolated = build_candidate( - "isolated", isolated_keypoints, isolated_scores, bbox, side - ) - candidates = [raw, isolated] - limb, limb_detail = select_limb(candidates, side, side_track) - hand, hand_valid, hand_detail = select_hand( - candidates, limb, bbox, side, side_track - ) - - other_hand = isolated if hand.source == "raw" else raw - endpoint_advantage = hand.hand_score - other_hand.hand_score - if hand.source != limb.source and hand_valid and endpoint_advantage >= 0.6: - limb = hand - limb_detail["visible_endpoint_override"] = 1.0 - hand_detail["link_to_selected_limb"] = hand.wrist_link - - body_indices = np.asarray(BODY_SIDE_INDICES[side]) - hand_start, hand_end = HAND_SLICES[side] - fused[body_indices] = limb.keypoints[body_indices] - fused[hand_start:hand_end] = hand.keypoints[hand_start:hand_end] - - limb_disagreement, hand_disagreement = candidate_disagreement(raw, isolated, side) - if limb_disagreement <= 0.05: - limb_state = "consensus" - else: - limb_state = "inferred" - - score_advantage = raw.hand_score - isolated.hand_score - raw_temporally_stable = hand_detail["temporal_cost"] <= 0.08 - if not hand_valid: - hand_state = "rejected" - distance_eligible = False - elif hand_disagreement <= 0.06: - hand_state = "consensus" - distance_eligible = True - elif ( - hand.source == "raw" - and score_advantage >= 0.6 - and raw_temporally_stable - ): - hand_state = "visible_endpoint" - distance_eligible = True - elif hand.source == "isolated": - hand_state = "inferred" - distance_eligible = False - else: - hand_state = "conflict" - distance_eligible = False - fast_alarm_endpoint = ( - hand_valid and hand.source == "raw" and score_advantage >= 0.6 - ) - alarm_eligible = distance_eligible or fast_alarm_endpoint - - metadata[side] = { - "limb_source": limb.source, - "hand_source": hand.source, - "limb_state": limb_state, - "hand_state": hand_state, - "distance_eligible": distance_eligible, - "alarm_eligible": alarm_eligible, - "limb_disagreement": round(limb_disagreement, 5), - "hand_disagreement": round(hand_disagreement, 5), - "raw_hand_score": round(raw.hand_score, 4), - "isolated_hand_score": round(isolated.hand_score, 4), - "selected_hand_span": round(hand.hand_span, 5), - "selected_hand_score": round(hand.hand_score, 4), - "selected_hand_valid": hand_valid, - "limb_selection": {key: round(value, 5) for key, value in limb_detail.items()}, - "hand_selection": {key: round(value, 5) for key, value in hand_detail.items()}, - } - - if side_track is not None: - limb_now = limb.normalized[body_indices] - hand_now = hand.normalized[hand_start:hand_end] - if side_track.limb_previous is not None: - delta = limb_now - side_track.limb_previous - side_track.limb_velocity = ( - delta - if side_track.limb_velocity is None - else 0.65 * side_track.limb_velocity + 0.35 * delta - ) - if hand_valid: - if side_track.hand_previous is not None: - delta = hand_now - side_track.hand_previous - side_track.hand_velocity = ( - delta - if side_track.hand_velocity is None - else 0.65 * side_track.hand_velocity + 0.35 * delta - ) - side_track.hand_previous = hand_now - side_track.hand_source = hand.source - side_track.limb_previous = limb_now - side_track.limb_source = limb.source - side_track.previous_hand_eligible = distance_eligible - - suppress_duplicate_hands(fused, metadata, bbox) - return fused, metadata - - -def adaptive_smooth( - keypoints: np.ndarray, - bbox: list[float], - track: PersonTrack, -) -> np.ndarray: - normalized = normalized_keypoints(keypoints, bbox) - if track.smoothed is None: - track.smoothed = normalized - else: - displacement = np.linalg.norm(normalized - track.smoothed, axis=1) - alpha = 0.32 + 0.58 * np.clip(displacement / 0.08, 0.0, 1.0) - track.smoothed = alpha[:, None] * normalized + (1.0 - alpha[:, None]) * track.smoothed - - x1, y1, x2, y2 = bbox - result = track.smoothed.copy() - result[:, 0] = result[:, 0] * max(x2 - x1, 1.0) + x1 - result[:, 1] = result[:, 1] * max(y2 - y1, 1.0) + y1 - return result - - -def build_global_hands( - keypoints: list[np.ndarray], - metadata: list[dict], -) -> list[dict]: - candidates = [] - for person_idx, (person_keypoints, person_meta) in enumerate( - zip(keypoints, metadata), start=1 - ): - for side, (start, end) in HAND_SLICES.items(): - meta = person_meta[side] - if not meta["alarm_eligible"]: - continue - hand = person_keypoints[start:end] - span_px = math.hypot(float(np.ptp(hand[:, 0])), float(np.ptp(hand[:, 1]))) - candidates.append( - { - "person_id": person_idx, - "side": side, - "source": meta["hand_source"], - "state": meta["hand_state"], - "distance_eligible": meta["distance_eligible"], - "alarm_eligible": meta["alarm_eligible"], - "score": meta["selected_hand_score"], - "center": np.mean(hand, axis=0), - "span_px": span_px, - "keypoints": hand, - "aliases": [], - } - ) - - kept: list[dict] = [] - for candidate in sorted(candidates, key=lambda item: item["score"], reverse=True): - duplicate = None - for existing in kept: - threshold = float(np.clip(0.45 * max(candidate["span_px"], existing["span_px"]), 18, 70)) - if float(np.linalg.norm(candidate["center"] - existing["center"])) <= threshold: - duplicate = existing - break - if duplicate is None: - kept.append(candidate) - else: - duplicate["aliases"].append( - {"person_id": candidate["person_id"], "side": candidate["side"]} - ) - - return [ - { - "person_id": item["person_id"], - "side": item["side"], - "source": item["source"], - "state": item["state"], - "distance_eligible": item["distance_eligible"], - "alarm_eligible": item["alarm_eligible"], - "score": item["score"], - "center": np.round(item["center"], 3).tolist(), - "span_px": round(item["span_px"], 3), - "keypoints": np.round(item["keypoints"], 3).tolist(), - "aliases": item["aliases"], - } - for item in kept - ] - - -def draw_dashed_line( - image: np.ndarray, - start: np.ndarray, - end: np.ndarray, - color: tuple[int, int, int], - thickness: int = 2, -) -> None: - a = np.asarray(start, dtype=np.float64) - b = np.asarray(end, dtype=np.float64) - length = float(np.linalg.norm(b - a)) - if length < 1.0: - return - direction = (b - a) / length - for offset in np.arange(0.0, length, 14.0): - segment_start = a + direction * offset - segment_end = a + direction * min(offset + 8.0, length) - cv2.line( - image, - tuple(np.round(segment_start).astype(int)), - tuple(np.round(segment_end).astype(int)), - color, - thickness, - cv2.LINE_AA, - ) - - -def draw_fused( - image: np.ndarray, - boxes: list[list[float]], - keypoints: list[np.ndarray], - metadata: list[dict], - title: str, -) -> np.ndarray: - canvas = image.copy() - for person_idx, (bbox, kpts, person_meta) in enumerate( - zip(boxes, keypoints, metadata), start=1 - ): - x1, y1, x2, y2 = (int(round(value)) for value in bbox) - cv2.rectangle(canvas, (x1, y1), (x2, y2), (0, 220, 255), 2) - cv2.putText( - canvas, - f"P{person_idx}", - (x1 + 4, max(22, y1 + 24)), - cv2.FONT_HERSHEY_SIMPLEX, - 0.72, - (0, 220, 255), - 2, - cv2.LINE_AA, - ) - for side in ("left", "right"): - shoulder, elbow, wrist = BODY_SIDE_INDICES[side] - side_meta = person_meta[side] - color = SIDE_COLORS[side] - for a, b in ((shoulder, elbow), (elbow, wrist)): - if side_meta["limb_state"] == "consensus": - cv2.line( - canvas, - tuple(np.round(kpts[a]).astype(int)), - tuple(np.round(kpts[b]).astype(int)), - color, - 3, - cv2.LINE_AA, - ) - else: - draw_dashed_line(canvas, kpts[a], kpts[b], (0, 140, 255), 3) - - start, end = HAND_SLICES[side] - hand = kpts[start:end] - if side_meta["hand_state"] != "rejected": - hand_color = HAND_COLORS[side] - if not side_meta["distance_eligible"]: - hand_color = (0, 140, 255) - for a, b in HAND_EDGES_LOCAL: - if side_meta["distance_eligible"]: - cv2.line( - canvas, - tuple(np.round(hand[a]).astype(int)), - tuple(np.round(hand[b]).astype(int)), - hand_color, - 2, - cv2.LINE_AA, - ) - else: - draw_dashed_line(canvas, hand[a], hand[b], hand_color, 2) - label = ( - f"{side[0].upper()}:{side_meta['hand_state'][:4]}" - f"/{side_meta['hand_source'][0]}" - ) - label_y = max(45, y1 + (48 if side == "left" else 70)) - cv2.putText( - canvas, - label, - (x1 + 4, label_y), - cv2.FONT_HERSHEY_SIMPLEX, - 0.5, - HAND_COLORS[side] if side_meta["distance_eligible"] else (0, 140, 255), - 1, - cv2.LINE_AA, - ) - - cv2.rectangle(canvas, (0, 0), (650, 45), (0, 0, 0), -1) - cv2.putText( - canvas, - title, - (10, 31), - cv2.FONT_HERSHEY_SIMPLEX, - 0.8, - (255, 255, 255), - 2, - cv2.LINE_AA, - ) - return canvas diff --git a/orin/pipeline/pose_geometry.py b/orin/pipeline/pose_geometry.py deleted file mode 100644 index e7f6ecb..0000000 --- a/orin/pipeline/pose_geometry.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Runtime pose geometry helpers.""" -from __future__ import annotations -import numpy as np - - -HAND_EDGES_LOCAL = ( - (0, 1), (1, 2), (2, 3), (3, 4), - (0, 5), (5, 6), (6, 7), (7, 8), - (0, 9), (9, 10), (10, 11), (11, 12), - (0, 13), (13, 14), (14, 15), (15, 16), - (0, 17), (17, 18), (18, 19), (19, 20), -) - - -HAND_SLICES = {"left": (91, 112), "right": (112, 133)} - - -BODY_WRISTS = {"left": 9, "right": 10} - - -def normalized_keypoints(kpts: np.ndarray, bbox: list[float]) -> np.ndarray: - x1, y1, x2, y2 = bbox - width = max(x2 - x1, 1.0) - height = max(y2 - y1, 1.0) - result = kpts.copy().astype(np.float64) - result[:, 0] = (result[:, 0] - x1) / width - result[:, 1] = (result[:, 1] - y1) / height - return result diff --git a/orin/pipeline/pose_occlusion.py b/orin/pipeline/pose_occlusion.py deleted file mode 100644 index 1ed4661..0000000 --- a/orin/pipeline/pose_occlusion.py +++ /dev/null @@ -1,513 +0,0 @@ -"""Runtime pose occlusion helpers.""" -from __future__ import annotations -import math -from dataclasses import dataclass -import numpy as np -from pose_geometry import HAND_SLICES - - -@dataclass -class Measurement: - keypoints: np.ndarray - precise: bool - provisional: bool - source: str = "model" - quality: float = 0.0 - - -@dataclass -class EndpointTrack: - wrist: np.ndarray | None = None - velocity: np.ndarray | None = None - hand_shape: np.ndarray | None = None - last_observed_frame: int | None = None - missed_frames: int = 0 - last_elbow: np.ndarray | None = None - last_direction: np.ndarray | None = None - - -def bbox_diagonal(bbox: list[float]) -> float: - return max(math.hypot(bbox[2] - bbox[0], bbox[3] - bbox[1]), 1.0) - - -def solve_two_bone_elbow( - shoulder: np.ndarray, - wrist: np.ndarray, - upper_length: float, - forearm_length: float, - reference_elbow: np.ndarray, -) -> np.ndarray: - direction = wrist - shoulder - distance = float(np.linalg.norm(direction)) - if distance < 1e-6: - return reference_elbow.copy() - unit = direction / distance - - if distance >= upper_length + forearm_length: - return shoulder + unit * upper_length - if distance <= abs(upper_length - forearm_length): - sign = 1.0 if upper_length >= forearm_length else -1.0 - return shoulder + unit * upper_length * sign - - along = ( - upper_length * upper_length - - forearm_length * forearm_length - + distance * distance - ) / (2.0 * distance) - height = math.sqrt(max(upper_length * upper_length - along * along, 0.0)) - base = shoulder + unit * along - perpendicular = np.asarray([-unit[1], unit[0]]) - candidates = (base + perpendicular * height, base - perpendicular * height) - return min(candidates, key=lambda candidate: float(np.linalg.norm(candidate - reference_elbow))) - - -def update_endpoint_track( - track: EndpointTrack, - measurement: Measurement | None, - frame_idx: int, - short_occlusion_frames: int, - medium_occlusion_frames: int, -) -> tuple[np.ndarray | None, np.ndarray | None, str]: - if measurement is not None: - measured_wrist = measurement.keypoints[0] - measured_shape = measurement.keypoints - measured_wrist - if track.wrist is None or track.missed_frames > short_occlusion_frames: - track.wrist = measured_wrist.copy() - track.velocity = np.zeros(2, dtype=np.float64) - track.hand_shape = measured_shape.copy() - else: - prediction = track.wrist + track.velocity - residual = measured_wrist - prediction - alpha = 0.78 if measurement.precise else 0.88 - beta = 0.18 if measurement.precise else 0.24 - track.wrist = prediction + alpha * residual - track.velocity = 0.82 * track.velocity + beta * residual - shape_alpha = 0.55 if measurement.precise else 0.7 - track.hand_shape = ( - shape_alpha * measured_shape + (1.0 - shape_alpha) * track.hand_shape - ) - track.last_observed_frame = frame_idx - track.missed_frames = 0 - if measurement.source == "global_reassignment": - state = "reassigned" - elif measurement.source == "temporal_recovery": - state = "recovered" - elif measurement.precise: - state = "observed" - elif measurement.provisional: - state = "provisional" - else: - state = "interpolated" - return track.wrist.copy(), track.wrist + track.hand_shape, state - - track.missed_frames += 1 - if track.wrist is None: - return None, None, "reachable_long" - if track.missed_frames <= short_occlusion_frames: - track.wrist = track.wrist + track.velocity - track.velocity *= 0.86 - return track.wrist.copy(), track.wrist + track.hand_shape, "predicted_short" - if track.missed_frames <= medium_occlusion_frames: - return None, None, "reachable_medium" - return None, None, "reachable_long" - - -def temporal_recovery_measurement( - keypoints: np.ndarray, - metadata: dict, - side: str, - bbox: list[float], - track: EndpointTrack, - medium_occlusion_frames: int, -) -> Measurement | None: - """Recover a weak current-frame hand only when history strongly supports it.""" - if track.wrist is None or track.missed_frames > medium_occlusion_frames: - return None - if metadata.get("hand_state") not in {"conflict", "inferred"}: - return None - if not metadata.get("selected_hand_valid", False): - return None - if float(metadata.get("selected_hand_score", 0.0)) < 2.0: - return None - link = float(metadata.get("hand_selection", {}).get("link_to_selected_limb", 1.0)) - if link > 0.08: - return None - - start, end = HAND_SLICES[side] - hand = np.asarray(keypoints[start:end], dtype=np.float64) - if hand.shape != (end - start, 2) or not np.isfinite(hand).all(): - return None - prediction = track.wrist + track.velocity - diagonal = bbox_diagonal(bbox) - gate = min(0.20, 0.05 + 0.008 * max(track.missed_frames, 1)) * diagonal - if float(np.linalg.norm(hand[0] - prediction)) > gate: - return None - return Measurement( - keypoints=hand, - precise=False, - provisional=True, - source="temporal_recovery", - quality=float(metadata.get("selected_hand_score", 0.0)), - ) - - -def hand_span_px(hand: np.ndarray) -> float: - return math.hypot(float(np.ptp(hand[:, 0])), float(np.ptp(hand[:, 1]))) - - -def bbox_area(bbox: list[float]) -> float: - return max((bbox[2] - bbox[0]) * (bbox[3] - bbox[1]), 1.0) - - -def reset_endpoint_track(track: EndpointTrack) -> None: - track.wrist = None - track.velocity = None - track.hand_shape = None - track.last_observed_frame = None - track.missed_frames = 0 - track.last_elbow = None - track.last_direction = None - - -def resolve_global_hand_assignments( - frame_measurements: dict[tuple[int, str], Measurement | None], - tracks: dict[tuple[int, str], EndpointTrack], - boxes: list[list[float]], - medium_occlusion_frames: int, -) -> tuple[dict[tuple[int, str], Measurement | None], dict[tuple[int, str], dict]]: - """Resolve overlapping-person hands before binding them to body sides. - - Large foreground top-down crops may include a smaller background person. - The same physical hand can therefore be emitted for both people, or only by - the smaller crop even though it belongs to the foreground person. Direct - duplicates are kept on the foreground track when their apparent scales - agree. A missing foreground side can then reclaim a nearby candidate using - its recent hand trajectory and historical hand scale. - """ - resolved = dict(frame_measurements) - events: dict[tuple[int, str], dict] = {} - keys = list(resolved) - - # First collapse direct cross-person duplicates. Keep this deliberately - # asymmetric: a substantially larger foreground crop must also carry the - # substantially larger historical hand shape. - for index, first_key in enumerate(keys): - first = resolved[first_key] - if first is None: - continue - for second_key in keys[index + 1 :]: - second = resolved[second_key] - if second is None or first_key[0] == second_key[0]: - continue - first_hand = first.keypoints - second_hand = second.keypoints - first_span = hand_span_px(first_hand) - second_span = hand_span_px(second_hand) - center_distance = float( - np.linalg.norm(np.mean(first_hand, axis=0) - np.mean(second_hand, axis=0)) - ) - if center_distance > max(70.0, 0.5 * max(first_span, second_span)): - continue - - first_area = bbox_area(boxes[first_key[0]]) - second_area = bbox_area(boxes[second_key[0]]) - if first_area >= 2.5 * second_area and ( - first_span >= 1.45 * second_span - or first.quality >= second.quality + 0.6 - ): - owner_key, released_key = first_key, second_key - elif second_area >= 2.5 * first_area and ( - second_span >= 1.45 * first_span - or second.quality >= first.quality + 0.6 - ): - owner_key, released_key = second_key, first_key - else: - continue - resolved[released_key] = None - reset_endpoint_track(tracks[released_key]) - events[owner_key] = { - "method": "foreground_duplicate_owner", - "released_person_id": released_key[0] + 1, - "released_side": released_key[1], - } - events[released_key] = { - "method": "reassigned_away", - "assigned_person_id": owner_key[0] + 1, - "assigned_side": owner_key[1], - } - - # Then recover missing foreground hands from another crop. The candidate - # must fall inside the target's recent motion gate and be much smaller than - # the target's historical foreground hand shape. - available_sources = [key for key, value in resolved.items() if value is not None] - target_candidates = [] - for target_key, target_measurement in resolved.items(): - if target_measurement is not None: - continue - target_track = tracks[target_key] - if ( - target_track.wrist is None - or target_track.hand_shape is None - or target_track.missed_frames > medium_occlusion_frames - ): - continue - target_area = bbox_area(boxes[target_key[0]]) - predicted_center = target_track.wrist + np.mean(target_track.hand_shape, axis=0) - target_span = hand_span_px(target_track.hand_shape) - gate = 0.20 * bbox_diagonal(boxes[target_key[0]]) - for source_key in available_sources: - if source_key[0] == target_key[0]: - continue - source = resolved[source_key] - if source is None or source.source != "model": - continue - source_area = bbox_area(boxes[source_key[0]]) - if target_area < 2.5 * source_area: - continue - source_center = np.mean(source.keypoints, axis=0) - source_span = hand_span_px(source.keypoints) - distance = float(np.linalg.norm(source_center - predicted_center)) - if distance > gate or target_span < 1.45 * max(source_span, 1.0): - continue - target_candidates.append( - (distance / gate, target_key, source_key, source_center) - ) - - used_targets = set() - used_sources = set() - for _, target_key, source_key, source_center in sorted(target_candidates): - if target_key in used_targets or source_key in used_sources: - continue - target_track = tracks[target_key] - shape_center = np.mean(target_track.hand_shape, axis=0) - reassigned_wrist = source_center - shape_center - reassigned_hand = reassigned_wrist + target_track.hand_shape - resolved[target_key] = Measurement( - keypoints=reassigned_hand, - precise=False, - provisional=True, - source="global_reassignment", - quality=resolved[source_key].quality, - ) - resolved[source_key] = None - reset_endpoint_track(tracks[source_key]) - events[target_key] = { - "method": "foreground_temporal_reassignment", - "source_person_id": source_key[0] + 1, - "source_side": source_key[1], - "observed_center": np.round(source_center, 3).tolist(), - } - events[source_key] = { - "method": "reassigned_away", - "assigned_person_id": target_key[0] + 1, - "assigned_side": target_key[1], - } - used_targets.add(target_key) - used_sources.add(source_key) - return resolved, events - - -def track_direction(track: EndpointTrack) -> np.ndarray: - if track.last_direction is not None: - return track.last_direction / max(float(np.linalg.norm(track.last_direction)), 1e-6) - if track.velocity is not None and float(np.linalg.norm(track.velocity)) > 1e-6: - return track.velocity / np.linalg.norm(track.velocity) - return np.asarray([1.0, 0.0], dtype=np.float64) - - -def ellipse_polygon( - center: np.ndarray, - major_axis: float, - minor_axis: float, - direction: np.ndarray, - point_count: int = 64, -) -> np.ndarray: - perpendicular = np.asarray([-direction[1], direction[0]], dtype=np.float64) - angles = np.linspace(0.0, 2.0 * math.pi, point_count, endpoint=False) - return ( - center - + np.cos(angles)[:, None] * major_axis * direction - + np.sin(angles)[:, None] * minor_axis * perpendicular - ) - - -def clip_polygon_from_anchor_to_circle( - points: np.ndarray, - anchor: np.ndarray, - circle_center: np.ndarray, - radius: float, -) -> np.ndarray: - """Clip radial polygon samples to a shoulder-centered anatomical limit.""" - clipped = [] - offset = anchor - circle_center - for point in points: - if float(np.linalg.norm(point - circle_center)) <= radius: - clipped.append(point) - continue - ray = point - anchor - a = float(np.dot(ray, ray)) - b = 2.0 * float(np.dot(offset, ray)) - c = float(np.dot(offset, offset) - radius * radius) - discriminant = max(b * b - 4.0 * a * c, 0.0) - roots = [ - value - for value in ( - (-b - math.sqrt(discriminant)) / max(2.0 * a, 1e-6), - (-b + math.sqrt(discriminant)) / max(2.0 * a, 1e-6), - ) - if 0.0 <= value <= 1.0 - ] - clipped.append(anchor + (max(roots) if roots else 0.0) * ray) - return np.asarray(clipped, dtype=np.float64) - - -def medium_motion_region( - shoulder: np.ndarray, - upper_length: float, - forearm_length: float, - bbox: list[float], - track: EndpointTrack, - short_occlusion_frames: int, -) -> dict: - diagonal = bbox_diagonal(bbox) - direction = track_direction(track) - hand_shape = ( - track.hand_shape - if track.hand_shape is not None - else np.zeros((21, 2), dtype=np.float64) - ) - shape_center = np.mean(hand_shape, axis=0) - centered_shape = hand_shape - shape_center - perpendicular = np.asarray([-direction[1], direction[0]], dtype=np.float64) - hand_major = float(np.max(np.abs(centered_shape @ direction))) - hand_minor = float(np.max(np.abs(centered_shape @ perpendicular))) - - extra_frames = max(track.missed_frames - short_occlusion_frames, 1) - motion_major = min(0.20, 0.04 + 0.007 * extra_frames) * diagonal - motion_minor = min(0.13, 0.03 + 0.0045 * extra_frames) * diagonal - major_axis = max(hand_major + motion_major, 0.08 * diagonal) - minor_axis = max(hand_minor + motion_minor, 0.06 * diagonal) - - center = track.wrist + shape_center - tracked_hand_extent = float(np.max(np.linalg.norm(centered_shape, axis=1))) - hand_reach = max(tracked_hand_extent, 0.10 * diagonal) - anatomical_reach = upper_length + forearm_length + hand_reach + 0.02 * diagonal - shoulder_to_center = center - shoulder - center_distance = float(np.linalg.norm(shoulder_to_center)) - if center_distance > 0.96 * anatomical_reach: - center = shoulder + shoulder_to_center / center_distance * 0.96 * anatomical_reach - - polygon = ellipse_polygon(center, major_axis, minor_axis, direction) - polygon = clip_polygon_from_anchor_to_circle( - polygon, - center, - shoulder, - anatomical_reach, - ) - direction_deg = math.degrees(math.atan2(float(direction[1]), float(direction[0]))) - return { - "geometry": "motion_corridor", - "center": np.round(center, 3).tolist(), - "predicted_wrist": np.round(track.wrist, 3).tolist(), - "major_axis_px": round(major_axis, 3), - "minor_axis_px": round(minor_axis, 3), - "outer_radius_px": round(major_axis, 3), - "direction_deg": round(direction_deg, 3), - "max_reach_from_shoulder_px": round(anatomical_reach, 3), - "polygon": np.round(polygon, 3).tolist(), - "requires_door_intersection": True, - "render_full_region": True, - "distance_lower_bound_rule": "minimum distance from motion-corridor polygon to door_edge", - } - - -def long_reachable_region( - shoulder: np.ndarray, - upper_length: float, - forearm_length: float, - bbox: list[float], - track: EndpointTrack, - medium_occlusion_frames: int, -) -> dict: - diagonal = bbox_diagonal(bbox) - height = max(bbox[3] - bbox[1], 1.0) - arm_reach = upper_length + forearm_length - hand_reach = min(0.13 * diagonal, 0.17 * height) - safety_margin = 0.025 * diagonal - outer_radius = arm_reach + hand_reach + safety_margin - inner_radius = max(abs(upper_length - forearm_length) - 0.04 * diagonal, 0.0) - if track.last_direction is None: - direction_deg = 0.0 - half_angle_deg = 180.0 - else: - direction = track_direction(track) - direction_deg = math.degrees(math.atan2(float(direction[1]), float(direction[0]))) - half_angle_deg = min( - 180.0, - 65.0 + 2.0 * max(track.missed_frames - medium_occlusion_frames, 0), - ) - return { - "geometry": "shoulder_sector", - "center": np.round(shoulder, 3).tolist(), - "inner_radius_px": round(inner_radius, 3), - "outer_radius_px": round(outer_radius, 3), - "arm_reach_px": round(arm_reach, 3), - "hand_reach_px": round(hand_reach, 3), - "safety_margin_px": round(safety_margin, 3), - "direction_deg": round(direction_deg, 3), - "half_angle_deg": round(half_angle_deg, 3), - "requires_door_intersection": True, - "render_full_region": False, - "distance_lower_bound_rule": "intersect shoulder sector with door danger band before alarming", - } - - -def reachable_region( - shoulder: np.ndarray, - upper_length: float, - forearm_length: float, - bbox: list[float], - track: EndpointTrack, - state: str = "reachable_long", - short_occlusion_frames: int = 12, - medium_occlusion_frames: int = 30, -) -> dict: - if state == "reachable_medium" and track.wrist is not None: - return medium_motion_region( - shoulder, - upper_length, - forearm_length, - bbox, - track, - short_occlusion_frames, - ) - return long_reachable_region( - shoulder, - upper_length, - forearm_length, - bbox, - track, - medium_occlusion_frames, - ) - - -def calibrated_bone_lengths( - upper_ratio: float, - forearm_ratio: float, - bbox: list[float], -) -> tuple[float, float]: - diagonal = bbox_diagonal(bbox) - height = max(bbox[3] - bbox[1], 1.0) - upper = upper_ratio * diagonal - forearm = forearm_ratio * diagonal - total = upper + forearm - # Keep only a loose sanity cap. The previous 0.55 * bbox-height cap was - # unsafe for close, clipped CMS views: a valid projected arm can occupy - # almost the full detection height. The ratios are already robust medians, - # so this cap only limits catastrophic calibration outliers. - maximum_total = 0.95 * diagonal - if total > maximum_total: - scale = maximum_total / total - upper *= scale - forearm *= scale - return upper, forearm diff --git a/orin/pipeline/pose_tracking.py b/orin/pipeline/pose_tracking.py deleted file mode 100644 index 3021674..0000000 --- a/orin/pipeline/pose_tracking.py +++ /dev/null @@ -1,304 +0,0 @@ -"""Runtime pose tracking helpers.""" -from __future__ import annotations -import math -from collections import deque -from dataclasses import dataclass, field -import numpy as np -from pose_geometry import HAND_SLICES -from pose_fusion import BODY_SIDE_INDICES, PersonTrack -from pose_occlusion import EndpointTrack, Measurement, calibrated_bone_lengths, reachable_region, solve_two_bone_elbow, temporal_recovery_measurement, update_endpoint_track - - -def bbox_iou(a: np.ndarray, b: np.ndarray) -> float: - x1 = max(float(a[0]), float(b[0])) - y1 = max(float(a[1]), float(b[1])) - x2 = min(float(a[2]), float(b[2])) - y2 = min(float(a[3]), float(b[3])) - intersection = max(x2 - x1, 0.0) * max(y2 - y1, 0.0) - union = ( - max(float(a[2] - a[0]), 0.0) * max(float(a[3] - a[1]), 0.0) - + max(float(b[2] - b[0]), 0.0) * max(float(b[3] - b[1]), 0.0) - - intersection - ) - return intersection / max(union, 1.0) - - -class BoxTracker: - def __init__(self, max_age: int = 30): - self.max_age = max_age - self.next_id = 1 - self.tracks: dict[int, dict] = {} - - def update( - self, - boxes: list[list[float]], - frame_idx: int, - image_shape: tuple[int, int], - ) -> list[int]: - height, width = image_shape - image_diagonal = math.hypot(width, height) - active_ids = [ - track_id - for track_id, track in self.tracks.items() - if frame_idx - track["last_frame"] <= self.max_age - ] - self.tracks = {track_id: self.tracks[track_id] for track_id in active_ids} - pairs = [] - for detection_idx, box in enumerate(boxes): - box_array = np.asarray(box, dtype=np.float64) - center = (box_array[:2] + box_array[2:]) * 0.5 - for track_id in active_ids: - prior = np.asarray(self.tracks[track_id]["box"], dtype=np.float64) - prior_center = (prior[:2] + prior[2:]) * 0.5 - iou = bbox_iou(box_array, prior) - center_distance = float(np.linalg.norm(center - prior_center)) / image_diagonal - if iou >= 0.05 or center_distance <= 0.18: - score = 2.0 * iou + max(0.0, 1.0 - center_distance / 0.18) - pairs.append((score, detection_idx, track_id)) - - assigned_detections = set() - assigned_tracks = set() - result: list[int | None] = [None] * len(boxes) - for _, detection_idx, track_id in sorted(pairs, reverse=True): - if detection_idx in assigned_detections or track_id in assigned_tracks: - continue - assigned_detections.add(detection_idx) - assigned_tracks.add(track_id) - result[detection_idx] = track_id - - for detection_idx, box in enumerate(boxes): - track_id = result[detection_idx] - if track_id is None: - track_id = self.next_id - self.next_id += 1 - result[detection_idx] = track_id - self.tracks[track_id] = {"box": box, "last_frame": frame_idx} - return [int(track_id) for track_id in result] - - -@dataclass -class LivePersonState: - fusion: PersonTrack = field(default_factory=PersonTrack) - endpoints: dict[str, EndpointTrack] = field( - default_factory=lambda: {"left": EndpointTrack(), "right": EndpointTrack()} - ) - bone_samples: dict[str, deque] = field( - default_factory=lambda: { - "left": deque(maxlen=120), - "right": deque(maxlen=120), - } - ) - last_frame: int = -1 - - -def bbox_diagonal(bbox: list[float]) -> float: - return max(math.hypot(bbox[2] - bbox[0], bbox[3] - bbox[1]), 1.0) - - -def update_bone_samples( - state: LivePersonState, - keypoints: np.ndarray, - metadata: dict, - bbox: list[float], -) -> None: - diagonal = bbox_diagonal(bbox) - for side, (shoulder, elbow, wrist) in BODY_SIDE_INDICES.items(): - meta = metadata[side] - if meta["limb_state"] != "consensus" or not meta["distance_eligible"]: - continue - upper = float(np.linalg.norm(keypoints[shoulder] - keypoints[elbow])) / diagonal - forearm = float(np.linalg.norm(keypoints[elbow] - keypoints[wrist])) / diagonal - if 0.04 <= upper <= 0.65 and 0.04 <= forearm <= 0.65: - state.bone_samples[side].append((upper, forearm)) - - -def current_bone_ratios(state: LivePersonState, side: str) -> tuple[float, float]: - samples = state.bone_samples[side] - if len(samples) < 10: - opposite = "right" if side == "left" else "left" - if len(state.bone_samples[opposite]) >= 10: - samples = state.bone_samples[opposite] - if not samples: - return 0.24, 0.22 - values = np.asarray(samples, dtype=np.float64) - return float(np.median(values[:, 0])), float(np.median(values[:, 1])) - - -def update_live_v2( - frame_idx: int, - bbox: list[float], - selected_keypoints: np.ndarray, - smoothed_keypoints: np.ndarray, - fusion_metadata: dict, - state: LivePersonState, - assigned_measurements: dict[str, Measurement | None] | None, - identity_assignments: dict[str, dict] | None, - short_occlusion_frames: int, - medium_occlusion_frames: int, -) -> tuple[np.ndarray, dict]: - frame_gap = 0 if state.last_frame < 0 else max(frame_idx - state.last_frame - 1, 0) - for endpoint in state.endpoints.values(): - endpoint.missed_frames += frame_gap - state.last_frame = frame_idx - update_bone_samples(state, selected_keypoints, fusion_metadata, bbox) - - output = smoothed_keypoints.copy() - output_metadata = {} - diagonal = bbox_diagonal(bbox) - for side, (shoulder_idx, elbow_idx, wrist_idx) in BODY_SIDE_INDICES.items(): - endpoint = state.endpoints[side] - meta = fusion_metadata[side] - start, end = HAND_SLICES[side] - measurement = ( - assigned_measurements.get(side) - if assigned_measurements is not None - else None - ) - if assigned_measurements is None and meta["alarm_eligible"]: - measurement = Measurement( - keypoints=selected_keypoints[start:end], - precise=bool(meta["distance_eligible"]), - provisional=not bool(meta["distance_eligible"]), - quality=float(meta.get("selected_hand_score", 0.0)), - ) - if measurement is None: - measurement = temporal_recovery_measurement( - selected_keypoints, - meta, - side, - bbox, - endpoint, - medium_occlusion_frames, - ) - returning_after_long = ( - measurement is not None - and endpoint.missed_frames > short_occlusion_frames - ) - wrist, hand, endpoint_state = update_endpoint_track( - endpoint, - measurement, - frame_idx, - short_occlusion_frames, - medium_occlusion_frames, - ) - shoulder = smoothed_keypoints[shoulder_idx] - upper_ratio, forearm_ratio = current_bone_ratios(state, side) - upper_length, forearm_length = calibrated_bone_lengths( - upper_ratio, forearm_ratio, bbox - ) - region = None - if wrist is not None and hand is not None: - reference_elbow = ( - selected_keypoints[elbow_idx] - if returning_after_long or endpoint.last_elbow is None - else endpoint.last_elbow - ) - use_ik = ( - meta["limb_state"] != "consensus" - or endpoint_state - in ("provisional", "recovered", "reassigned", "predicted_short") - ) - elbow = ( - solve_two_bone_elbow( - shoulder, - wrist, - upper_length, - forearm_length, - reference_elbow, - ) - if use_ik - else smoothed_keypoints[elbow_idx] - ) - endpoint.last_elbow = elbow.copy() - direction = wrist - shoulder - if float(np.linalg.norm(direction)) > 1e-6: - endpoint.last_direction = direction / np.linalg.norm(direction) - output[shoulder_idx] = shoulder - output[elbow_idx] = elbow - output[wrist_idx] = wrist - output[start:end] = hand - uncertainty = ( - 0.025 * diagonal - if endpoint_state == "observed" - else (0.04 + 0.015 * endpoint.missed_frames) * diagonal - ) - precise = endpoint_state == "observed" - else: - region = reachable_region( - shoulder, - upper_length, - forearm_length, - bbox, - endpoint, - endpoint_state, - short_occlusion_frames, - medium_occlusion_frames, - ) - uncertainty = region["outer_radius_px"] - precise = False - output_metadata[side] = { - "state": endpoint_state, - "precise_distance": precise, - "fast_alarm_candidate": True, - "risk_mode": ( - "precise_hand" - if endpoint_state == "observed" - else ( - "motion_corridor_intersection" - if endpoint_state == "reachable_medium" - else ( - "door_intersection_only" - if endpoint_state == "reachable_long" - else ( - "reassigned_hand" - if endpoint_state == "reassigned" - else "temporal_hand" - ) - ) - ) - ), - "requires_door_intersection": endpoint_state - in ("reachable_medium", "reachable_long"), - "identity_assignment": ( - identity_assignments.get(side) - if identity_assignments is not None - else None - ), - "missed_frames": endpoint.missed_frames, - "uncertainty_radius_px": round(float(uncertainty), 3), - "bone_lengths_px": { - "upper": round(upper_length, 3), - "forearm": round(forearm_length, 3), - }, - "reachable_region": region, - } - return output, output_metadata - - -def mask_other_people( - frame: np.ndarray, - boxes: list[list[float]], - target_idx: int, -) -> np.ndarray: - masked = frame.copy() - height, width = frame.shape[:2] - mask = np.zeros((height, width), dtype=bool) - target = boxes[target_idx] - target_area = max((target[2] - target[0]) * (target[3] - target[1]), 1.0) - for idx, bbox in enumerate(boxes): - if idx == target_idx: - continue - other_area = max((bbox[2] - bbox[0]) * (bbox[3] - bbox[1]), 1.0) - # The failure in this video is the small background person being - # absorbed by the much larger foreground person's 3:4 pose crop. - # Mask only markedly smaller people. Masking a larger occluder while - # estimating a small person removes genuine target pixels at overlap. - if other_area >= 0.5 * target_area: - continue - x1, y1, x2, y2 = (int(round(value)) for value in bbox) - x1, x2 = max(0, x1), min(width - 1, x2) - y1, y2 = max(0, y1), min(height - 1, y2) - mask[y1:y2 + 1, x1:x2 + 1] = True - - masked[mask] = (124, 116, 104) - return masked diff --git a/orin/pipeline/visualization.py b/orin/pipeline/visualization.py deleted file mode 100644 index 9530ef4..0000000 --- a/orin/pipeline/visualization.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Runtime visualization helpers.""" -from __future__ import annotations -import cv2 -import numpy as np - - -def percentile(values: list[float], p: float) -> float | None: - return round(float(np.percentile(values, p)), 3) if values else None - - -def timing_stats(values: list[float]) -> dict: - return { - "count": len(values), - "total": round(float(np.sum(values)), 3) if values else 0.0, - "mean": round(float(np.mean(values)), 3) if values else None, - "median": percentile(values, 50), - "p95": percentile(values, 95), - } - - -def visualization_skeletons(record: dict) -> list[dict]: - """Return the exact fused keypoints and metadata consumed by draw_fused.""" - - boxes = record.get("boxes", []) - track_ids = [int(value) for value in record.get("track_ids", [])] - keypoints = record.get("temporal_selected_keypoints", []) - metadata = record.get("temporal_metadata", []) - if not (len(boxes) == len(keypoints) == len(metadata)): - return [] - return [ - { - "track_id": track_ids[position] if position < len(track_ids) else position + 1, - "person_index": position, - "layout": "coco_wholebody_133", - "keypoints": person_keypoints, - "metadata": metadata[position], - } - for position, person_keypoints in enumerate(keypoints) - ] - - -def render_frame( - frame: np.ndarray, - record: dict, - frame_risk: dict, - door_mask: np.ndarray | None, - door_assignments: list[dict], -) -> np.ndarray: - canvas = frame.copy() - boxes = record.get("boxes", []) - keypoints = record.get("temporal_selected_keypoints", []) - metadata = record.get("temporal_metadata", []) - if len(boxes) == len(keypoints) == len(metadata) and boxes: - from pose_fusion import draw_fused - - canvas = draw_fused(canvas, boxes, keypoints, metadata, "YOLO + RTMW risk") - else: - for position, box in enumerate(boxes): - x1, y1, x2, y2 = [int(round(float(value))) for value in box[:4]] - cv2.rectangle(canvas, (x1, y1), (x2, y2), (0, 220, 255), 2) - cv2.putText( - canvas, - f"P{position + 1}", - (x1 + 4, max(22, y1 + 24)), - cv2.FONT_HERSHEY_SIMPLEX, - 0.7, - (0, 220, 255), - 2, - cv2.LINE_AA, - ) - zone_colors = { - "front": (0, 220, 255), - "rear": (255, 180, 0), - "transition": (0, 140, 255), - "unassigned": (150, 150, 150), - } - for assignment in door_assignments: - position = int(assignment["person_index"]) - if not 0 <= position < len(boxes): - continue - box = boxes[position] - x1, y1 = int(round(float(box[0]))), int(round(float(box[1]))) - zone = str(assignment["door_zone"]) - cv2.putText( - canvas, - f"T{assignment['track_id']} {zone.upper()}", - (max(0, x1 + 4), max(24, y1 - 8)), - cv2.FONT_HERSHEY_SIMPLEX, - 0.68, - zone_colors.get(zone, (160, 160, 160)), - 2, - cv2.LINE_AA, - ) - if door_mask is not None and np.any(door_mask): - overlay = np.zeros_like(canvas) - overlay[door_mask > 0] = (255, 180, 0) - canvas = cv2.addWeighted(canvas, 1.0, overlay, 0.22, 0) - - score = frame_risk.get("risk_score") - score_text = "N/A" if score is None else f"{float(score):.3f}" - level = str(frame_risk.get("risk_level", "unavailable")) - color = (0, 0, 255) if level == "danger" else (0, 180, 255) - if level in ("clear", "unavailable", "inactive", "unknown"): - color = (40, 210, 80) if level == "clear" else (160, 160, 160) - cv2.rectangle(canvas, (12, 55), (490, 95), (0, 0, 0), -1) - cv2.putText( - canvas, - f"{frame_risk.get('risk_type', 'DOOR_RISK')} score={score_text} level={level}", - (24, 83), - cv2.FONT_HERSHEY_SIMPLEX, - 0.68, - color, - 2, - cv2.LINE_AA, - ) - return canvas diff --git a/orin/run.sh b/orin/run.sh index 7f8c795..b616076 100755 --- a/orin/run.sh +++ b/orin/run.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash set -euo pipefail root="$(cd "$(dirname "$0")" && pwd)" -export OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=2 export LD_LIBRARY_PATH="$root/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" -export PYTHONDONTWRITEBYTECODE=1 -exec "${PYTHON:-python3}" -u "$root/run_camera.py" "$@" +exec "$root/build/camera_risk" --config "$root/config/camera.json" "$@" diff --git a/orin/run_camera.py b/orin/run_camera.py deleted file mode 100644 index 3d53b6d..0000000 --- a/orin/run_camera.py +++ /dev/null @@ -1,300 +0,0 @@ -#!/usr/bin/env python3 -"""Live baseline V4L2/CAN capture + native TensorRT + CMS v9.10 risk.""" -from __future__ import annotations -import argparse -from collections import Counter, deque -from contextlib import ExitStack -import copy -import json -import signal -import sys -import threading -import time -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from pathlib import Path -import cv2 -import numpy as np -ROOT=Path(__file__).resolve().parent -sys.path.insert(0,str(ROOT/'pipeline')) -from native_backend import NativeBackend -from pose_engine import NativePoseEngine -from latest_result import LatestResult, result_to_dict -from door_mask_risk import DoorMaskSequence, DoorRiskEvaluator -from door_zone_risk import DoorZoneConfig, DoorZoneAssigner, build_door_risks -from visualization import render_frame, visualization_skeletons, timing_stats - - -def select_active_door(mode, snapshot, side='left', timeout_ms=1000): - if mode!='can': return mode, True - p=snapshot['positions'];offset=0 if side=='left' else 1 - if snapshot['age_ms']<0 or snapshot['age_ms']>timeout_ms or min(p[offset],p[offset+2])<0: - return 'none',False - front,rear=p[offset]==0,p[offset+2]==0 - return ('both' if front and rear else 'front' if front else 'rear' if rear else 'none'),True - - -def gate_risks(raw, assignments, active, can_valid): - doors,risk=build_door_risks(raw,assignments,active) - if not can_valid: - for door,item in doors.items(): - item.update(risk_type=door.upper()+'_UNKNOWN',risk_level='unknown',risk_score=None,decision='CAN_UNAVAILABLE_OR_STALE') - risk=dict(risk,risk_type='CAN_UNKNOWN',risk_level='unknown',risk_score=None,decision='CAN_UNAVAILABLE_OR_STALE') - return doors,risk - - -class JsonlWriter: - """Bound the disk usage of continuous camera runs (current + two backups).""" - def __init__(self,path,max_bytes): - self.path=path;self.max_bytes=max_bytes;self.size=0 - self.handle=path.open('w',encoding='utf-8',buffering=1) - def __enter__(self):return self - def __exit__(self,*args):self.close() - def close(self):self.handle.close() - def write(self,text): - size=len(text.encode('utf-8')) - if self.max_bytes and self.size and self.size+size>self.max_bytes: - self.handle.close() - backup=self.path.with_suffix(self.path.suffix+'.1') - if backup.exists():backup.replace(self.path.with_suffix(self.path.suffix+'.2')) - self.path.replace(backup) - self.handle=self.path.open('w',encoding='utf-8',buffering=1);self.size=0 - self.handle.write(text);self.size+=size - - -def render_output(frame, record, risk, mask, assignments, status): - canvas = render_frame(frame, record, risk, mask, assignments) - cv2.putText(canvas,status,(24,frame.shape[0]-30),cv2.FONT_HERSHEY_SIMPLEX,.8,(0,180,255),2) - return canvas - - -class Preview: - def __init__(self,host,port,results): - self.jpeg=None;self.results=results;self.lock=threading.Lock() - self.pending=threading.Condition();self.packet=None;self.stopped=False - outer=self - class Handler(BaseHTTPRequestHandler): - def do_GET(self): - with outer.lock: - if self.path.split('?')[0]=='/frame.jpg': payload=outer.jpeg or b'';mime='image/jpeg' - elif self.path=='/result.json':payload=json.dumps(result_to_dict(outer.results.load()) or {},ensure_ascii=False,allow_nan=False).encode();mime='application/json' - else: - payload=b'''Vision anti-pinch 0923

    Vision anti-pinch 0923 / Live camera

    ''';mime='text/html; charset=utf-8' - self.send_response(200 if payload else 503);self.send_header('Content-Type',mime);self.send_header('Cache-Control','no-store');self.send_header('Content-Length',str(len(payload)));self.end_headers() - try:self.wfile.write(payload) - except (BrokenPipeError,ConnectionResetError):pass - def log_message(self,*args):pass - self.server=ThreadingHTTPServer((host,port),Handler) - self.thread=threading.Thread(target=self.server.serve_forever,daemon=True);self.thread.start() - self.renderer=threading.Thread(target=self._render_loop,name='risk-preview',daemon=True) - self.renderer.start() - def submit(self, frame, record, risk, mask, assignments, status): - # All inputs belong to a finished frame; inference never modifies them - # after submission. Retain only one pending frame, replacing older work. - with self.pending: - if not self.stopped: - self.packet=(frame,record,risk,mask,assignments,status) - self.pending.notify() - - def _render_loop(self): - try: - while True: - with self.pending: - self.pending.wait_for(lambda:self.packet is not None or self.stopped) - if self.stopped:return - packet=self.packet;self.packet=None - self.update(render_output(*packet)) - except Exception as exc: - with self.pending:self.stopped=True;self.packet=None - with self.lock:self.jpeg=None - print('Visualization disabled; inference continues: '+str(exc),file=sys.stderr,flush=True) - - def update(self,image): - w=960;h=round(image.shape[0]*w/image.shape[1]);image=cv2.resize(image,(w,h)) - ok,jpeg=cv2.imencode('.jpg',image,[cv2.IMWRITE_JPEG_QUALITY,80]) - if ok: - with self.lock:self.jpeg=jpeg.tobytes() - def close(self): - with self.pending:self.stopped=True;self.packet=None;self.pending.notify_all() - self.renderer.join() - self.server.shutdown();self.server.server_close();self.thread.join() - - -def parse_args(argv=None): - p=argparse.ArgumentParser(description=__doc__) - p.add_argument('--video',type=Path,help='Diagnostic video replay through the same TensorRT/risk chain');p.add_argument('--camera',default='/dev/video5');p.add_argument('--width',type=int,default=1920);p.add_argument('--height',type=int,default=1536);p.add_argument('--fps',type=int,default=30) - p.add_argument('--engines',type=Path,default=ROOT/'orin/models/engines' if (ROOT/'orin').is_dir() else ROOT/'models/engines') - p.add_argument('--constant-door-mask',type=Path,default=ROOT/'annotations/constant_door_mask.json') - p.add_argument('--door-zone-config',type=Path,default=ROOT/'annotations/door_zone_multifeature.json') - p.add_argument('--active-door',choices=['can','front','rear','both','none'],default='can') - p.add_argument('--can-side',choices=['left','right'],default='left');p.add_argument('--can-timeout-ms',type=float,default=1000);p.add_argument('--no-can',action='store_true') - p.add_argument('--max-people',type=int,default=3);p.add_argument('--det-conf',type=float,default=.30) - p.add_argument('--max-frames',type=int,default=0);p.add_argument('--duration',type=float,default=0) - p.add_argument('--jsonl-max-mb',type=float,default=32,help='Rotate each JSONL at this size; keep two backups; 0 disables rotation');p.add_argument('--output',type=Path,default=ROOT/'outputs'/time.strftime('%Y%m%d-%H%M%S')) - p.add_argument('--visualize',action='store_true',help='Enable optional HTTP visualization; disabled by default') - p.add_argument('--save-results',action='store_true',help='Save per-frame JSONL and periodic latest.json') - p.add_argument('--save-snapshots',action='store_true',help='Render and save latest.jpg every 10 seconds') - p.add_argument('--preview-port',type=int,default=8765);p.add_argument('--preview-host',default='0.0.0.0');p.add_argument('--preview-every',type=int,default=3) - p.add_argument('--write-video',action='store_true');p.add_argument('--save-pose-records',action='store_true');p.add_argument('--calibration-verified',action='store_true') - a=p.parse_args(argv) - if not 1<=a.max_people<=16 or not 0=a.max_frames) or (a.duration and time.monotonic()-run_started>=a.duration):break - if video: - ok,frame=video.read() - if not ok:break - capture={'sequence':count,'capture_monotonic_ms':time.monotonic()*1000,'dropped_before_inference':0,'latency_clock':'video_decode_complete'} - else:frame,capture=native.read() - if frame is None:break - tick=time.monotonic();record=pose.infer(frame,count);pose_ms.append((time.monotonic()-tick)*1000) - rt=time.monotonic();snapshot=native.can_snapshot();active,valid=select_active_door(a.active_door,snapshot,a.can_side,a.can_timeout_ms) - assignments=assigner.update(count,record,(a.height,a.width));raw,mask=evaluator.evaluate(count,record,(a.height,a.width)) - doors,risk=gate_risks(raw,assignments,active,valid);risk_ms.append((time.monotonic()-rt)*1000) - result={'frame_index':count,'track_ids':record['track_ids'],'boxes':record['boxes'],'skeletons':visualization_skeletons(record),'active_door':active,'door_assignments':assignments,'door_risks':doors,'risk':risk,'ungated_risk':raw,'capture':capture,'can':dict(snapshot,valid=valid,source='hardware' if a.active_door=='can' else 'simulation',side=a.can_side),'calibration_verified':a.calibration_verified} - result['capture_to_result_ms']=round(time.monotonic()*1000-capture['capture_monotonic_ms'],3) - results.publish(result) - if out:out.write(json.dumps(result,ensure_ascii=False,allow_nan=False)+'\n') - if pose_out:pose_out.write(json.dumps(record,ensure_ascii=False,allow_nan=False)+'\n') - now=time.monotonic() - snapshot_due=now-last_snapshot>=10 - if a.save_results and snapshot_due: - (a.output/'latest.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)) - status=('CAN LIVE' if a.active_door=='can' else 'CAN SIMULATION: '+active)+' | '+('CALIBRATED' if a.calibration_verified else 'CALIBRATION UNVERIFIED') - if preview and count%a.preview_every==0: - preview.submit(frame,record,risk,mask,assignments,status) - if writer or (a.save_snapshots and snapshot_due): - canvas=render_output(frame,record,risk,mask,assignments,status) - if writer:writer.write(canvas) - if a.save_snapshots and snapshot_due: - cv2.imwrite(str(a.output/'latest.jpg'),canvas) - if snapshot_due:last_snapshot=now - count+=1;dropped+=capture['dropped_before_inference'];levels[risk['risk_level']]+=1;ages.append(result['capture_to_result_ms']);latencies.append((time.monotonic()-tick)*1000) - if now-last_report>=5: - print(json.dumps({'frames':count,'fps':round(count/(now-run_started),2),'people':len(record['boxes']),'risk':risk['risk_type'],'latency_ms':round(float(np.mean(latencies)),1),'capture_age_ms':round(float(np.mean(ages)),1),'can_valid':valid}),flush=True);last_report=now - finally: - if pose_out:pose_out.close() - except Exception as exc: - error=str(exc);raise - finally: - ended=time.monotonic() - if video:video.release() - if native:native.close() - if writer:writer.release() - if preview:preview.close() - elapsed=ended-(run_started or started) - summary={'mode':'baseline_v4l2_can_native_trt_cms_v9_10','camera':a.camera if not a.video else None,'video':str(a.video) if a.video else None,'frames':count,'elapsed_seconds':round(elapsed,3),'throughput_fps':round(count/max(elapsed,.001),3),'dropped_latest_frames':dropped,'risk_levels':dict(levels),'active_door_mode':a.active_door,'can_side':a.can_side,'calibration_verified':a.calibration_verified,'visualize_requested':a.visualize,'save_results':a.save_results,'save_snapshots':a.save_snapshots,'latency_window_frames':3000,'latency_ms':{'total':timing_stats(list(latencies)),'pose':timing_stats(list(pose_ms)),'risk':timing_stats(list(risk_ms)),'capture_to_result':timing_stats(list(ages))},'error':error} - if summary_sink is not None:summary_sink.update(summary) - if save_any:(a.output/'summary.json').write_text(json.dumps(summary,ensure_ascii=False,indent=2)) - print(json.dumps(summary,ensure_ascii=False),flush=True) - -class CameraPipeline: - """One lifecycle per instance, with the 0920 start/latest/stop/join pattern. - - The worker owns inference. Downstream threads read the latest immutable - snapshot directly, without JSON files, HTTP, callbacks or a result queue. - """ - def __init__(self, args=None): - self.args = copy.deepcopy(args if args is not None else parse_args([])) - self._results = LatestResult() - self._stop = threading.Event() - self._lifecycle_lock = threading.Lock() - self._thread = None - self._error = None - self.summary = {} - - @property - def latest_result(self): - """The latest complete result variable, or None before the first frame.""" - return self._results.load() - - def latest(self): - return self.latest_result - - def wait_for_result(self, after_frame_index=-1, timeout=None): - return self._results.wait(after_frame_index, timeout) - - def start(self): - with self._lifecycle_lock: - if self._thread is not None: - raise RuntimeError('CameraPipeline can only be started once') - self._thread = threading.Thread(target=self._run, name='vision-risk') - self._thread.start() - return self - - def _run(self): - try: - run_pipeline(self.args, self._results, self._stop, self.summary) - except BaseException as exc: - self._error = exc - finally: - self._results.close() - - def running(self): - return self._thread is not None and self._thread.is_alive() - - def request_stop(self): - self._stop.set() - - def join(self, timeout=None): - if self._thread is not None: - self._thread.join(timeout) - if not self._thread.is_alive() and self._error is not None: - raise self._error - - def __enter__(self): - return self.start() - - def __exit__(self, *args): - self.request_stop() - self.join() - - -def main(): - pipeline=CameraPipeline(parse_args()) - for sig in (signal.SIGINT,signal.SIGTERM): - signal.signal(sig,lambda *_:pipeline.request_stop()) - pipeline.start() - # Periodic joins allow Python to deliver stop signals on the main thread. - while pipeline.running(): - pipeline.join(.2) - pipeline.join() - - -if __name__=='__main__':main() diff --git a/pipeline/door_mask_risk.py b/pipeline/door_mask_risk.py deleted file mode 100644 index 49eeec5..0000000 --- a/pipeline/door_mask_risk.py +++ /dev/null @@ -1,488 +0,0 @@ -"""Per-frame door-mask loading and YOLO/RTMW-only 2-D risk scoring.""" - -from __future__ import annotations - -import json -import math -import re -import time -from collections import Counter -from dataclasses import dataclass -from pathlib import Path - -import cv2 -import numpy as np - - -RASTER_SUFFIXES = {".png", ".bmp", ".tif", ".tiff"} -DEFAULT_DOOR_LABELS = ("door", "车门") - - -@dataclass -class LoadedDoorMask: - status: str - annotation_index: int - path: Path | None - mask: np.ndarray | None - source_format: str | None - - def metadata(self) -> dict: - return { - "status": self.status, - "annotation_index": self.annotation_index, - "path": str(self.path.resolve()) if self.path is not None else None, - "format": self.source_format, - "area_px": int(np.count_nonzero(self.mask)) if self.mask is not None else 0, - } - - -def _frame_index_from_path(path: Path) -> int | None: - matches = re.findall(r"\d+", path.stem) - return int(matches[-1]) if matches else None - - -class DoorMaskSequence: - """Index a directory of X-AnyLabeling JSON polygons or raster masks.""" - - def __init__( - self, - directory: Path | None = None, - mask_format: str = "auto", - labels: tuple[str, ...] = DEFAULT_DOOR_LABELS, - frame_offset: int = 0, - require_every_frame: bool = False, - constant_mask: Path | None = None, - ): - if (directory is None) == (constant_mask is None): - raise ValueError("Provide exactly one of directory or constant_mask") - self.constant_path = ( - Path(constant_mask).expanduser().resolve() - if constant_mask is not None - else None - ) - self.directory = ( - self.constant_path.parent if self.constant_path is not None else Path(directory) - ) - if self.constant_path is not None and not self.constant_path.is_file(): - raise ValueError(f"Constant door mask does not exist: {self.constant_path}") - if self.constant_path is None and not self.directory.is_dir(): - raise ValueError(f"Door mask directory does not exist: {self.directory}") - self.labels = {label.casefold() for label in labels} - self.frame_offset = int(frame_offset) - self.require_every_frame = require_every_frame - - if self.constant_path is not None: - if mask_format == "auto": - selected_format = ( - "xanylabeling" - if self.constant_path.suffix.lower() == ".json" - else "raster" - ) - else: - selected_format = mask_format - self.source_format = selected_format - self.paths = {0: self.constant_path} - return - - json_paths = sorted(self.directory.rglob("*.json")) - raster_paths = sorted( - path - for path in self.directory.rglob("*") - if path.is_file() and path.suffix.lower() in RASTER_SUFFIXES - ) - if mask_format == "auto": - selected_format = "xanylabeling" if json_paths else "raster" - else: - selected_format = mask_format - candidates = json_paths if selected_format == "xanylabeling" else raster_paths - if not candidates: - expected = "JSON" if selected_format == "xanylabeling" else "PNG/BMP/TIFF" - raise ValueError(f"No {expected} door masks found in {self.directory}") - - self.source_format = selected_format - self.paths: dict[int, Path] = {} - for path in candidates: - index = _frame_index_from_path(path) - if index is None and selected_format == "xanylabeling": - data = json.loads(path.read_text(encoding="utf-8-sig")) - explicit = data.get("frame_index", data.get("frameIndex")) - index = int(explicit) if explicit is not None else None - if index is None: - continue - if index in self.paths: - raise ValueError( - f"Multiple door masks map to annotation frame {index}: " - f"{self.paths[index]} and {path}" - ) - self.paths[index] = path - if not self.paths: - raise ValueError( - "Door mask filenames need a frame number, for example 000000.json" - ) - - def load(self, video_frame_index: int, frame_shape: tuple[int, int]) -> LoadedDoorMask: - if self.constant_path is not None: - annotation_index = video_frame_index - path = self.constant_path - else: - annotation_index = video_frame_index + self.frame_offset - path = self.paths.get(annotation_index) - if path is None: - if self.require_every_frame: - raise RuntimeError( - f"No door mask for video frame {video_frame_index} " - f"(expected annotation index {annotation_index})" - ) - return LoadedDoorMask("missing", annotation_index, None, None, self.source_format) - - if self.source_format == "xanylabeling": - mask = self._load_xanylabeling(path, frame_shape) - else: - mask = self._load_raster(path, frame_shape) - status = "loaded" if np.any(mask) else "empty" - return LoadedDoorMask(status, annotation_index, path, mask, self.source_format) - - @staticmethod - def _load_raster(path: Path, frame_shape: tuple[int, int]) -> np.ndarray: - image = cv2.imread(str(path), cv2.IMREAD_UNCHANGED) - if image is None: - raise RuntimeError(f"Cannot read door mask: {path}") - if image.ndim == 3: - image = np.max(image[:, :, :3], axis=2) - height, width = frame_shape - if image.shape != (height, width): - image = cv2.resize(image, (width, height), interpolation=cv2.INTER_NEAREST) - return image > 0 - - def _load_xanylabeling( - self, path: Path, frame_shape: tuple[int, int] - ) -> np.ndarray: - data = json.loads(path.read_text(encoding="utf-8-sig")) - height, width = frame_shape - source_width = float(data.get("imageWidth") or width) - source_height = float(data.get("imageHeight") or height) - scale = np.asarray( - [width / max(source_width, 1.0), height / max(source_height, 1.0)], - dtype=np.float32, - ) - mask = np.zeros((height, width), dtype=np.uint8) - for shape in data.get("shapes", []): - if str(shape.get("label", "")).casefold() not in self.labels: - continue - points = np.asarray(shape.get("points", []), dtype=np.float32).reshape(-1, 2) - if len(points) < 2: - continue - points = np.rint(points * scale).astype(np.int32) - shape_type = str(shape.get("shape_type", "polygon")).lower() - if shape_type == "rectangle" and len(points) >= 2: - x1, y1 = points[0] - x2, y2 = points[1] - cv2.rectangle(mask, (x1, y1), (x2, y2), 1, -1) - elif shape_type == "circle" and len(points) >= 2: - radius = int(round(float(np.linalg.norm(points[1] - points[0])))) - cv2.circle(mask, tuple(points[0]), radius, 1, -1) - elif len(points) >= 3: - cv2.fillPoly(mask, [points], 1) - return mask > 0 - - -def _fit_mask(mask: np.ndarray, frame_shape: tuple[int, int]) -> np.ndarray: - height, width = frame_shape - result = np.asarray(mask) > 0 - if result.shape != (height, width): - result = cv2.resize( - result.astype(np.uint8), (width, height), interpolation=cv2.INTER_NEAREST - ) > 0 - return result - - -def _box_mask(box: list[float] | None, frame_shape: tuple[int, int]) -> np.ndarray | None: - if box is None or len(box) < 4: - return None - height, width = frame_shape - x1, y1, x2, y2 = [int(round(float(value))) for value in box[:4]] - x1, x2 = sorted((max(0, min(width - 1, x1)), max(0, min(width - 1, x2)))) - y1, y2 = sorted((max(0, min(height - 1, y1)), max(0, min(height - 1, y2)))) - if x2 <= x1 or y2 <= y1: - return None - mask = np.zeros((height, width), dtype=bool) - mask[y1 : y2 + 1, x1 : x2 + 1] = True - return mask - - -def _hand_mask(points: list, frame_shape: tuple[int, int]) -> np.ndarray | None: - hand = np.asarray(points, dtype=np.float32).reshape(-1, 2) - if len(hand) < 3 or not np.isfinite(hand).all(): - return None - hull = cv2.convexHull(np.rint(hand).astype(np.int32)) - mask = np.zeros(frame_shape, dtype=np.uint8) - cv2.fillConvexPoly(mask, hull, 1) - return mask > 0 if np.any(mask) else None - - -def _decision(distance_relative: float, overlap_px: int, warning: float, release: float) -> tuple[str, str]: - if overlap_px > 0: - return "danger", "DANGER_INTERSECTION" - if distance_relative <= warning: - return "warning", "WARNING_NEAR" - if distance_relative <= release: - return "watch", "WATCH" - return "clear", "CLEAR" - - -class DoorRiskEvaluator: - """Score RTMW hand regions, with one YOLO body-box fallback per person.""" - - def __init__( - self, - sequence: DoorMaskSequence, - warning_relative: float = 0.30, - release_relative: float = 0.45, - ): - if warning_relative < 0 or release_relative <= warning_relative: - raise ValueError("Risk thresholds require 0 <= warning < release") - self.sequence = sequence - self.warning_relative = float(warning_relative) - self.release_relative = float(release_relative) - self.mask_status_counts: Counter = Counter() - self.level_counts: Counter = Counter() - self.source_counts: Counter = Counter() - self.maximum_score = 0.0 - self.timing_seconds: Counter = Counter() - self.timing_counts: Counter = Counter() - self._constant_cache_shape: tuple[int, int] | None = None - self._constant_loaded: LoadedDoorMask | None = None - self._constant_distance_map: np.ndarray | None = None - self.distance_map_cache_builds = 0 - - def _record_timing(self, name: str, started: float) -> None: - self.timing_seconds[name] += time.perf_counter() - started - self.timing_counts[name] += 1 - - def _measure_region( - self, - region: np.ndarray, - door_mask: np.ndarray, - distance_map: np.ndarray, - source: str, - track_id: int, - side: str, - state: str | None, - ) -> dict: - area_px = int(np.count_nonzero(region)) - overlap_px = int(np.count_nonzero(region & door_mask)) - distances = distance_map[region] - distance_px = float(np.min(distances)) if len(distances) else math.inf - scale_px = max(math.sqrt(area_px), 1.0) - distance_relative = distance_px / scale_px - score = float(np.clip(1.0 - distance_relative / self.release_relative, 0.0, 1.0)) - if overlap_px: - score = 1.0 - level, decision = _decision( - distance_relative, - overlap_px, - self.warning_relative, - self.release_relative, - ) - result = { - "status": "calculated", - "risk_score": round(score, 5), - "risk_level": level, - "decision": decision, - "source": source, - "track_id": int(track_id), - "side": side, - "state": state, - "region_area_px": area_px, - "door_overlap_px": overlap_px, - "region_overlap_ratio": round(overlap_px / max(area_px, 1), 6), - "min_distance_px": round(distance_px, 3), - "region_scale_px": round(scale_px, 3), - "distance_relative": round(distance_relative, 6), - } - self.level_counts[level] += 1 - self.source_counts[source] += 1 - self.maximum_score = max(self.maximum_score, score) - return result - - @staticmethod - def _unavailable(source: str, track_id: int, side: str, status: str) -> dict: - return { - "status": status, - "risk_score": None, - "risk_level": "unavailable", - "decision": "EVIDENCE_UNAVAILABLE", - "source": source, - "track_id": int(track_id), - "side": side, - } - - def evaluate( - self, - frame_index: int, - record: dict, - frame_shape: tuple[int, int], - ) -> tuple[dict, np.ndarray | None]: - total_started = time.perf_counter() - stage_started = time.perf_counter() - use_constant_cache = self.sequence.constant_path is not None - if use_constant_cache and self._constant_cache_shape == frame_shape: - cached = self._constant_loaded - assert cached is not None - loaded = LoadedDoorMask( - cached.status, - frame_index, - cached.path, - cached.mask, - cached.source_format, - ) - else: - loaded = self.sequence.load(frame_index, frame_shape) - if use_constant_cache: - self._constant_cache_shape = frame_shape - self._constant_loaded = loaded - self._constant_distance_map = None - self._record_timing("door_mask_load", stage_started) - self.mask_status_counts[loaded.status] += 1 - mask_ready = loaded.status == "loaded" and loaded.mask is not None - door_mask = loaded.mask if mask_ready else None - distance_map = None - if door_mask is not None and use_constant_cache and self._constant_distance_map is not None: - distance_map = self._constant_distance_map - elif door_mask is not None: - stage_started = time.perf_counter() - distance_map = cv2.distanceTransform( - (~door_mask).astype(np.uint8), cv2.DIST_L2, cv2.DIST_MASK_PRECISE - ) - self._record_timing("distance_transform", stage_started) - if use_constant_cache: - self._constant_distance_map = distance_map - self.distance_map_cache_builds += 1 - - stage_started = time.perf_counter() - regions: list[tuple[dict, np.ndarray, str, int, str, str | None]] = [] - track_ids = [int(value) for value in record.get("track_ids", [])] - person_positions_with_hands: set[int] = set() - for hand in record.get("global_hands", []): - position = int(hand.get("person_id", 0)) - 1 - track_id = track_ids[position] if 0 <= position < len(track_ids) else position + 1 - side = str(hand.get("side", "unknown")) - region = _hand_mask(hand.get("keypoints", []), frame_shape) - if region is None: - continue - person_positions_with_hands.add(position) - regions.append( - ( - hand, - region, - "RTMW_HAND_KEYPOINTS", - track_id, - side, - str(hand.get("state", "unknown")), - ) - ) - - boxes = record.get("boxes", []) - body_fallbacks = [] - for position, box in enumerate(boxes): - if position in person_positions_with_hands: - continue - region = _box_mask(box, frame_shape) - if region is None: - continue - track_id = track_ids[position] if position < len(track_ids) else position + 1 - fallback = { - "track_id": track_id, - "side": "body", - "state": "rtmw_hand_unavailable", - "body_zone_box": box, - } - body_fallbacks.append(fallback) - regions.append( - ( - fallback, - region, - "YOLO_BODY_BOX_FALLBACK", - track_id, - "body", - "rtmw_hand_unavailable", - ) - ) - record["body_fallbacks"] = body_fallbacks - self._record_timing("region_build", stage_started) - - stage_started = time.perf_counter() - measurements = [] - unavailable_status = f"door_mask_{loaded.status}" - for owner, region, source, track_id, side, state in regions: - if door_mask is None or distance_map is None: - measurement = self._unavailable( - source, track_id, side, unavailable_status - ) - else: - measurement = self._measure_region( - region, door_mask, distance_map, source, track_id, side, state - ) - owner["risk"] = measurement - measurements.append(measurement) - self._record_timing("region_measure", stage_started) - - calculated = [item for item in measurements if item["risk_score"] is not None] - nearest = max(calculated, key=lambda item: item["risk_score"]) if calculated else None - if loaded.status != "loaded": - status = unavailable_status - elif not calculated: - status = "person_region_missing" - else: - status = "calculated" - frame_risk = { - "status": status, - "risk_score": nearest["risk_score"] if nearest else None, - "risk_level": nearest["risk_level"] if nearest else "unavailable", - "decision": nearest["decision"] if nearest else "EVIDENCE_UNAVAILABLE", - "source": nearest["source"] if nearest else None, - "track_id": nearest["track_id"] if nearest else None, - "side": nearest["side"] if nearest else None, - "door_mask": loaded.metadata(), - "warning_distance_relative": self.warning_relative, - "release_distance_relative": self.release_relative, - "regions": measurements, - } - self._record_timing("risk_total", total_started) - return frame_risk, loaded.mask - - def summary(self) -> dict: - return { - "mask_directory": str(self.sequence.directory.resolve()), - "constant_mask": ( - str(self.sequence.constant_path) - if self.sequence.constant_path is not None - else None - ), - "constant_for_all_frames": self.sequence.constant_path is not None, - "mask_format": self.sequence.source_format, - "door_labels": sorted(self.sequence.labels), - "frame_offset": self.sequence.frame_offset, - "warning_distance_relative": self.warning_relative, - "release_distance_relative": self.release_relative, - "mask_status_counts": dict(self.mask_status_counts), - "risk_level_counts": dict(self.level_counts), - "source_counts": dict(self.source_counts), - "maximum_risk_score": round(self.maximum_score, 5), - "constant_distance_map_cached": self.sequence.constant_path is not None, - "distance_map_cache_builds": self.distance_map_cache_builds, - "timing_ms": { - name: { - "count": int(self.timing_counts[name]), - "total": round(float(seconds) * 1000.0, 3), - "mean": round( - float(seconds) * 1000.0 - / max(int(self.timing_counts[name]), 1), - 3, - ), - } - for name, seconds in self.timing_seconds.items() - }, - "score_semantics": "2-D image overlap/proximity research score; not millimeters or safety certification", - "region_policy": "RTMW alarm-eligible hand hulls; YOLO body box only when a person has no usable RTMW hand region", - } diff --git a/pipeline/door_zone_risk.py b/pipeline/door_zone_risk.py deleted file mode 100644 index 972cfb7..0000000 --- a/pipeline/door_zone_risk.py +++ /dev/null @@ -1,358 +0,0 @@ -"""Multi-feature person-to-door assignment and active-CAN risk gating.""" - -from __future__ import annotations - -import json -import math -from collections import Counter, defaultdict, deque -from dataclasses import dataclass -from pathlib import Path -from statistics import median - - -DOORS = ("front", "rear") -POSE_FEATURES = { - "shoulder_center_x", - "shoulder_center_y", - "shoulder_width", - "torso_height", - "hip_center_y", -} - - -@dataclass(frozen=True) -class FeatureCalibration: - rear: float - front: float - weight: float - - def frontness(self, value: float) -> float: - span = self.front - self.rear - if span == 0: - return 0.5 - return min(1.0, max(0.0, (value - self.rear) / span)) - - -@dataclass(frozen=True) -class DoorZoneConfig: - features: dict[str, FeatureCalibration] - front_probability_min: float - rear_probability_max: float - minimum_person_height: float - minimum_pose_features: int - history_frames: int - stale_track_frames: int - source_path: Path | None = None - - @classmethod - def load(cls, path: Path) -> "DoorZoneConfig": - source = Path(path).expanduser().resolve() - data = json.loads(source.read_text(encoding="utf-8-sig")) - features = { - name: FeatureCalibration( - rear=float(values["rear"]), - front=float(values["front"]), - weight=float(values["weight"]), - ) - for name, values in data["features"].items() - } - return cls( - features=features, - front_probability_min=float(data.get("front_probability_min", 0.72)), - rear_probability_max=float(data.get("rear_probability_max", 0.28)), - minimum_person_height=float(data.get("minimum_person_height", 0.40)), - minimum_pose_features=int(data.get("minimum_pose_features", 4)), - history_frames=int(data.get("history_frames", 5)), - stale_track_frames=int(data.get("stale_track_frames", 120)), - source_path=source, - ) - - def classify(self, probability: float) -> str: - if probability >= self.front_probability_min: - return "front" - if probability <= self.rear_probability_max: - return "rear" - return "transition" - - -def _point(keypoints: list, index: int) -> tuple[float, float] | None: - if index >= len(keypoints) or len(keypoints[index]) < 2: - return None - x, y = float(keypoints[index][0]), float(keypoints[index][1]) - return (x, y) if math.isfinite(x) and math.isfinite(y) else None - - -def extract_person_features( - record: dict, - position: int, - frame_shape: tuple[int, int], -) -> tuple[dict[str, float], int]: - """Extract fixed-camera pose features, with box geometry as low-weight fallback.""" - - height, width = frame_shape - box = record.get("boxes", [])[position] - x1, y1, x2, y2 = [float(value) for value in box[:4]] - features = { - "box_center_x": ((x1 + x2) * 0.5) / max(width, 1), - "box_height": (y2 - y1) / max(height, 1), - } - - all_keypoints = record.get("temporal_selected_keypoints", []) - if position >= len(all_keypoints): - return features, 0 - keypoints = all_keypoints[position] - left_shoulder = _point(keypoints, 5) - right_shoulder = _point(keypoints, 6) - left_hip = _point(keypoints, 11) - right_hip = _point(keypoints, 12) - if None in (left_shoulder, right_shoulder, left_hip, right_hip): - return features, 0 - - shoulder_x = (left_shoulder[0] + right_shoulder[0]) * 0.5 - shoulder_y = (left_shoulder[1] + right_shoulder[1]) * 0.5 - hip_y = (left_hip[1] + right_hip[1]) * 0.5 - features.update( - { - "shoulder_center_x": shoulder_x / max(width, 1), - "shoulder_center_y": shoulder_y / max(height, 1), - "shoulder_width": math.hypot( - left_shoulder[0] - right_shoulder[0], - left_shoulder[1] - right_shoulder[1], - ) - / max(width, 1), - "torso_height": abs(hip_y - shoulder_y) / max(height, 1), - "hip_center_y": hip_y / max(height, 1), - } - ) - return features, len(POSE_FEATURES) - - -class DoorZoneAssigner: - """Fuse pose-dominant evidence and smooth front probability per track.""" - - def __init__(self, config: DoorZoneConfig): - self.config = config - self.history: dict[int, deque[float]] = defaultdict( - lambda: deque(maxlen=self.config.history_frames) - ) - self.last_seen: dict[int, int] = {} - self.zone_counts: Counter = Counter() - - def _score(self, features: dict[str, float]) -> tuple[float, dict[str, float]]: - weighted_sum = 0.0 - total_weight = 0.0 - contributions = {} - for name, calibration in self.config.features.items(): - if name not in features: - continue - frontness = calibration.frontness(float(features[name])) - contributions[name] = round(frontness, 6) - weighted_sum += frontness * calibration.weight - total_weight += calibration.weight - probability = weighted_sum / total_weight if total_weight else 0.5 - return probability, contributions - - def update( - self, - frame_index: int, - record: dict, - frame_shape: tuple[int, int], - ) -> list[dict]: - track_ids = [int(value) for value in record.get("track_ids", [])] - assignments = [] - for position, _box in enumerate(record.get("boxes", [])): - track_id = track_ids[position] if position < len(track_ids) else position + 1 - features, pose_feature_count = extract_person_features( - record, position, frame_shape - ) - probability, contributions = self._score(features) - enough_pose = pose_feature_count >= self.config.minimum_pose_features - if features["box_height"] < self.config.minimum_person_height: - raw_zone = "unassigned" - door_zone = "unassigned" - elif not enough_pose: - raw_zone = "transition" - door_zone = "transition" - else: - raw_zone = self.config.classify(probability) - values = self.history[track_id] - values.append(probability) - door_zone = self.config.classify(float(median(values))) - - values = self.history.get(track_id, ()) - smoothed_probability = ( - float(median(values)) if values else float(probability) - ) - self.last_seen[track_id] = int(frame_index) - self.zone_counts[door_zone] += 1 - assignments.append( - { - "track_id": track_id, - "person_index": position, - "door_zone": door_zone, - "raw_door_zone": raw_zone, - "front_probability": round(float(probability), 6), - "smoothed_front_probability": round(smoothed_probability, 6), - "confidence": round(abs(smoothed_probability - 0.5) * 2.0, 6), - "evidence_source": "pose_primary_bbox_secondary", - "pose_feature_count": pose_feature_count, - "features": {name: round(value, 6) for name, value in features.items()}, - "feature_frontness": contributions, - "history_frames": len(values), - } - ) - - expired = [ - track_id - for track_id, seen_at in self.last_seen.items() - if frame_index - seen_at > self.config.stale_track_frames - ] - for track_id in expired: - self.history.pop(track_id, None) - self.last_seen.pop(track_id, None) - return assignments - - def summary(self) -> dict: - return { - "policy": "pose-primary weighted front probability with causal median", - "config": str(self.config.source_path) if self.config.source_path else None, - "history_frames": self.config.history_frames, - "front_probability_min": self.config.front_probability_min, - "rear_probability_max": self.config.rear_probability_max, - "minimum_person_height": self.config.minimum_person_height, - "minimum_pose_features": self.config.minimum_pose_features, - "features": { - name: { - "rear": calibration.rear, - "front": calibration.front, - "weight": calibration.weight, - } - for name, calibration in self.config.features.items() - }, - "zone_counts": dict(self.zone_counts), - } - - -def _inactive_risk(door: str) -> dict: - return { - "door_id": door, - "status": "inactive", - "risk_score": None, - "risk_level": "inactive", - "decision": "DOOR_CAN_NOT_ACTIVE", - "risk_type": f"{door.upper()}_INACTIVE", - "source": None, - "track_id": None, - "side": None, - "regions": [], - } - - -def _clear_risk(door: str) -> dict: - return { - "door_id": door, - "status": "calculated", - "risk_score": 0.0, - "risk_level": "clear", - "decision": "CLEAR_NO_PERSON_ASSIGNED_TO_ACTIVE_DOOR", - "risk_type": f"{door.upper()}_CLEAR", - "source": None, - "track_id": None, - "side": None, - "regions": [], - } - - -def _unknown_risk(door: str, transition_regions: list[dict]) -> dict: - return { - "door_id": door, - "status": "door_zone_ambiguous", - "risk_score": None, - "risk_level": "unknown", - "decision": "EVIDENCE_UNAVAILABLE_TRANSITION_ZONE", - "risk_type": f"{door.upper()}_UNKNOWN", - "source": None, - "track_id": None, - "side": None, - "regions": transition_regions, - } - - -def build_door_risks( - raw_risk: dict, - assignments: list[dict], - active_door: str, -) -> tuple[dict[str, dict], dict]: - """Build per-door risk and select the CAN-active door result.""" - - zone_by_track = { - int(assignment["track_id"]): str(assignment["door_zone"]) - for assignment in assignments - } - annotated_regions = [] - for original in raw_risk.get("regions", []): - region = dict(original) - region["door_zone"] = zone_by_track.get(int(region.get("track_id", -1)), "unknown") - annotated_regions.append(region) - - active_doors = set(DOORS) if active_door == "both" else {active_door} - if active_door == "none": - active_doors.clear() - - door_risks: dict[str, dict] = {} - for door in DOORS: - if door not in active_doors: - door_risks[door] = _inactive_risk(door) - continue - exact = [region for region in annotated_regions if region["door_zone"] == door] - transition = [ - region for region in annotated_regions if region["door_zone"] == "transition" - ] - calculated = [region for region in exact if region.get("risk_score") is not None] - if calculated: - nearest = max(calculated, key=lambda item: float(item["risk_score"])) - level = str(nearest["risk_level"]) - if transition and level in ("clear", "watch"): - door_risks[door] = _unknown_risk(door, transition) - else: - door_risks[door] = { - "door_id": door, - "status": "calculated", - "risk_score": nearest["risk_score"], - "risk_level": level, - "decision": nearest["decision"], - "risk_type": f"{door.upper()}_{level.upper()}", - "source": nearest["source"], - "track_id": nearest["track_id"], - "side": nearest["side"], - "regions": exact, - } - elif transition: - door_risks[door] = _unknown_risk(door, transition) - else: - door_risks[door] = _clear_risk(door) - - if len(active_doors) == 1: - selected = dict(door_risks[next(iter(active_doors))]) - elif not active_doors: - selected = { - "door_id": None, - "status": "inactive", - "risk_score": None, - "risk_level": "inactive", - "decision": "NO_DOOR_CAN_ACTIVE", - "risk_type": "NO_ACTIVE_DOOR", - "source": None, - "track_id": None, - "side": None, - "regions": [], - } - else: - severity = {"danger": 4, "warning": 3, "watch": 2, "unknown": 1, "clear": 0} - selected = dict( - max(door_risks.values(), key=lambda item: severity.get(item["risk_level"], -1)) - ) - selected["door_mask"] = raw_risk.get("door_mask") - selected["warning_distance_relative"] = raw_risk.get("warning_distance_relative") - selected["release_distance_relative"] = raw_risk.get("release_distance_relative") - return door_risks, selected diff --git a/pipeline/latest_result.py b/pipeline/latest_result.py deleted file mode 100644 index 1ab68ae..0000000 --- a/pipeline/latest_result.py +++ /dev/null @@ -1,64 +0,0 @@ -"""One owned, immutable latest result, mirroring the 0920 LatestResult contract.""" -from __future__ import annotations - -from collections.abc import Mapping -import threading -from types import MappingProxyType - - -def freeze(value): - if isinstance(value, Mapping): - return MappingProxyType({key: freeze(item) for key, item in value.items()}) - if isinstance(value, (list, tuple)): - return tuple(freeze(item) for item in value) - return value - - -def result_to_dict(value): - """Return a mutable JSON-compatible copy when a consumer needs serialization.""" - if isinstance(value, Mapping): - return {key: result_to_dict(item) for key, item in value.items()} - if isinstance(value, tuple): - return [result_to_dict(item) for item in value] - return value - - -class LatestResult: - """Readers share a stable snapshot; reading never removes or acknowledges it. - - Only the latest result is retained here. Slow readers can skip frames, and - references they already hold remain valid after subsequent publications. - """ - - def __init__(self): - self._condition = threading.Condition() - self._value = None - self._closed = False - - def publish(self, result): - if result is None: - raise ValueError("Cannot publish a null result") - snapshot = freeze(result) - with self._condition: - if self._closed: - raise RuntimeError("Result publisher is closed") - self._value = snapshot - self._condition.notify_all() - - def load(self): - with self._condition: - return self._value - - def wait(self, after_frame_index=-1, timeout=None): - """Wait for a newer result; return None on timeout/end without a new frame.""" - with self._condition: - def has_new_frame(): - return self._value is not None and self._value['frame_index'] > after_frame_index - - self._condition.wait_for(lambda: has_new_frame() or self._closed, timeout) - return self._value if has_new_frame() else None - - def close(self): - with self._condition: - self._closed = True - self._condition.notify_all() diff --git a/pipeline/native_backend.py b/pipeline/native_backend.py deleted file mode 100644 index 6f425c5..0000000 --- a/pipeline/native_backend.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Native Jetson TensorRT adapter; temporal/risk policy stays in upstream Python.""" -from __future__ import annotations -import ctypes as C -from pathlib import Path -import numpy as np - -ROOT = Path(__file__).resolve().parents[1] - -class NativeBackend: - def __init__(self, args): - self.lib = C.CDLL(str(ROOT / 'build/libvision_native.so')) - pointer = C.c_void_p - signatures = { - 'vap_error': ([], C.c_char_p), - 'vap_engine': ([C.c_char_p,C.c_int,C.c_int,C.c_int,C.c_float],pointer), - 'vap_engine_close': ([pointer],None), - 'vap_detect': ([pointer,pointer,C.c_int,C.c_int,pointer],C.c_int), - 'vap_pose': ([pointer,pointer,C.c_int,C.c_int,pointer,C.c_int,pointer],C.c_int), - 'vap_camera': ([C.c_char_p,C.c_int,C.c_int,C.c_int,C.c_int],pointer), - 'vap_camera_close': ([pointer],None), - 'vap_read': ([pointer,pointer,pointer],C.c_int), - 'vap_can': ([pointer],None), - } - for name,(inputs,output) in signatures.items(): - fn=getattr(self.lib,name);fn.argtypes=inputs;fn.restype=output - self.engine = self.lib.vap_engine(str(args.engines.resolve()).encode(),args.width,args.height,args.max_people,args.det_conf) - if not self.engine: self.fail() - self.camera=None - self.args=args - - def fail(self): - raise RuntimeError(self.lib.vap_error().decode()) - - def open_camera(self): - a=self.args - self.camera=self.lib.vap_camera(a.camera.encode(),a.width,a.height,a.fps,not a.no_can) - if not self.camera:self.fail() - - def read(self): - image=np.empty((self.args.height,self.args.width,3),np.uint8) - meta=np.zeros(4,np.float64) - result=self.lib.vap_read(self.camera,image.ctypes.data,meta.ctypes.data) - if result<0:self.fail() - if result==0:return None,None - return image,{'sequence':int(meta[0]),'capture_monotonic_ms':float(meta[1]),'dropped_before_inference':int(meta[2]),'driver_timestamp_ms':float(meta[3]),'latency_clock':'host_monotonic_at_dequeue'} - - def can_snapshot(self): - data=np.zeros(5,np.float64);self.lib.vap_can(data.ctypes.data) - return {'positions':[int(x) for x in data[:4]],'age_ms':float(data[4])} - - def detect(self, frame): - frame=np.ascontiguousarray(frame);out=np.empty((self.args.max_people,5),np.float32) - n=self.lib.vap_detect(self.engine,frame.ctypes.data,frame.shape[1],frame.shape[0],out.ctypes.data) - if n<0:self.fail() - return out[:n,:4].tolist(),out[:n,4].tolist() - - def pose(self,frame,bboxes): - frame=np.ascontiguousarray(frame);boxes=np.ascontiguousarray(bboxes,dtype=np.float32) - out=np.empty((len(boxes),133,3),np.float32) - n=self.lib.vap_pose(self.engine,frame.ctypes.data,frame.shape[1],frame.shape[0],boxes.ctypes.data,len(boxes),out.ctypes.data) - if n<0:self.fail() - return out[:n,:,:2].astype(np.float64),out[:n,:,2].astype(np.float64) - - def close(self): - if self.camera:self.lib.vap_camera_close(self.camera);self.camera=None - if self.engine:self.lib.vap_engine_close(self.engine);self.engine=None diff --git a/pipeline/pose_engine.py b/pipeline/pose_engine.py deleted file mode 100644 index fcc745c..0000000 --- a/pipeline/pose_engine.py +++ /dev/null @@ -1,148 +0,0 @@ -"""Resident native TensorRT inference with causal pose fusion and tracking.""" -from __future__ import annotations -import time -from collections import defaultdict, deque -import numpy as np -from pose_tracking import BoxTracker - -SIDES = ("left", "right") - -class NativePoseEngine: - def __init__(self, args, native): - self.args = args - self.native = native - self.pose = native.pose - self.box_tracker = BoxTracker(max_age=30) - self.people = {} - self.timings = defaultdict(lambda: deque(maxlen=3000)) - self.isolated_inferences = 0 - - def infer(self, frame: np.ndarray, frame_idx: int) -> dict: - from pose_geometry import HAND_SLICES - from pose_fusion import adaptive_smooth, build_global_hands, fuse_person - from pose_occlusion import Measurement, resolve_global_hand_assignments - from pose_tracking import ( - LivePersonState, - mask_other_people, - update_live_v2, - ) - - infer_started = time.perf_counter() - height, width = frame.shape[:2] - started = time.perf_counter() - detection_height = max( - 1, min(height, round(height * self.args.det_roi_y_max_ratio)) - ) - boxes, confidences = self.native.detect(frame[:detection_height]) - self.timings["detector_ms"].append((time.perf_counter() - started) * 1000) - track_ids = self.box_tracker.update(boxes, frame_idx, (height, width)) - - # Retire disappeared people in long-running camera sessions. - for stale_id in list(self.people): - if stale_id not in self.box_tracker.tracks: - del self.people[stale_id] - if boxes: - started = time.perf_counter() - raw_keypoints, raw_scores = self.pose(frame, bboxes=boxes) - self.timings["raw_pose_ms"].append((time.perf_counter() - started) * 1000) - isolated_keypoints = raw_keypoints.copy() - isolated_scores = raw_scores.copy() - started = time.perf_counter() - for person_idx, bbox in enumerate(boxes): - target_area = max((bbox[2] - bbox[0]) * (bbox[3] - bbox[1]), 1.0) - needs_isolation = any( - idx != person_idx - and max((other[2] - other[0]) * (other[3] - other[1]), 1.0) - < 0.5 * target_area - for idx, other in enumerate(boxes) - ) - if not needs_isolation: - continue - masked = mask_other_people(frame, boxes, person_idx) - keypoints, scores = self.pose(masked, bboxes=[bbox]) - isolated_keypoints[person_idx] = keypoints[0] - isolated_scores[person_idx] = scores[0] - self.isolated_inferences += 1 - self.timings["isolated_extra_ms"].append( - (time.perf_counter() - started) * 1000 - ) - else: - raw_keypoints = np.empty((0, 133, 2), dtype=np.float64) - raw_scores = np.empty((0, 133), dtype=np.float64) - isolated_keypoints = raw_keypoints.copy() - isolated_scores = raw_scores.copy() - - selected_list, smoothed_list, metadata_list, states = [], [], [], [] - for person_idx, (bbox, track_id) in enumerate(zip(boxes, track_ids)): - state = self.people.setdefault(track_id, LivePersonState()) - selected, metadata = fuse_person( - raw_keypoints[person_idx], - raw_scores[person_idx], - isolated_keypoints[person_idx], - isolated_scores[person_idx], - bbox, - state.fusion, - ) - selected_list.append(selected) - smoothed_list.append(adaptive_smooth(selected, bbox, state.fusion)) - metadata_list.append(metadata) - states.append(state) - - measurements, endpoint_tracks = {}, {} - for person_idx, (selected, metadata, state) in enumerate( - zip(selected_list, metadata_list, states) - ): - for side, (start, end) in HAND_SLICES.items(): - meta = metadata[side] - measurements[(person_idx, side)] = ( - Measurement( - keypoints=selected[start:end], - precise=bool(meta["distance_eligible"]), - provisional=not bool(meta["distance_eligible"]), - quality=float(meta.get("selected_hand_score", 0.0)), - ) - if meta["alarm_eligible"] - else None - ) - endpoint_tracks[(person_idx, side)] = state.endpoints[side] - measurements, assignments = resolve_global_hand_assignments( - measurements, endpoint_tracks, boxes, 30 - ) - - v2_list, v2_meta_list = [], [] - for person_idx, (bbox, selected, smoothed, metadata, state) in enumerate( - zip(boxes, selected_list, smoothed_list, metadata_list, states) - ): - v2, v2_meta = update_live_v2( - frame_idx, - bbox, - selected, - smoothed, - metadata, - state, - {side: measurements[(person_idx, side)] for side in SIDES}, - {side: assignments.get((person_idx, side)) for side in SIDES}, - 12, - 30, - ) - v2_list.append(v2) - v2_meta_list.append(v2_meta) - - self.timings["pose_pipeline_total_ms"].append( - (time.perf_counter() - infer_started) * 1000 - ) - return { - "frame_index": frame_idx, - "track_ids": track_ids, - "boxes": np.round(np.asarray(boxes), 3).tolist() if boxes else [], - "det_confidences": [round(value, 4) for value in confidences], - "raw_keypoints": np.round(raw_keypoints, 3).tolist(), - "temporal_selected_keypoints": np.round( - np.asarray(selected_list), 3 - ).tolist(), - "temporal_keypoints": np.round(np.asarray(smoothed_list), 3).tolist(), - "temporal_metadata": metadata_list, - "v2_keypoints": np.round(np.asarray(v2_list), 3).tolist(), - "v2_metadata": v2_meta_list, - "global_hands": build_global_hands(selected_list, metadata_list), - } diff --git a/pipeline/pose_fusion.py b/pipeline/pose_fusion.py deleted file mode 100644 index 281b683..0000000 --- a/pipeline/pose_fusion.py +++ /dev/null @@ -1,530 +0,0 @@ -"""Runtime pose fusion helpers.""" -from __future__ import annotations -import math -from dataclasses import dataclass, field -import cv2 -import numpy as np -from pose_geometry import BODY_WRISTS, HAND_EDGES_LOCAL, HAND_SLICES, normalized_keypoints - - -BODY_SIDE_INDICES = {"left": (5, 7, 9), "right": (6, 8, 10)} - - -SIDE_COLORS = {"left": (40, 40, 255), "right": (30, 230, 30)} - - -HAND_COLORS = {"left": (255, 80, 220), "right": (255, 190, 40)} - - -@dataclass -class Candidate: - source: str - keypoints: np.ndarray - scores: np.ndarray - normalized: np.ndarray - limb_score: float - hand_score: float - upper_length: float - forearm_length: float - hand_span: float - wrist_link: float - limb_valid: bool - hand_valid: bool - - -@dataclass -class SideTrack: - limb_previous: np.ndarray | None = None - limb_velocity: np.ndarray | None = None - hand_previous: np.ndarray | None = None - hand_velocity: np.ndarray | None = None - limb_source: str | None = None - hand_source: str | None = None - previous_hand_eligible: bool = False - - -@dataclass -class PersonTrack: - sides: dict[str, SideTrack] = field( - default_factory=lambda: {"left": SideTrack(), "right": SideTrack()} - ) - smoothed: np.ndarray | None = None - - -def bbox_diagonal(bbox: list[float]) -> float: - return max(math.hypot(bbox[2] - bbox[0], bbox[3] - bbox[1]), 1.0) - - -def build_candidate( - source: str, - keypoints: np.ndarray, - scores: np.ndarray, - bbox: list[float], - side: str, -) -> Candidate: - normalized = normalized_keypoints(keypoints, bbox) - shoulder, elbow, wrist = BODY_SIDE_INDICES[side] - hand_start, hand_end = HAND_SLICES[side] - hand = keypoints[hand_start:hand_end] - diagonal = bbox_diagonal(bbox) - upper_length = float(np.linalg.norm(normalized[shoulder] - normalized[elbow])) - forearm_length = float(np.linalg.norm(normalized[elbow] - normalized[wrist])) - hand_span = math.hypot(float(np.ptp(hand[:, 0])), float(np.ptp(hand[:, 1]))) / diagonal - wrist_link = float(np.linalg.norm(keypoints[wrist] - hand[0])) / diagonal - limb_score = float(np.mean(scores[[shoulder, elbow, wrist]])) - hand_score = float(np.mean(scores[hand_start:hand_end])) - limb_valid = 0.025 <= upper_length <= 0.8 and 0.025 <= forearm_length <= 0.8 - hand_valid = 0.008 <= hand_span <= 0.35 and wrist_link <= 0.12 - return Candidate( - source=source, - keypoints=keypoints, - scores=scores, - normalized=normalized, - limb_score=limb_score, - hand_score=hand_score, - upper_length=upper_length, - forearm_length=forearm_length, - hand_span=hand_span, - wrist_link=wrist_link, - limb_valid=limb_valid, - hand_valid=hand_valid, - ) - - -def predicted(previous: np.ndarray | None, velocity: np.ndarray | None) -> np.ndarray | None: - if previous is None: - return None - if velocity is None: - return previous - return previous + 0.65 * velocity - - -def robust_distance(points: np.ndarray, target: np.ndarray | None) -> float: - if target is None: - return 0.0 - distances = np.linalg.norm(points - target, axis=1) - return float(np.median(distances)) - - -def select_limb( - candidates: list[Candidate], - side: str, - track: SideTrack | None, -) -> tuple[Candidate, dict[str, float]]: - indices = np.asarray(BODY_SIDE_INDICES[side]) - target = predicted(track.limb_previous, track.limb_velocity) if track else None - scored: list[tuple[float, Candidate, float]] = [] - for candidate in candidates: - temporal_cost = robust_distance(candidate.normalized[indices], target) - geometry_bonus = 1.2 if candidate.limb_valid else -5.0 - switch_bonus = 0.35 if track and candidate.source == track.limb_source else 0.0 - score = candidate.limb_score + geometry_bonus + switch_bonus - 7.0 * temporal_cost - scored.append((score, candidate, temporal_cost)) - score, selected, temporal_cost = max(scored, key=lambda item: item[0]) - return selected, {"score": score, "temporal_cost": temporal_cost} - - -def hand_link_to_limb( - candidate: Candidate, - selected_limb: Candidate, - bbox: list[float], - side: str, -) -> float: - hand_start, _ = HAND_SLICES[side] - wrist_idx = BODY_WRISTS[side] - return float( - np.linalg.norm(candidate.keypoints[hand_start] - selected_limb.keypoints[wrist_idx]) - ) / bbox_diagonal(bbox) - - -def select_hand( - candidates: list[Candidate], - selected_limb: Candidate, - bbox: list[float], - side: str, - track: SideTrack | None, -) -> tuple[Candidate, bool, dict[str, float]]: - start, end = HAND_SLICES[side] - target = predicted(track.hand_previous, track.hand_velocity) if track else None - scored: list[tuple[float, Candidate, bool, float, float]] = [] - for candidate in candidates: - temporal_cost = robust_distance(candidate.normalized[start:end], target) - link = hand_link_to_limb(candidate, selected_limb, bbox, side) - valid = candidate.hand_valid - geometry_bonus = 3.0 if valid else -7.0 - switch_bonus = 0.45 if track and candidate.source == track.hand_source else 0.0 - limb_source_bonus = 0.15 if candidate.source == selected_limb.source else 0.0 - # A newly visible endpoint may disagree with the currently selected - # (occlusion-driven) limb. Keep this as a soft cost so strong image - # evidence can switch both the hand and its limb together. - link_penalty = 1.5 * min(max(link - 0.12, 0.0), 0.2) - score = ( - candidate.hand_score - + geometry_bonus - + switch_bonus - + limb_source_bonus - - link_penalty - - 8.0 * min(temporal_cost, 0.15) - ) - scored.append((score, candidate, valid, temporal_cost, link)) - score, selected, valid, temporal_cost, link = max(scored, key=lambda item: item[0]) - return selected, valid, { - "score": score, - "temporal_cost": temporal_cost, - "link_to_selected_limb": link, - } - - -def candidate_disagreement( - raw: Candidate, - isolated: Candidate, - side: str, -) -> tuple[float, float]: - shoulder, elbow, wrist = BODY_SIDE_INDICES[side] - start, end = HAND_SLICES[side] - limb = float( - np.median( - np.linalg.norm( - raw.normalized[[shoulder, elbow, wrist]] - - isolated.normalized[[shoulder, elbow, wrist]], - axis=1, - ) - ) - ) - hand_distances = np.linalg.norm( - raw.normalized[start:end] - isolated.normalized[start:end], axis=1 - ) - hand = float(np.percentile(hand_distances, 90)) - return limb, hand - - -def suppress_duplicate_hands( - fused: np.ndarray, - metadata: dict, - bbox: list[float], -) -> None: - left_start, left_end = HAND_SLICES["left"] - right_start, right_end = HAND_SLICES["right"] - left_center = np.mean(fused[left_start:left_end], axis=0) - right_center = np.mean(fused[right_start:right_end], axis=0) - separation = float(np.linalg.norm(left_center - right_center)) / bbox_diagonal(bbox) - if separation >= 0.08: - return - weaker = min( - ("left", "right"), - key=lambda side: metadata[side]["selected_hand_score"], - ) - metadata[weaker]["hand_state"] = "duplicate" - metadata[weaker]["distance_eligible"] = False - metadata[weaker]["alarm_eligible"] = False - metadata[weaker]["duplicate_hand_separation"] = round(separation, 5) - - -def fuse_person( - raw_keypoints: np.ndarray, - raw_scores: np.ndarray, - isolated_keypoints: np.ndarray, - isolated_scores: np.ndarray, - bbox: list[float], - track: PersonTrack | None, -) -> tuple[np.ndarray, dict]: - fused = raw_keypoints.copy() - metadata: dict[str, dict] = {} - - for side in ("left", "right"): - side_track = track.sides[side] if track else None - raw = build_candidate("raw", raw_keypoints, raw_scores, bbox, side) - isolated = build_candidate( - "isolated", isolated_keypoints, isolated_scores, bbox, side - ) - candidates = [raw, isolated] - limb, limb_detail = select_limb(candidates, side, side_track) - hand, hand_valid, hand_detail = select_hand( - candidates, limb, bbox, side, side_track - ) - - other_hand = isolated if hand.source == "raw" else raw - endpoint_advantage = hand.hand_score - other_hand.hand_score - if hand.source != limb.source and hand_valid and endpoint_advantage >= 0.6: - limb = hand - limb_detail["visible_endpoint_override"] = 1.0 - hand_detail["link_to_selected_limb"] = hand.wrist_link - - body_indices = np.asarray(BODY_SIDE_INDICES[side]) - hand_start, hand_end = HAND_SLICES[side] - fused[body_indices] = limb.keypoints[body_indices] - fused[hand_start:hand_end] = hand.keypoints[hand_start:hand_end] - - limb_disagreement, hand_disagreement = candidate_disagreement(raw, isolated, side) - if limb_disagreement <= 0.05: - limb_state = "consensus" - else: - limb_state = "inferred" - - score_advantage = raw.hand_score - isolated.hand_score - raw_temporally_stable = hand_detail["temporal_cost"] <= 0.08 - if not hand_valid: - hand_state = "rejected" - distance_eligible = False - elif hand_disagreement <= 0.06: - hand_state = "consensus" - distance_eligible = True - elif ( - hand.source == "raw" - and score_advantage >= 0.6 - and raw_temporally_stable - ): - hand_state = "visible_endpoint" - distance_eligible = True - elif hand.source == "isolated": - hand_state = "inferred" - distance_eligible = False - else: - hand_state = "conflict" - distance_eligible = False - fast_alarm_endpoint = ( - hand_valid and hand.source == "raw" and score_advantage >= 0.6 - ) - alarm_eligible = distance_eligible or fast_alarm_endpoint - - metadata[side] = { - "limb_source": limb.source, - "hand_source": hand.source, - "limb_state": limb_state, - "hand_state": hand_state, - "distance_eligible": distance_eligible, - "alarm_eligible": alarm_eligible, - "limb_disagreement": round(limb_disagreement, 5), - "hand_disagreement": round(hand_disagreement, 5), - "raw_hand_score": round(raw.hand_score, 4), - "isolated_hand_score": round(isolated.hand_score, 4), - "selected_hand_span": round(hand.hand_span, 5), - "selected_hand_score": round(hand.hand_score, 4), - "selected_hand_valid": hand_valid, - "limb_selection": {key: round(value, 5) for key, value in limb_detail.items()}, - "hand_selection": {key: round(value, 5) for key, value in hand_detail.items()}, - } - - if side_track is not None: - limb_now = limb.normalized[body_indices] - hand_now = hand.normalized[hand_start:hand_end] - if side_track.limb_previous is not None: - delta = limb_now - side_track.limb_previous - side_track.limb_velocity = ( - delta - if side_track.limb_velocity is None - else 0.65 * side_track.limb_velocity + 0.35 * delta - ) - if hand_valid: - if side_track.hand_previous is not None: - delta = hand_now - side_track.hand_previous - side_track.hand_velocity = ( - delta - if side_track.hand_velocity is None - else 0.65 * side_track.hand_velocity + 0.35 * delta - ) - side_track.hand_previous = hand_now - side_track.hand_source = hand.source - side_track.limb_previous = limb_now - side_track.limb_source = limb.source - side_track.previous_hand_eligible = distance_eligible - - suppress_duplicate_hands(fused, metadata, bbox) - return fused, metadata - - -def adaptive_smooth( - keypoints: np.ndarray, - bbox: list[float], - track: PersonTrack, -) -> np.ndarray: - normalized = normalized_keypoints(keypoints, bbox) - if track.smoothed is None: - track.smoothed = normalized - else: - displacement = np.linalg.norm(normalized - track.smoothed, axis=1) - alpha = 0.32 + 0.58 * np.clip(displacement / 0.08, 0.0, 1.0) - track.smoothed = alpha[:, None] * normalized + (1.0 - alpha[:, None]) * track.smoothed - - x1, y1, x2, y2 = bbox - result = track.smoothed.copy() - result[:, 0] = result[:, 0] * max(x2 - x1, 1.0) + x1 - result[:, 1] = result[:, 1] * max(y2 - y1, 1.0) + y1 - return result - - -def build_global_hands( - keypoints: list[np.ndarray], - metadata: list[dict], -) -> list[dict]: - candidates = [] - for person_idx, (person_keypoints, person_meta) in enumerate( - zip(keypoints, metadata), start=1 - ): - for side, (start, end) in HAND_SLICES.items(): - meta = person_meta[side] - if not meta["alarm_eligible"]: - continue - hand = person_keypoints[start:end] - span_px = math.hypot(float(np.ptp(hand[:, 0])), float(np.ptp(hand[:, 1]))) - candidates.append( - { - "person_id": person_idx, - "side": side, - "source": meta["hand_source"], - "state": meta["hand_state"], - "distance_eligible": meta["distance_eligible"], - "alarm_eligible": meta["alarm_eligible"], - "score": meta["selected_hand_score"], - "center": np.mean(hand, axis=0), - "span_px": span_px, - "keypoints": hand, - "aliases": [], - } - ) - - kept: list[dict] = [] - for candidate in sorted(candidates, key=lambda item: item["score"], reverse=True): - duplicate = None - for existing in kept: - threshold = float(np.clip(0.45 * max(candidate["span_px"], existing["span_px"]), 18, 70)) - if float(np.linalg.norm(candidate["center"] - existing["center"])) <= threshold: - duplicate = existing - break - if duplicate is None: - kept.append(candidate) - else: - duplicate["aliases"].append( - {"person_id": candidate["person_id"], "side": candidate["side"]} - ) - - return [ - { - "person_id": item["person_id"], - "side": item["side"], - "source": item["source"], - "state": item["state"], - "distance_eligible": item["distance_eligible"], - "alarm_eligible": item["alarm_eligible"], - "score": item["score"], - "center": np.round(item["center"], 3).tolist(), - "span_px": round(item["span_px"], 3), - "keypoints": np.round(item["keypoints"], 3).tolist(), - "aliases": item["aliases"], - } - for item in kept - ] - - -def draw_dashed_line( - image: np.ndarray, - start: np.ndarray, - end: np.ndarray, - color: tuple[int, int, int], - thickness: int = 2, -) -> None: - a = np.asarray(start, dtype=np.float64) - b = np.asarray(end, dtype=np.float64) - length = float(np.linalg.norm(b - a)) - if length < 1.0: - return - direction = (b - a) / length - for offset in np.arange(0.0, length, 14.0): - segment_start = a + direction * offset - segment_end = a + direction * min(offset + 8.0, length) - cv2.line( - image, - tuple(np.round(segment_start).astype(int)), - tuple(np.round(segment_end).astype(int)), - color, - thickness, - cv2.LINE_AA, - ) - - -def draw_fused( - image: np.ndarray, - boxes: list[list[float]], - keypoints: list[np.ndarray], - metadata: list[dict], - title: str, -) -> np.ndarray: - canvas = image.copy() - for person_idx, (bbox, kpts, person_meta) in enumerate( - zip(boxes, keypoints, metadata), start=1 - ): - x1, y1, x2, y2 = (int(round(value)) for value in bbox) - cv2.rectangle(canvas, (x1, y1), (x2, y2), (0, 220, 255), 2) - cv2.putText( - canvas, - f"P{person_idx}", - (x1 + 4, max(22, y1 + 24)), - cv2.FONT_HERSHEY_SIMPLEX, - 0.72, - (0, 220, 255), - 2, - cv2.LINE_AA, - ) - for side in ("left", "right"): - shoulder, elbow, wrist = BODY_SIDE_INDICES[side] - side_meta = person_meta[side] - color = SIDE_COLORS[side] - for a, b in ((shoulder, elbow), (elbow, wrist)): - if side_meta["limb_state"] == "consensus": - cv2.line( - canvas, - tuple(np.round(kpts[a]).astype(int)), - tuple(np.round(kpts[b]).astype(int)), - color, - 3, - cv2.LINE_AA, - ) - else: - draw_dashed_line(canvas, kpts[a], kpts[b], (0, 140, 255), 3) - - start, end = HAND_SLICES[side] - hand = kpts[start:end] - if side_meta["hand_state"] != "rejected": - hand_color = HAND_COLORS[side] - if not side_meta["distance_eligible"]: - hand_color = (0, 140, 255) - for a, b in HAND_EDGES_LOCAL: - if side_meta["distance_eligible"]: - cv2.line( - canvas, - tuple(np.round(hand[a]).astype(int)), - tuple(np.round(hand[b]).astype(int)), - hand_color, - 2, - cv2.LINE_AA, - ) - else: - draw_dashed_line(canvas, hand[a], hand[b], hand_color, 2) - label = ( - f"{side[0].upper()}:{side_meta['hand_state'][:4]}" - f"/{side_meta['hand_source'][0]}" - ) - label_y = max(45, y1 + (48 if side == "left" else 70)) - cv2.putText( - canvas, - label, - (x1 + 4, label_y), - cv2.FONT_HERSHEY_SIMPLEX, - 0.5, - HAND_COLORS[side] if side_meta["distance_eligible"] else (0, 140, 255), - 1, - cv2.LINE_AA, - ) - - cv2.rectangle(canvas, (0, 0), (650, 45), (0, 0, 0), -1) - cv2.putText( - canvas, - title, - (10, 31), - cv2.FONT_HERSHEY_SIMPLEX, - 0.8, - (255, 255, 255), - 2, - cv2.LINE_AA, - ) - return canvas diff --git a/pipeline/pose_geometry.py b/pipeline/pose_geometry.py deleted file mode 100644 index e7f6ecb..0000000 --- a/pipeline/pose_geometry.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Runtime pose geometry helpers.""" -from __future__ import annotations -import numpy as np - - -HAND_EDGES_LOCAL = ( - (0, 1), (1, 2), (2, 3), (3, 4), - (0, 5), (5, 6), (6, 7), (7, 8), - (0, 9), (9, 10), (10, 11), (11, 12), - (0, 13), (13, 14), (14, 15), (15, 16), - (0, 17), (17, 18), (18, 19), (19, 20), -) - - -HAND_SLICES = {"left": (91, 112), "right": (112, 133)} - - -BODY_WRISTS = {"left": 9, "right": 10} - - -def normalized_keypoints(kpts: np.ndarray, bbox: list[float]) -> np.ndarray: - x1, y1, x2, y2 = bbox - width = max(x2 - x1, 1.0) - height = max(y2 - y1, 1.0) - result = kpts.copy().astype(np.float64) - result[:, 0] = (result[:, 0] - x1) / width - result[:, 1] = (result[:, 1] - y1) / height - return result diff --git a/pipeline/pose_occlusion.py b/pipeline/pose_occlusion.py deleted file mode 100644 index 1ed4661..0000000 --- a/pipeline/pose_occlusion.py +++ /dev/null @@ -1,513 +0,0 @@ -"""Runtime pose occlusion helpers.""" -from __future__ import annotations -import math -from dataclasses import dataclass -import numpy as np -from pose_geometry import HAND_SLICES - - -@dataclass -class Measurement: - keypoints: np.ndarray - precise: bool - provisional: bool - source: str = "model" - quality: float = 0.0 - - -@dataclass -class EndpointTrack: - wrist: np.ndarray | None = None - velocity: np.ndarray | None = None - hand_shape: np.ndarray | None = None - last_observed_frame: int | None = None - missed_frames: int = 0 - last_elbow: np.ndarray | None = None - last_direction: np.ndarray | None = None - - -def bbox_diagonal(bbox: list[float]) -> float: - return max(math.hypot(bbox[2] - bbox[0], bbox[3] - bbox[1]), 1.0) - - -def solve_two_bone_elbow( - shoulder: np.ndarray, - wrist: np.ndarray, - upper_length: float, - forearm_length: float, - reference_elbow: np.ndarray, -) -> np.ndarray: - direction = wrist - shoulder - distance = float(np.linalg.norm(direction)) - if distance < 1e-6: - return reference_elbow.copy() - unit = direction / distance - - if distance >= upper_length + forearm_length: - return shoulder + unit * upper_length - if distance <= abs(upper_length - forearm_length): - sign = 1.0 if upper_length >= forearm_length else -1.0 - return shoulder + unit * upper_length * sign - - along = ( - upper_length * upper_length - - forearm_length * forearm_length - + distance * distance - ) / (2.0 * distance) - height = math.sqrt(max(upper_length * upper_length - along * along, 0.0)) - base = shoulder + unit * along - perpendicular = np.asarray([-unit[1], unit[0]]) - candidates = (base + perpendicular * height, base - perpendicular * height) - return min(candidates, key=lambda candidate: float(np.linalg.norm(candidate - reference_elbow))) - - -def update_endpoint_track( - track: EndpointTrack, - measurement: Measurement | None, - frame_idx: int, - short_occlusion_frames: int, - medium_occlusion_frames: int, -) -> tuple[np.ndarray | None, np.ndarray | None, str]: - if measurement is not None: - measured_wrist = measurement.keypoints[0] - measured_shape = measurement.keypoints - measured_wrist - if track.wrist is None or track.missed_frames > short_occlusion_frames: - track.wrist = measured_wrist.copy() - track.velocity = np.zeros(2, dtype=np.float64) - track.hand_shape = measured_shape.copy() - else: - prediction = track.wrist + track.velocity - residual = measured_wrist - prediction - alpha = 0.78 if measurement.precise else 0.88 - beta = 0.18 if measurement.precise else 0.24 - track.wrist = prediction + alpha * residual - track.velocity = 0.82 * track.velocity + beta * residual - shape_alpha = 0.55 if measurement.precise else 0.7 - track.hand_shape = ( - shape_alpha * measured_shape + (1.0 - shape_alpha) * track.hand_shape - ) - track.last_observed_frame = frame_idx - track.missed_frames = 0 - if measurement.source == "global_reassignment": - state = "reassigned" - elif measurement.source == "temporal_recovery": - state = "recovered" - elif measurement.precise: - state = "observed" - elif measurement.provisional: - state = "provisional" - else: - state = "interpolated" - return track.wrist.copy(), track.wrist + track.hand_shape, state - - track.missed_frames += 1 - if track.wrist is None: - return None, None, "reachable_long" - if track.missed_frames <= short_occlusion_frames: - track.wrist = track.wrist + track.velocity - track.velocity *= 0.86 - return track.wrist.copy(), track.wrist + track.hand_shape, "predicted_short" - if track.missed_frames <= medium_occlusion_frames: - return None, None, "reachable_medium" - return None, None, "reachable_long" - - -def temporal_recovery_measurement( - keypoints: np.ndarray, - metadata: dict, - side: str, - bbox: list[float], - track: EndpointTrack, - medium_occlusion_frames: int, -) -> Measurement | None: - """Recover a weak current-frame hand only when history strongly supports it.""" - if track.wrist is None or track.missed_frames > medium_occlusion_frames: - return None - if metadata.get("hand_state") not in {"conflict", "inferred"}: - return None - if not metadata.get("selected_hand_valid", False): - return None - if float(metadata.get("selected_hand_score", 0.0)) < 2.0: - return None - link = float(metadata.get("hand_selection", {}).get("link_to_selected_limb", 1.0)) - if link > 0.08: - return None - - start, end = HAND_SLICES[side] - hand = np.asarray(keypoints[start:end], dtype=np.float64) - if hand.shape != (end - start, 2) or not np.isfinite(hand).all(): - return None - prediction = track.wrist + track.velocity - diagonal = bbox_diagonal(bbox) - gate = min(0.20, 0.05 + 0.008 * max(track.missed_frames, 1)) * diagonal - if float(np.linalg.norm(hand[0] - prediction)) > gate: - return None - return Measurement( - keypoints=hand, - precise=False, - provisional=True, - source="temporal_recovery", - quality=float(metadata.get("selected_hand_score", 0.0)), - ) - - -def hand_span_px(hand: np.ndarray) -> float: - return math.hypot(float(np.ptp(hand[:, 0])), float(np.ptp(hand[:, 1]))) - - -def bbox_area(bbox: list[float]) -> float: - return max((bbox[2] - bbox[0]) * (bbox[3] - bbox[1]), 1.0) - - -def reset_endpoint_track(track: EndpointTrack) -> None: - track.wrist = None - track.velocity = None - track.hand_shape = None - track.last_observed_frame = None - track.missed_frames = 0 - track.last_elbow = None - track.last_direction = None - - -def resolve_global_hand_assignments( - frame_measurements: dict[tuple[int, str], Measurement | None], - tracks: dict[tuple[int, str], EndpointTrack], - boxes: list[list[float]], - medium_occlusion_frames: int, -) -> tuple[dict[tuple[int, str], Measurement | None], dict[tuple[int, str], dict]]: - """Resolve overlapping-person hands before binding them to body sides. - - Large foreground top-down crops may include a smaller background person. - The same physical hand can therefore be emitted for both people, or only by - the smaller crop even though it belongs to the foreground person. Direct - duplicates are kept on the foreground track when their apparent scales - agree. A missing foreground side can then reclaim a nearby candidate using - its recent hand trajectory and historical hand scale. - """ - resolved = dict(frame_measurements) - events: dict[tuple[int, str], dict] = {} - keys = list(resolved) - - # First collapse direct cross-person duplicates. Keep this deliberately - # asymmetric: a substantially larger foreground crop must also carry the - # substantially larger historical hand shape. - for index, first_key in enumerate(keys): - first = resolved[first_key] - if first is None: - continue - for second_key in keys[index + 1 :]: - second = resolved[second_key] - if second is None or first_key[0] == second_key[0]: - continue - first_hand = first.keypoints - second_hand = second.keypoints - first_span = hand_span_px(first_hand) - second_span = hand_span_px(second_hand) - center_distance = float( - np.linalg.norm(np.mean(first_hand, axis=0) - np.mean(second_hand, axis=0)) - ) - if center_distance > max(70.0, 0.5 * max(first_span, second_span)): - continue - - first_area = bbox_area(boxes[first_key[0]]) - second_area = bbox_area(boxes[second_key[0]]) - if first_area >= 2.5 * second_area and ( - first_span >= 1.45 * second_span - or first.quality >= second.quality + 0.6 - ): - owner_key, released_key = first_key, second_key - elif second_area >= 2.5 * first_area and ( - second_span >= 1.45 * first_span - or second.quality >= first.quality + 0.6 - ): - owner_key, released_key = second_key, first_key - else: - continue - resolved[released_key] = None - reset_endpoint_track(tracks[released_key]) - events[owner_key] = { - "method": "foreground_duplicate_owner", - "released_person_id": released_key[0] + 1, - "released_side": released_key[1], - } - events[released_key] = { - "method": "reassigned_away", - "assigned_person_id": owner_key[0] + 1, - "assigned_side": owner_key[1], - } - - # Then recover missing foreground hands from another crop. The candidate - # must fall inside the target's recent motion gate and be much smaller than - # the target's historical foreground hand shape. - available_sources = [key for key, value in resolved.items() if value is not None] - target_candidates = [] - for target_key, target_measurement in resolved.items(): - if target_measurement is not None: - continue - target_track = tracks[target_key] - if ( - target_track.wrist is None - or target_track.hand_shape is None - or target_track.missed_frames > medium_occlusion_frames - ): - continue - target_area = bbox_area(boxes[target_key[0]]) - predicted_center = target_track.wrist + np.mean(target_track.hand_shape, axis=0) - target_span = hand_span_px(target_track.hand_shape) - gate = 0.20 * bbox_diagonal(boxes[target_key[0]]) - for source_key in available_sources: - if source_key[0] == target_key[0]: - continue - source = resolved[source_key] - if source is None or source.source != "model": - continue - source_area = bbox_area(boxes[source_key[0]]) - if target_area < 2.5 * source_area: - continue - source_center = np.mean(source.keypoints, axis=0) - source_span = hand_span_px(source.keypoints) - distance = float(np.linalg.norm(source_center - predicted_center)) - if distance > gate or target_span < 1.45 * max(source_span, 1.0): - continue - target_candidates.append( - (distance / gate, target_key, source_key, source_center) - ) - - used_targets = set() - used_sources = set() - for _, target_key, source_key, source_center in sorted(target_candidates): - if target_key in used_targets or source_key in used_sources: - continue - target_track = tracks[target_key] - shape_center = np.mean(target_track.hand_shape, axis=0) - reassigned_wrist = source_center - shape_center - reassigned_hand = reassigned_wrist + target_track.hand_shape - resolved[target_key] = Measurement( - keypoints=reassigned_hand, - precise=False, - provisional=True, - source="global_reassignment", - quality=resolved[source_key].quality, - ) - resolved[source_key] = None - reset_endpoint_track(tracks[source_key]) - events[target_key] = { - "method": "foreground_temporal_reassignment", - "source_person_id": source_key[0] + 1, - "source_side": source_key[1], - "observed_center": np.round(source_center, 3).tolist(), - } - events[source_key] = { - "method": "reassigned_away", - "assigned_person_id": target_key[0] + 1, - "assigned_side": target_key[1], - } - used_targets.add(target_key) - used_sources.add(source_key) - return resolved, events - - -def track_direction(track: EndpointTrack) -> np.ndarray: - if track.last_direction is not None: - return track.last_direction / max(float(np.linalg.norm(track.last_direction)), 1e-6) - if track.velocity is not None and float(np.linalg.norm(track.velocity)) > 1e-6: - return track.velocity / np.linalg.norm(track.velocity) - return np.asarray([1.0, 0.0], dtype=np.float64) - - -def ellipse_polygon( - center: np.ndarray, - major_axis: float, - minor_axis: float, - direction: np.ndarray, - point_count: int = 64, -) -> np.ndarray: - perpendicular = np.asarray([-direction[1], direction[0]], dtype=np.float64) - angles = np.linspace(0.0, 2.0 * math.pi, point_count, endpoint=False) - return ( - center - + np.cos(angles)[:, None] * major_axis * direction - + np.sin(angles)[:, None] * minor_axis * perpendicular - ) - - -def clip_polygon_from_anchor_to_circle( - points: np.ndarray, - anchor: np.ndarray, - circle_center: np.ndarray, - radius: float, -) -> np.ndarray: - """Clip radial polygon samples to a shoulder-centered anatomical limit.""" - clipped = [] - offset = anchor - circle_center - for point in points: - if float(np.linalg.norm(point - circle_center)) <= radius: - clipped.append(point) - continue - ray = point - anchor - a = float(np.dot(ray, ray)) - b = 2.0 * float(np.dot(offset, ray)) - c = float(np.dot(offset, offset) - radius * radius) - discriminant = max(b * b - 4.0 * a * c, 0.0) - roots = [ - value - for value in ( - (-b - math.sqrt(discriminant)) / max(2.0 * a, 1e-6), - (-b + math.sqrt(discriminant)) / max(2.0 * a, 1e-6), - ) - if 0.0 <= value <= 1.0 - ] - clipped.append(anchor + (max(roots) if roots else 0.0) * ray) - return np.asarray(clipped, dtype=np.float64) - - -def medium_motion_region( - shoulder: np.ndarray, - upper_length: float, - forearm_length: float, - bbox: list[float], - track: EndpointTrack, - short_occlusion_frames: int, -) -> dict: - diagonal = bbox_diagonal(bbox) - direction = track_direction(track) - hand_shape = ( - track.hand_shape - if track.hand_shape is not None - else np.zeros((21, 2), dtype=np.float64) - ) - shape_center = np.mean(hand_shape, axis=0) - centered_shape = hand_shape - shape_center - perpendicular = np.asarray([-direction[1], direction[0]], dtype=np.float64) - hand_major = float(np.max(np.abs(centered_shape @ direction))) - hand_minor = float(np.max(np.abs(centered_shape @ perpendicular))) - - extra_frames = max(track.missed_frames - short_occlusion_frames, 1) - motion_major = min(0.20, 0.04 + 0.007 * extra_frames) * diagonal - motion_minor = min(0.13, 0.03 + 0.0045 * extra_frames) * diagonal - major_axis = max(hand_major + motion_major, 0.08 * diagonal) - minor_axis = max(hand_minor + motion_minor, 0.06 * diagonal) - - center = track.wrist + shape_center - tracked_hand_extent = float(np.max(np.linalg.norm(centered_shape, axis=1))) - hand_reach = max(tracked_hand_extent, 0.10 * diagonal) - anatomical_reach = upper_length + forearm_length + hand_reach + 0.02 * diagonal - shoulder_to_center = center - shoulder - center_distance = float(np.linalg.norm(shoulder_to_center)) - if center_distance > 0.96 * anatomical_reach: - center = shoulder + shoulder_to_center / center_distance * 0.96 * anatomical_reach - - polygon = ellipse_polygon(center, major_axis, minor_axis, direction) - polygon = clip_polygon_from_anchor_to_circle( - polygon, - center, - shoulder, - anatomical_reach, - ) - direction_deg = math.degrees(math.atan2(float(direction[1]), float(direction[0]))) - return { - "geometry": "motion_corridor", - "center": np.round(center, 3).tolist(), - "predicted_wrist": np.round(track.wrist, 3).tolist(), - "major_axis_px": round(major_axis, 3), - "minor_axis_px": round(minor_axis, 3), - "outer_radius_px": round(major_axis, 3), - "direction_deg": round(direction_deg, 3), - "max_reach_from_shoulder_px": round(anatomical_reach, 3), - "polygon": np.round(polygon, 3).tolist(), - "requires_door_intersection": True, - "render_full_region": True, - "distance_lower_bound_rule": "minimum distance from motion-corridor polygon to door_edge", - } - - -def long_reachable_region( - shoulder: np.ndarray, - upper_length: float, - forearm_length: float, - bbox: list[float], - track: EndpointTrack, - medium_occlusion_frames: int, -) -> dict: - diagonal = bbox_diagonal(bbox) - height = max(bbox[3] - bbox[1], 1.0) - arm_reach = upper_length + forearm_length - hand_reach = min(0.13 * diagonal, 0.17 * height) - safety_margin = 0.025 * diagonal - outer_radius = arm_reach + hand_reach + safety_margin - inner_radius = max(abs(upper_length - forearm_length) - 0.04 * diagonal, 0.0) - if track.last_direction is None: - direction_deg = 0.0 - half_angle_deg = 180.0 - else: - direction = track_direction(track) - direction_deg = math.degrees(math.atan2(float(direction[1]), float(direction[0]))) - half_angle_deg = min( - 180.0, - 65.0 + 2.0 * max(track.missed_frames - medium_occlusion_frames, 0), - ) - return { - "geometry": "shoulder_sector", - "center": np.round(shoulder, 3).tolist(), - "inner_radius_px": round(inner_radius, 3), - "outer_radius_px": round(outer_radius, 3), - "arm_reach_px": round(arm_reach, 3), - "hand_reach_px": round(hand_reach, 3), - "safety_margin_px": round(safety_margin, 3), - "direction_deg": round(direction_deg, 3), - "half_angle_deg": round(half_angle_deg, 3), - "requires_door_intersection": True, - "render_full_region": False, - "distance_lower_bound_rule": "intersect shoulder sector with door danger band before alarming", - } - - -def reachable_region( - shoulder: np.ndarray, - upper_length: float, - forearm_length: float, - bbox: list[float], - track: EndpointTrack, - state: str = "reachable_long", - short_occlusion_frames: int = 12, - medium_occlusion_frames: int = 30, -) -> dict: - if state == "reachable_medium" and track.wrist is not None: - return medium_motion_region( - shoulder, - upper_length, - forearm_length, - bbox, - track, - short_occlusion_frames, - ) - return long_reachable_region( - shoulder, - upper_length, - forearm_length, - bbox, - track, - medium_occlusion_frames, - ) - - -def calibrated_bone_lengths( - upper_ratio: float, - forearm_ratio: float, - bbox: list[float], -) -> tuple[float, float]: - diagonal = bbox_diagonal(bbox) - height = max(bbox[3] - bbox[1], 1.0) - upper = upper_ratio * diagonal - forearm = forearm_ratio * diagonal - total = upper + forearm - # Keep only a loose sanity cap. The previous 0.55 * bbox-height cap was - # unsafe for close, clipped CMS views: a valid projected arm can occupy - # almost the full detection height. The ratios are already robust medians, - # so this cap only limits catastrophic calibration outliers. - maximum_total = 0.95 * diagonal - if total > maximum_total: - scale = maximum_total / total - upper *= scale - forearm *= scale - return upper, forearm diff --git a/pipeline/pose_tracking.py b/pipeline/pose_tracking.py deleted file mode 100644 index 3021674..0000000 --- a/pipeline/pose_tracking.py +++ /dev/null @@ -1,304 +0,0 @@ -"""Runtime pose tracking helpers.""" -from __future__ import annotations -import math -from collections import deque -from dataclasses import dataclass, field -import numpy as np -from pose_geometry import HAND_SLICES -from pose_fusion import BODY_SIDE_INDICES, PersonTrack -from pose_occlusion import EndpointTrack, Measurement, calibrated_bone_lengths, reachable_region, solve_two_bone_elbow, temporal_recovery_measurement, update_endpoint_track - - -def bbox_iou(a: np.ndarray, b: np.ndarray) -> float: - x1 = max(float(a[0]), float(b[0])) - y1 = max(float(a[1]), float(b[1])) - x2 = min(float(a[2]), float(b[2])) - y2 = min(float(a[3]), float(b[3])) - intersection = max(x2 - x1, 0.0) * max(y2 - y1, 0.0) - union = ( - max(float(a[2] - a[0]), 0.0) * max(float(a[3] - a[1]), 0.0) - + max(float(b[2] - b[0]), 0.0) * max(float(b[3] - b[1]), 0.0) - - intersection - ) - return intersection / max(union, 1.0) - - -class BoxTracker: - def __init__(self, max_age: int = 30): - self.max_age = max_age - self.next_id = 1 - self.tracks: dict[int, dict] = {} - - def update( - self, - boxes: list[list[float]], - frame_idx: int, - image_shape: tuple[int, int], - ) -> list[int]: - height, width = image_shape - image_diagonal = math.hypot(width, height) - active_ids = [ - track_id - for track_id, track in self.tracks.items() - if frame_idx - track["last_frame"] <= self.max_age - ] - self.tracks = {track_id: self.tracks[track_id] for track_id in active_ids} - pairs = [] - for detection_idx, box in enumerate(boxes): - box_array = np.asarray(box, dtype=np.float64) - center = (box_array[:2] + box_array[2:]) * 0.5 - for track_id in active_ids: - prior = np.asarray(self.tracks[track_id]["box"], dtype=np.float64) - prior_center = (prior[:2] + prior[2:]) * 0.5 - iou = bbox_iou(box_array, prior) - center_distance = float(np.linalg.norm(center - prior_center)) / image_diagonal - if iou >= 0.05 or center_distance <= 0.18: - score = 2.0 * iou + max(0.0, 1.0 - center_distance / 0.18) - pairs.append((score, detection_idx, track_id)) - - assigned_detections = set() - assigned_tracks = set() - result: list[int | None] = [None] * len(boxes) - for _, detection_idx, track_id in sorted(pairs, reverse=True): - if detection_idx in assigned_detections or track_id in assigned_tracks: - continue - assigned_detections.add(detection_idx) - assigned_tracks.add(track_id) - result[detection_idx] = track_id - - for detection_idx, box in enumerate(boxes): - track_id = result[detection_idx] - if track_id is None: - track_id = self.next_id - self.next_id += 1 - result[detection_idx] = track_id - self.tracks[track_id] = {"box": box, "last_frame": frame_idx} - return [int(track_id) for track_id in result] - - -@dataclass -class LivePersonState: - fusion: PersonTrack = field(default_factory=PersonTrack) - endpoints: dict[str, EndpointTrack] = field( - default_factory=lambda: {"left": EndpointTrack(), "right": EndpointTrack()} - ) - bone_samples: dict[str, deque] = field( - default_factory=lambda: { - "left": deque(maxlen=120), - "right": deque(maxlen=120), - } - ) - last_frame: int = -1 - - -def bbox_diagonal(bbox: list[float]) -> float: - return max(math.hypot(bbox[2] - bbox[0], bbox[3] - bbox[1]), 1.0) - - -def update_bone_samples( - state: LivePersonState, - keypoints: np.ndarray, - metadata: dict, - bbox: list[float], -) -> None: - diagonal = bbox_diagonal(bbox) - for side, (shoulder, elbow, wrist) in BODY_SIDE_INDICES.items(): - meta = metadata[side] - if meta["limb_state"] != "consensus" or not meta["distance_eligible"]: - continue - upper = float(np.linalg.norm(keypoints[shoulder] - keypoints[elbow])) / diagonal - forearm = float(np.linalg.norm(keypoints[elbow] - keypoints[wrist])) / diagonal - if 0.04 <= upper <= 0.65 and 0.04 <= forearm <= 0.65: - state.bone_samples[side].append((upper, forearm)) - - -def current_bone_ratios(state: LivePersonState, side: str) -> tuple[float, float]: - samples = state.bone_samples[side] - if len(samples) < 10: - opposite = "right" if side == "left" else "left" - if len(state.bone_samples[opposite]) >= 10: - samples = state.bone_samples[opposite] - if not samples: - return 0.24, 0.22 - values = np.asarray(samples, dtype=np.float64) - return float(np.median(values[:, 0])), float(np.median(values[:, 1])) - - -def update_live_v2( - frame_idx: int, - bbox: list[float], - selected_keypoints: np.ndarray, - smoothed_keypoints: np.ndarray, - fusion_metadata: dict, - state: LivePersonState, - assigned_measurements: dict[str, Measurement | None] | None, - identity_assignments: dict[str, dict] | None, - short_occlusion_frames: int, - medium_occlusion_frames: int, -) -> tuple[np.ndarray, dict]: - frame_gap = 0 if state.last_frame < 0 else max(frame_idx - state.last_frame - 1, 0) - for endpoint in state.endpoints.values(): - endpoint.missed_frames += frame_gap - state.last_frame = frame_idx - update_bone_samples(state, selected_keypoints, fusion_metadata, bbox) - - output = smoothed_keypoints.copy() - output_metadata = {} - diagonal = bbox_diagonal(bbox) - for side, (shoulder_idx, elbow_idx, wrist_idx) in BODY_SIDE_INDICES.items(): - endpoint = state.endpoints[side] - meta = fusion_metadata[side] - start, end = HAND_SLICES[side] - measurement = ( - assigned_measurements.get(side) - if assigned_measurements is not None - else None - ) - if assigned_measurements is None and meta["alarm_eligible"]: - measurement = Measurement( - keypoints=selected_keypoints[start:end], - precise=bool(meta["distance_eligible"]), - provisional=not bool(meta["distance_eligible"]), - quality=float(meta.get("selected_hand_score", 0.0)), - ) - if measurement is None: - measurement = temporal_recovery_measurement( - selected_keypoints, - meta, - side, - bbox, - endpoint, - medium_occlusion_frames, - ) - returning_after_long = ( - measurement is not None - and endpoint.missed_frames > short_occlusion_frames - ) - wrist, hand, endpoint_state = update_endpoint_track( - endpoint, - measurement, - frame_idx, - short_occlusion_frames, - medium_occlusion_frames, - ) - shoulder = smoothed_keypoints[shoulder_idx] - upper_ratio, forearm_ratio = current_bone_ratios(state, side) - upper_length, forearm_length = calibrated_bone_lengths( - upper_ratio, forearm_ratio, bbox - ) - region = None - if wrist is not None and hand is not None: - reference_elbow = ( - selected_keypoints[elbow_idx] - if returning_after_long or endpoint.last_elbow is None - else endpoint.last_elbow - ) - use_ik = ( - meta["limb_state"] != "consensus" - or endpoint_state - in ("provisional", "recovered", "reassigned", "predicted_short") - ) - elbow = ( - solve_two_bone_elbow( - shoulder, - wrist, - upper_length, - forearm_length, - reference_elbow, - ) - if use_ik - else smoothed_keypoints[elbow_idx] - ) - endpoint.last_elbow = elbow.copy() - direction = wrist - shoulder - if float(np.linalg.norm(direction)) > 1e-6: - endpoint.last_direction = direction / np.linalg.norm(direction) - output[shoulder_idx] = shoulder - output[elbow_idx] = elbow - output[wrist_idx] = wrist - output[start:end] = hand - uncertainty = ( - 0.025 * diagonal - if endpoint_state == "observed" - else (0.04 + 0.015 * endpoint.missed_frames) * diagonal - ) - precise = endpoint_state == "observed" - else: - region = reachable_region( - shoulder, - upper_length, - forearm_length, - bbox, - endpoint, - endpoint_state, - short_occlusion_frames, - medium_occlusion_frames, - ) - uncertainty = region["outer_radius_px"] - precise = False - output_metadata[side] = { - "state": endpoint_state, - "precise_distance": precise, - "fast_alarm_candidate": True, - "risk_mode": ( - "precise_hand" - if endpoint_state == "observed" - else ( - "motion_corridor_intersection" - if endpoint_state == "reachable_medium" - else ( - "door_intersection_only" - if endpoint_state == "reachable_long" - else ( - "reassigned_hand" - if endpoint_state == "reassigned" - else "temporal_hand" - ) - ) - ) - ), - "requires_door_intersection": endpoint_state - in ("reachable_medium", "reachable_long"), - "identity_assignment": ( - identity_assignments.get(side) - if identity_assignments is not None - else None - ), - "missed_frames": endpoint.missed_frames, - "uncertainty_radius_px": round(float(uncertainty), 3), - "bone_lengths_px": { - "upper": round(upper_length, 3), - "forearm": round(forearm_length, 3), - }, - "reachable_region": region, - } - return output, output_metadata - - -def mask_other_people( - frame: np.ndarray, - boxes: list[list[float]], - target_idx: int, -) -> np.ndarray: - masked = frame.copy() - height, width = frame.shape[:2] - mask = np.zeros((height, width), dtype=bool) - target = boxes[target_idx] - target_area = max((target[2] - target[0]) * (target[3] - target[1]), 1.0) - for idx, bbox in enumerate(boxes): - if idx == target_idx: - continue - other_area = max((bbox[2] - bbox[0]) * (bbox[3] - bbox[1]), 1.0) - # The failure in this video is the small background person being - # absorbed by the much larger foreground person's 3:4 pose crop. - # Mask only markedly smaller people. Masking a larger occluder while - # estimating a small person removes genuine target pixels at overlap. - if other_area >= 0.5 * target_area: - continue - x1, y1, x2, y2 = (int(round(value)) for value in bbox) - x1, x2 = max(0, x1), min(width - 1, x2) - y1, y2 = max(0, y1), min(height - 1, y2) - mask[y1:y2 + 1, x1:x2 + 1] = True - - masked[mask] = (124, 116, 104) - return masked diff --git a/pipeline/visualization.py b/pipeline/visualization.py deleted file mode 100644 index 9530ef4..0000000 --- a/pipeline/visualization.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Runtime visualization helpers.""" -from __future__ import annotations -import cv2 -import numpy as np - - -def percentile(values: list[float], p: float) -> float | None: - return round(float(np.percentile(values, p)), 3) if values else None - - -def timing_stats(values: list[float]) -> dict: - return { - "count": len(values), - "total": round(float(np.sum(values)), 3) if values else 0.0, - "mean": round(float(np.mean(values)), 3) if values else None, - "median": percentile(values, 50), - "p95": percentile(values, 95), - } - - -def visualization_skeletons(record: dict) -> list[dict]: - """Return the exact fused keypoints and metadata consumed by draw_fused.""" - - boxes = record.get("boxes", []) - track_ids = [int(value) for value in record.get("track_ids", [])] - keypoints = record.get("temporal_selected_keypoints", []) - metadata = record.get("temporal_metadata", []) - if not (len(boxes) == len(keypoints) == len(metadata)): - return [] - return [ - { - "track_id": track_ids[position] if position < len(track_ids) else position + 1, - "person_index": position, - "layout": "coco_wholebody_133", - "keypoints": person_keypoints, - "metadata": metadata[position], - } - for position, person_keypoints in enumerate(keypoints) - ] - - -def render_frame( - frame: np.ndarray, - record: dict, - frame_risk: dict, - door_mask: np.ndarray | None, - door_assignments: list[dict], -) -> np.ndarray: - canvas = frame.copy() - boxes = record.get("boxes", []) - keypoints = record.get("temporal_selected_keypoints", []) - metadata = record.get("temporal_metadata", []) - if len(boxes) == len(keypoints) == len(metadata) and boxes: - from pose_fusion import draw_fused - - canvas = draw_fused(canvas, boxes, keypoints, metadata, "YOLO + RTMW risk") - else: - for position, box in enumerate(boxes): - x1, y1, x2, y2 = [int(round(float(value))) for value in box[:4]] - cv2.rectangle(canvas, (x1, y1), (x2, y2), (0, 220, 255), 2) - cv2.putText( - canvas, - f"P{position + 1}", - (x1 + 4, max(22, y1 + 24)), - cv2.FONT_HERSHEY_SIMPLEX, - 0.7, - (0, 220, 255), - 2, - cv2.LINE_AA, - ) - zone_colors = { - "front": (0, 220, 255), - "rear": (255, 180, 0), - "transition": (0, 140, 255), - "unassigned": (150, 150, 150), - } - for assignment in door_assignments: - position = int(assignment["person_index"]) - if not 0 <= position < len(boxes): - continue - box = boxes[position] - x1, y1 = int(round(float(box[0]))), int(round(float(box[1]))) - zone = str(assignment["door_zone"]) - cv2.putText( - canvas, - f"T{assignment['track_id']} {zone.upper()}", - (max(0, x1 + 4), max(24, y1 - 8)), - cv2.FONT_HERSHEY_SIMPLEX, - 0.68, - zone_colors.get(zone, (160, 160, 160)), - 2, - cv2.LINE_AA, - ) - if door_mask is not None and np.any(door_mask): - overlay = np.zeros_like(canvas) - overlay[door_mask > 0] = (255, 180, 0) - canvas = cv2.addWeighted(canvas, 1.0, overlay, 0.22, 0) - - score = frame_risk.get("risk_score") - score_text = "N/A" if score is None else f"{float(score):.3f}" - level = str(frame_risk.get("risk_level", "unavailable")) - color = (0, 0, 255) if level == "danger" else (0, 180, 255) - if level in ("clear", "unavailable", "inactive", "unknown"): - color = (40, 210, 80) if level == "clear" else (160, 160, 160) - cv2.rectangle(canvas, (12, 55), (490, 95), (0, 0, 0), -1) - cv2.putText( - canvas, - f"{frame_risk.get('risk_type', 'DOOR_RISK')} score={score_text} level={level}", - (24, 83), - cv2.FONT_HERSHEY_SIMPLEX, - 0.68, - color, - 2, - cv2.LINE_AA, - ) - return canvas diff --git a/run_camera.py b/run_camera.py deleted file mode 100644 index 3d53b6d..0000000 --- a/run_camera.py +++ /dev/null @@ -1,300 +0,0 @@ -#!/usr/bin/env python3 -"""Live baseline V4L2/CAN capture + native TensorRT + CMS v9.10 risk.""" -from __future__ import annotations -import argparse -from collections import Counter, deque -from contextlib import ExitStack -import copy -import json -import signal -import sys -import threading -import time -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from pathlib import Path -import cv2 -import numpy as np -ROOT=Path(__file__).resolve().parent -sys.path.insert(0,str(ROOT/'pipeline')) -from native_backend import NativeBackend -from pose_engine import NativePoseEngine -from latest_result import LatestResult, result_to_dict -from door_mask_risk import DoorMaskSequence, DoorRiskEvaluator -from door_zone_risk import DoorZoneConfig, DoorZoneAssigner, build_door_risks -from visualization import render_frame, visualization_skeletons, timing_stats - - -def select_active_door(mode, snapshot, side='left', timeout_ms=1000): - if mode!='can': return mode, True - p=snapshot['positions'];offset=0 if side=='left' else 1 - if snapshot['age_ms']<0 or snapshot['age_ms']>timeout_ms or min(p[offset],p[offset+2])<0: - return 'none',False - front,rear=p[offset]==0,p[offset+2]==0 - return ('both' if front and rear else 'front' if front else 'rear' if rear else 'none'),True - - -def gate_risks(raw, assignments, active, can_valid): - doors,risk=build_door_risks(raw,assignments,active) - if not can_valid: - for door,item in doors.items(): - item.update(risk_type=door.upper()+'_UNKNOWN',risk_level='unknown',risk_score=None,decision='CAN_UNAVAILABLE_OR_STALE') - risk=dict(risk,risk_type='CAN_UNKNOWN',risk_level='unknown',risk_score=None,decision='CAN_UNAVAILABLE_OR_STALE') - return doors,risk - - -class JsonlWriter: - """Bound the disk usage of continuous camera runs (current + two backups).""" - def __init__(self,path,max_bytes): - self.path=path;self.max_bytes=max_bytes;self.size=0 - self.handle=path.open('w',encoding='utf-8',buffering=1) - def __enter__(self):return self - def __exit__(self,*args):self.close() - def close(self):self.handle.close() - def write(self,text): - size=len(text.encode('utf-8')) - if self.max_bytes and self.size and self.size+size>self.max_bytes: - self.handle.close() - backup=self.path.with_suffix(self.path.suffix+'.1') - if backup.exists():backup.replace(self.path.with_suffix(self.path.suffix+'.2')) - self.path.replace(backup) - self.handle=self.path.open('w',encoding='utf-8',buffering=1);self.size=0 - self.handle.write(text);self.size+=size - - -def render_output(frame, record, risk, mask, assignments, status): - canvas = render_frame(frame, record, risk, mask, assignments) - cv2.putText(canvas,status,(24,frame.shape[0]-30),cv2.FONT_HERSHEY_SIMPLEX,.8,(0,180,255),2) - return canvas - - -class Preview: - def __init__(self,host,port,results): - self.jpeg=None;self.results=results;self.lock=threading.Lock() - self.pending=threading.Condition();self.packet=None;self.stopped=False - outer=self - class Handler(BaseHTTPRequestHandler): - def do_GET(self): - with outer.lock: - if self.path.split('?')[0]=='/frame.jpg': payload=outer.jpeg or b'';mime='image/jpeg' - elif self.path=='/result.json':payload=json.dumps(result_to_dict(outer.results.load()) or {},ensure_ascii=False,allow_nan=False).encode();mime='application/json' - else: - payload=b'''Vision anti-pinch 0923

    Vision anti-pinch 0923 / Live camera

    ''';mime='text/html; charset=utf-8' - self.send_response(200 if payload else 503);self.send_header('Content-Type',mime);self.send_header('Cache-Control','no-store');self.send_header('Content-Length',str(len(payload)));self.end_headers() - try:self.wfile.write(payload) - except (BrokenPipeError,ConnectionResetError):pass - def log_message(self,*args):pass - self.server=ThreadingHTTPServer((host,port),Handler) - self.thread=threading.Thread(target=self.server.serve_forever,daemon=True);self.thread.start() - self.renderer=threading.Thread(target=self._render_loop,name='risk-preview',daemon=True) - self.renderer.start() - def submit(self, frame, record, risk, mask, assignments, status): - # All inputs belong to a finished frame; inference never modifies them - # after submission. Retain only one pending frame, replacing older work. - with self.pending: - if not self.stopped: - self.packet=(frame,record,risk,mask,assignments,status) - self.pending.notify() - - def _render_loop(self): - try: - while True: - with self.pending: - self.pending.wait_for(lambda:self.packet is not None or self.stopped) - if self.stopped:return - packet=self.packet;self.packet=None - self.update(render_output(*packet)) - except Exception as exc: - with self.pending:self.stopped=True;self.packet=None - with self.lock:self.jpeg=None - print('Visualization disabled; inference continues: '+str(exc),file=sys.stderr,flush=True) - - def update(self,image): - w=960;h=round(image.shape[0]*w/image.shape[1]);image=cv2.resize(image,(w,h)) - ok,jpeg=cv2.imencode('.jpg',image,[cv2.IMWRITE_JPEG_QUALITY,80]) - if ok: - with self.lock:self.jpeg=jpeg.tobytes() - def close(self): - with self.pending:self.stopped=True;self.packet=None;self.pending.notify_all() - self.renderer.join() - self.server.shutdown();self.server.server_close();self.thread.join() - - -def parse_args(argv=None): - p=argparse.ArgumentParser(description=__doc__) - p.add_argument('--video',type=Path,help='Diagnostic video replay through the same TensorRT/risk chain');p.add_argument('--camera',default='/dev/video5');p.add_argument('--width',type=int,default=1920);p.add_argument('--height',type=int,default=1536);p.add_argument('--fps',type=int,default=30) - p.add_argument('--engines',type=Path,default=ROOT/'orin/models/engines' if (ROOT/'orin').is_dir() else ROOT/'models/engines') - p.add_argument('--constant-door-mask',type=Path,default=ROOT/'annotations/constant_door_mask.json') - p.add_argument('--door-zone-config',type=Path,default=ROOT/'annotations/door_zone_multifeature.json') - p.add_argument('--active-door',choices=['can','front','rear','both','none'],default='can') - p.add_argument('--can-side',choices=['left','right'],default='left');p.add_argument('--can-timeout-ms',type=float,default=1000);p.add_argument('--no-can',action='store_true') - p.add_argument('--max-people',type=int,default=3);p.add_argument('--det-conf',type=float,default=.30) - p.add_argument('--max-frames',type=int,default=0);p.add_argument('--duration',type=float,default=0) - p.add_argument('--jsonl-max-mb',type=float,default=32,help='Rotate each JSONL at this size; keep two backups; 0 disables rotation');p.add_argument('--output',type=Path,default=ROOT/'outputs'/time.strftime('%Y%m%d-%H%M%S')) - p.add_argument('--visualize',action='store_true',help='Enable optional HTTP visualization; disabled by default') - p.add_argument('--save-results',action='store_true',help='Save per-frame JSONL and periodic latest.json') - p.add_argument('--save-snapshots',action='store_true',help='Render and save latest.jpg every 10 seconds') - p.add_argument('--preview-port',type=int,default=8765);p.add_argument('--preview-host',default='0.0.0.0');p.add_argument('--preview-every',type=int,default=3) - p.add_argument('--write-video',action='store_true');p.add_argument('--save-pose-records',action='store_true');p.add_argument('--calibration-verified',action='store_true') - a=p.parse_args(argv) - if not 1<=a.max_people<=16 or not 0=a.max_frames) or (a.duration and time.monotonic()-run_started>=a.duration):break - if video: - ok,frame=video.read() - if not ok:break - capture={'sequence':count,'capture_monotonic_ms':time.monotonic()*1000,'dropped_before_inference':0,'latency_clock':'video_decode_complete'} - else:frame,capture=native.read() - if frame is None:break - tick=time.monotonic();record=pose.infer(frame,count);pose_ms.append((time.monotonic()-tick)*1000) - rt=time.monotonic();snapshot=native.can_snapshot();active,valid=select_active_door(a.active_door,snapshot,a.can_side,a.can_timeout_ms) - assignments=assigner.update(count,record,(a.height,a.width));raw,mask=evaluator.evaluate(count,record,(a.height,a.width)) - doors,risk=gate_risks(raw,assignments,active,valid);risk_ms.append((time.monotonic()-rt)*1000) - result={'frame_index':count,'track_ids':record['track_ids'],'boxes':record['boxes'],'skeletons':visualization_skeletons(record),'active_door':active,'door_assignments':assignments,'door_risks':doors,'risk':risk,'ungated_risk':raw,'capture':capture,'can':dict(snapshot,valid=valid,source='hardware' if a.active_door=='can' else 'simulation',side=a.can_side),'calibration_verified':a.calibration_verified} - result['capture_to_result_ms']=round(time.monotonic()*1000-capture['capture_monotonic_ms'],3) - results.publish(result) - if out:out.write(json.dumps(result,ensure_ascii=False,allow_nan=False)+'\n') - if pose_out:pose_out.write(json.dumps(record,ensure_ascii=False,allow_nan=False)+'\n') - now=time.monotonic() - snapshot_due=now-last_snapshot>=10 - if a.save_results and snapshot_due: - (a.output/'latest.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)) - status=('CAN LIVE' if a.active_door=='can' else 'CAN SIMULATION: '+active)+' | '+('CALIBRATED' if a.calibration_verified else 'CALIBRATION UNVERIFIED') - if preview and count%a.preview_every==0: - preview.submit(frame,record,risk,mask,assignments,status) - if writer or (a.save_snapshots and snapshot_due): - canvas=render_output(frame,record,risk,mask,assignments,status) - if writer:writer.write(canvas) - if a.save_snapshots and snapshot_due: - cv2.imwrite(str(a.output/'latest.jpg'),canvas) - if snapshot_due:last_snapshot=now - count+=1;dropped+=capture['dropped_before_inference'];levels[risk['risk_level']]+=1;ages.append(result['capture_to_result_ms']);latencies.append((time.monotonic()-tick)*1000) - if now-last_report>=5: - print(json.dumps({'frames':count,'fps':round(count/(now-run_started),2),'people':len(record['boxes']),'risk':risk['risk_type'],'latency_ms':round(float(np.mean(latencies)),1),'capture_age_ms':round(float(np.mean(ages)),1),'can_valid':valid}),flush=True);last_report=now - finally: - if pose_out:pose_out.close() - except Exception as exc: - error=str(exc);raise - finally: - ended=time.monotonic() - if video:video.release() - if native:native.close() - if writer:writer.release() - if preview:preview.close() - elapsed=ended-(run_started or started) - summary={'mode':'baseline_v4l2_can_native_trt_cms_v9_10','camera':a.camera if not a.video else None,'video':str(a.video) if a.video else None,'frames':count,'elapsed_seconds':round(elapsed,3),'throughput_fps':round(count/max(elapsed,.001),3),'dropped_latest_frames':dropped,'risk_levels':dict(levels),'active_door_mode':a.active_door,'can_side':a.can_side,'calibration_verified':a.calibration_verified,'visualize_requested':a.visualize,'save_results':a.save_results,'save_snapshots':a.save_snapshots,'latency_window_frames':3000,'latency_ms':{'total':timing_stats(list(latencies)),'pose':timing_stats(list(pose_ms)),'risk':timing_stats(list(risk_ms)),'capture_to_result':timing_stats(list(ages))},'error':error} - if summary_sink is not None:summary_sink.update(summary) - if save_any:(a.output/'summary.json').write_text(json.dumps(summary,ensure_ascii=False,indent=2)) - print(json.dumps(summary,ensure_ascii=False),flush=True) - -class CameraPipeline: - """One lifecycle per instance, with the 0920 start/latest/stop/join pattern. - - The worker owns inference. Downstream threads read the latest immutable - snapshot directly, without JSON files, HTTP, callbacks or a result queue. - """ - def __init__(self, args=None): - self.args = copy.deepcopy(args if args is not None else parse_args([])) - self._results = LatestResult() - self._stop = threading.Event() - self._lifecycle_lock = threading.Lock() - self._thread = None - self._error = None - self.summary = {} - - @property - def latest_result(self): - """The latest complete result variable, or None before the first frame.""" - return self._results.load() - - def latest(self): - return self.latest_result - - def wait_for_result(self, after_frame_index=-1, timeout=None): - return self._results.wait(after_frame_index, timeout) - - def start(self): - with self._lifecycle_lock: - if self._thread is not None: - raise RuntimeError('CameraPipeline can only be started once') - self._thread = threading.Thread(target=self._run, name='vision-risk') - self._thread.start() - return self - - def _run(self): - try: - run_pipeline(self.args, self._results, self._stop, self.summary) - except BaseException as exc: - self._error = exc - finally: - self._results.close() - - def running(self): - return self._thread is not None and self._thread.is_alive() - - def request_stop(self): - self._stop.set() - - def join(self, timeout=None): - if self._thread is not None: - self._thread.join(timeout) - if not self._thread.is_alive() and self._error is not None: - raise self._error - - def __enter__(self): - return self.start() - - def __exit__(self, *args): - self.request_stop() - self.join() - - -def main(): - pipeline=CameraPipeline(parse_args()) - for sig in (signal.SIGINT,signal.SIGTERM): - signal.signal(sig,lambda *_:pipeline.request_stop()) - pipeline.start() - # Periodic joins allow Python to deliver stop signals on the main thread. - while pipeline.running(): - pipeline.join(.2) - pipeline.join() - - -if __name__=='__main__':main() diff --git a/run_camera.sh b/run_camera.sh index 7fd59bc..730ce59 100755 --- a/run_camera.sh +++ b/run_camera.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash set -euo pipefail root="$(cd "$(dirname "$0")" && pwd)" -export OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=2 export LD_LIBRARY_PATH="$root/orin/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" -exec "${PYTHON:-python3}" -u "$root/run_camera.py" "$@" +exec "$root/build/camera_risk" --config "$root/config/camera.json" "$@" diff --git a/scripts/package_orin.sh b/scripts/package_orin.sh index 21f5b6e..4a407e3 100755 --- a/scripts/package_orin.sh +++ b/scripts/package_orin.sh @@ -1,26 +1,18 @@ #!/usr/bin/env bash -# Refresh the standalone runtime after compiling on the target Orin. +# Assemble the C++ runtime; no Python interpreter is used for packaging or execution. set -euo pipefail root="$(cd "$(dirname "$0")/.." && pwd)" -python3 - "$root" <<'PY' -from pathlib import Path -import hashlib,shutil,sys -root=Path(sys.argv[1]);package=root/'orin' -library=root/'build/libvision_native.so' -if not library.is_file():raise SystemExit('Run scripts/build.sh on Orin first.') -# Replace generated Python directories so removed modules cannot survive packaging. -for name in ('pipeline','annotations'): - target=package/name - if target.exists():shutil.rmtree(target) - shutil.copytree(root/name,target,ignore=shutil.ignore_patterns('__pycache__','*.pyc')) -(package/'build').mkdir(exist_ok=True) -shutil.copy2(library,package/'build/libvision_native.so') -shutil.copy2(root/'run_camera.py',package/'run_camera.py') -shutil.copy2(root/'scripts/initialize_camera.sh',package/'scripts/initialize_camera_impl.sh') -for name in ('lib/libusbcanfd.so','lib/libusb-1.0.so','models/engines/yolo11s.engine','models/engines/rtmw_l_384.engine','bin/drivers/max9295.ko','bin/drivers/max9296.ko','bin/drivers/sgx-yuv-gmsl2.ko'): - if not (package/name).is_file():raise SystemExit('Missing packaged dependency: '+name) -ignored={'__pycache__','outputs','logs'} -files=sorted(p for p in package.rglob('*') if p.is_file() and not p.is_symlink() and p.name!='SHA256SUMS' and not ignored.intersection(p.relative_to(package).parts)) -(package/'SHA256SUMS').write_text(''.join(hashlib.sha256(p.read_bytes()).hexdigest()+' '+p.relative_to(package).as_posix()+'\n' for p in files)) -print('Package ready:',package/'run.sh') -PY +package="$root/orin" +[[ -x "$root/build/camera_risk" ]] || { echo 'Compile on Orin with scripts/build.sh first.' >&2; exit 1; } +mkdir -p "$package/build" "$package/config" "$package/annotations" "$package/scripts" +cp -p "$root/build/camera_risk" "$package/build/" +cp -p "$root/annotations/"*.json "$package/annotations/" +# Source and standalone configurations differ only in the engine directory. +sed 's|../orin/models/engines|../models/engines|' "$root/config/camera.json" > "$package/config/camera.json" +cp -p "$root/scripts/initialize_camera.sh" "$package/scripts/initialize_camera_impl.sh" +( + cd "$package" + find . -type f ! -path './outputs/*' ! -path './logs/*' ! -name SHA256SUMS -print0 | + sort -z | xargs -0 sha256sum > SHA256SUMS +) +echo "C++ runtime ready: $package/build/camera_risk" diff --git a/src/camera.cpp b/src/camera.cpp new file mode 100644 index 0000000..6aec642 --- /dev/null +++ b/src/camera.cpp @@ -0,0 +1,66 @@ +#include "camera.hpp" +#include "v4l2_capture.h" +#include "uyvy_converter.hpp" +#include +#include +#include +#include +#include +namespace cms { +std::uint64_t monotonicNs(){return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count();} +struct Camera::Impl { + CameraConfig config; + V4l2Capture capture; + cv::VideoCapture video; + std::unique_ptr converter; + std::atomic stopped{false}; + std::uint64_t index{}; + std::uint32_t previousSequence{}; + bool haveSequence{}; + explicit Impl(CameraConfig c):config(std::move(c)) { + if(!config.video.empty()) { + if(!video.open(config.video))throw std::runtime_error("Cannot open video: "+config.video); + if(int(video.get(cv::CAP_PROP_FRAME_WIDTH))!=config.width||int(video.get(cv::CAP_PROP_FRAME_HEIGHT))!=config.height)throw std::runtime_error("Video dimensions must match camera configuration"); + } else { + if(!capture.open(config.device)||!capture.setFormat(config.width,config.height,V4L2_PIX_FMT_UYVY))throw std::runtime_error("Camera open/format failed"); + if(capture.width()!=unsigned(config.width)||capture.height()!=unsigned(config.height))throw std::runtime_error("Unexpected negotiated camera dimensions"); + capture.setFps(config.fps); + if(!capture.initMmap(config.buffers)||!capture.startStreaming())throw std::runtime_error("Camera streaming failed"); + converter=std::make_unique(config.width,config.height); + } + } + std::optional read() { + if(stopped)return {}; + if(video.isOpened()) { + cv::Mat bgr;if(!video.read(bgr))return {}; + auto image=std::make_shared();image->width=bgr.cols;image->height=bgr.rows;image->stride=bgr.cols*3; + image->pixels.resize(std::size_t(image->stride)*image->height); + for(int y=0;ypixels.data()+y*image->stride,bgr.ptr(y),image->stride); + CaptureMetadata m{};m.frame_index=index;m.driver_sequence=index++;m.capture_monotonic_ns=m.received_monotonic_ns=monotonicNs();return CapturedFrame{m,image}; + } + auto started=monotonicNs();unsigned misses=0; + while(!stopped) { + V4l2Capture::Frame f; + if(!capture.getFrame(f,100)) { + ++misses; + if((monotonicNs()-started)*1e-6>=config.timeout_ms)throw std::runtime_error("Camera timeout or repeated invalid frames"); + continue; + } + CaptureMetadata m{};m.frame_index=index++;m.driver_sequence=f.sequence; + m.received_monotonic_ns=m.capture_monotonic_ns=monotonicNs();m.driver_timestamp_ns=f.timestamp_us*1000;m.discarded_error_frames=misses; + if(haveSequence){auto delta=std::uint32_t(f.sequence-previousSequence);if(delta>0&&delta<0x80000000U)m.skipped_driver_frames=delta-1;} + previousSequence=f.sequence;haveSequence=true; + auto stride=f.bytes_per_line?f.bytes_per_line:unsigned(config.width*2); + try { + if(f.sizeconvert(f.data,stride);capture.releaseFrame(f);return CapturedFrame{m,std::move(image)}; + }catch(...){capture.releaseFrame(f);throw;} + } + return {}; + } +}; +Camera::Camera(CameraConfig c):impl_(std::make_unique(std::move(c))){} +Camera::~Camera()=default; +std::optional Camera::read(){return impl_->read();} +void Camera::requestStop()noexcept{impl_->stopped=true;} +} diff --git a/src/camera_pipeline.cpp b/src/camera_pipeline.cpp new file mode 100644 index 0000000..d060a52 --- /dev/null +++ b/src/camera_pipeline.cpp @@ -0,0 +1,213 @@ +#include "camera_pipeline.hpp" +#include "bounded_queue.hpp" +#include "latest_result.hpp" +#include "detector.hpp" +#include "door_risk.hpp" +#include "records.hpp" +#include "result_conversion.hpp" +#include "door_zone.hpp" +#include "can_state.hpp" +#include "serialization.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cms { +namespace { +struct Stream { + cudaStream_t value{}; + Stream(){CUDA_CHECK(cudaStreamCreateWithFlags(&value,cudaStreamNonBlocking));} + ~Stream(){cudaStreamDestroy(value);} +}; +cv::Mat imageView(BgrImage const& image) { + return cv::Mat(image.height,image.width,CV_8UC3,const_cast(image.pixels.data()),image.stride); +} +double elapsedMs(std::uint64_t start){return (monotonicNs()-start)*1e-6;} +void attachPoses(FrameResult& result,fangjia::PoseFrameResult const& fused) { + for(std::size_t i=0;i boxes;double elapsed_ms{};}; + PipelineConfig config; + BoundedQueue captures; + BoundedQueue detections; + LatestResult results; + fangjia::TrtLogger logger; + std::unique_ptr detector; + std::unique_ptr pose; + std::unique_ptr risk; + std::unique_ptr zone; + std::unique_ptr can; + std::ofstream output, poseOutput; + std::shared_ptr doorPixels; + std::unique_ptr camera; + std::thread captureThread,detectorThread,inferenceThread; + mutable std::mutex stateMutex; + std::exception_ptr failure; + bool started{}; + std::atomic stopped{false},active{false}; + std::atomic captured{0},published{0},gaps{0},errorFrames{0}; + double totalLatency{}; + + explicit Impl(PipelineConfig c):config(std::move(c)),captures(config.queue_capacity),detections(config.queue_capacity) { + if(config.can_side!="left"&&config.can_side!="right")throw std::invalid_argument("CAN side must be left or right"); + if(config.active_door!="can"&&config.active_door!="front"&&config.active_door!="rear"&&config.active_door!="both"&&config.active_door!="none")throw std::invalid_argument("Invalid active door"); + if(!config.can_enabled&&config.active_door=="can")throw std::invalid_argument("Disabled CAN requires an explicit simulated active door"); + if(config.can_timeout_ms<=0)throw std::invalid_argument("CAN timeout must be positive"); + if(config.queue_capacity>8)throw std::invalid_argument("Queue capacity must be between 1 and 8"); + if(config.max_people<1||config.max_people>16||!std::isfinite(config.detector_confidence)||config.detector_confidence<=0||config.detector_confidence>=1) + throw std::invalid_argument("Invalid detector configuration"); + if(!std::filesystem::is_regular_file(config.door_mask))throw std::invalid_argument("Missing door mask: "+config.door_mask.string()); + } + ~Impl(){stop();joinWorkers();} + void joinWorkers() noexcept { + if(captureThread.joinable())captureThread.join(); + if(detectorThread.joinable())detectorThread.join(); + if(inferenceThread.joinable())inferenceThread.join(); + } + void stop() noexcept { + stopped=true;active=false; + // Camera stays alive until all threads have joined. start()/destruction + // are owner-thread operations; requestStop() only interrupts existing work. + {std::lock_guard lock(stateMutex);if(camera)camera->requestStop();} + captures.close(true);detections.close(true); + } + void fail() noexcept { + {std::lock_guard lock(stateMutex);if(!failure)failure=std::current_exception();} + stop(); + } + void rethrowFailure() const { + std::lock_guard lock(stateMutex);if(failure)std::rethrow_exception(failure); + } + void start() { + {std::lock_guard lock(stateMutex);if(started||stopped)throw std::logic_error("CameraPipeline can only be started once");started=true;} + try { + detector=std::make_unique(config.engines/"yolo11s.engine",logger,config.detector_confidence,config.max_people); + pose=std::make_unique(config.engines/"rtmw_l_384.engine",logger,config.camera.width,config.camera.height,config.max_people); + DoorOptions options;options.constant=config.door_mask;options.warning=config.warning_relative;options.release=config.release_relative; + risk=std::make_unique(options,cv::Size(config.camera.width,config.camera.height));risk->initialize(); + if(risk->mask().empty()||cv::countNonZero(risk->mask())==0)throw std::runtime_error("Door mask has no usable region"); + auto pixels=std::make_shared();pixels->width=config.camera.width;pixels->height=config.camera.height;pixels->stride=config.camera.width; + auto const& mask=risk->mask();pixels->pixels.resize(std::size_t(mask.cols)*mask.rows); + for(int y=0;ypixels.data()+std::size_t(y)*mask.cols,mask.ptr(y),mask.cols); + doorPixels=std::move(pixels); + zone=std::make_unique(config.door_zone_config); + auto openOutput=[](std::ofstream& out,std::filesystem::path const& path) { + if(path.empty())return; + if(!path.parent_path().empty())std::filesystem::create_directories(path.parent_path()); + out.exceptions(std::ios::failbit|std::ios::badbit);out.open(path); + }; + openOutput(output,config.results_path);openOutput(poseOutput,config.pose_records_path); + Stream warmup; + pose->warmup(warmup.value); + detector->infer(cv::Mat(config.camera.height,config.camera.width,CV_8UC3,cv::Scalar()),warmup.value); + {std::lock_guard lock(stateMutex);camera=std::make_unique(config.camera);if(stopped)camera->requestStop();} + can=std::make_unique(config.can_enabled); + active=!stopped.load(); + captureThread=std::thread([this]{try{captureLoop();}catch(...){fail();}}); + detectorThread=std::thread([this]{try{detectorLoop();}catch(...){fail();}}); + inferenceThread=std::thread([this]{try{inferenceLoop();}catch(...){fail();}}); + }catch(...){stop();joinWorkers();throw;} + } + void captureLoop() { + while(!stopped) { + auto frame=camera->read();if(!frame)break; + gaps+=frame->metadata.skipped_driver_frames; + errorFrames+=frame->metadata.discarded_error_frames; + bool queued=config.camera.video.empty()?captures.pushLatest(std::move(*frame)):captures.push(std::move(*frame)); + if(!queued)break; + ++captured; + } + captures.close(); + } + void detectorLoop() { + Stream stream; + while(!stopped) { + auto frame=captures.pop();if(!frame)break; + auto startedAt=monotonicNs();auto boxes=detector->infer(imageView(*frame->image),stream.value); + Detected item{std::move(*frame),std::move(boxes),elapsedMs(startedAt)}; + bool queued=config.camera.video.empty()?detections.pushLatest(std::move(item)):detections.push(std::move(item)); + if(!queued)break; + } + detections.close(); + } + void inferenceLoop() { + Stream stream;fangjia::BoxTracker boxesTracker;fangjia::PoseFusionTracker fusion;std::uint64_t processed=0; + while(!stopped) { + auto detected=detections.pop();if(!detected)break; + if(processed>static_cast(std::numeric_limits::max()))throw std::runtime_error("Frame index limit reached; restart pipeline"); + auto index=static_cast(processed++); + auto& boxes=detected->boxes; + auto frame=imageView(*detected->frame.image); + boxesTracker.update(boxes,index,frame.cols,frame.rows); + auto poseStart=monotonicNs(); + auto raw=pose->infer(frame,boxes,stream.value);auto isolated=raw; + for(std::size_t p=0;pinfer(masked,{box},stream.value).at(0);} + } + auto poseElapsed=elapsedMs(poseStart);auto fusionStart=monotonicNs(); + auto fused=fusion.update(raw,isolated,index); + auto record=makeRecord(index,raw,isolated,fused,poseOutput.is_open(),true); + auto riskData=risk->evaluate(index,record); + auto result=std::make_shared(makeFrameResult(record,riskData,detected->frame.metadata)); + auto assignments=zone->update(index,record,frame.size()); + result->can=can->snapshot(config.can_side,config.can_timeout_ms); + result->active_door=activeDoor(result->can); + if(config.active_door!="can") {result->active_door=config.active_door;result->can.source="simulation";result->can.valid=true;} + auto decisions=buildDoorRisks(riskData,assignments,result->active_door,result->can.valid); + result->risk=parseRisk(decisions.selected); + for(auto const& entry:decisions.doors.items())result->door_risks[entry.key()]=parseRisk(entry.value()); + for(auto const& a:assignments)result->door_assignments.push_back(parseAssignment(a)); + attachPoses(*result,fused); + result->image=std::move(detected->frame.image);result->door_mask_pixels=doorPixels; + result->door_mask_calibration_verified=config.door_mask_calibration_verified; + result->timing={detected->elapsed_ms,poseElapsed,elapsedMs(fusionStart),elapsedMs(result->capture.capture_monotonic_ns)}; + FrameResultPtr immutable=result; + results.publish(std::move(immutable)); + {std::lock_guard lock(stateMutex);totalLatency+=result->timing.capture_to_result_ms;++published;} + if(output.is_open())output<=config.max_frames){stop();break;} + } + if(output.is_open())output.flush(); + if(poseOutput.is_open())poseOutput.flush(); + active=false; + } +}; +CameraPipeline::CameraPipeline(PipelineConfig config):impl_(std::make_unique(std::move(config))){} +CameraPipeline::~CameraPipeline()=default; +void CameraPipeline::start(){impl_->start();} +FrameResultPtr CameraPipeline::latest()const{return impl_->results.load();} +bool CameraPipeline::running()const noexcept{return impl_->active.load();} +void CameraPipeline::requestStop()noexcept{impl_->stop();} +void CameraPipeline::join(){impl_->joinWorkers();{std::lock_guard lock(impl_->stateMutex);impl_->camera.reset();impl_->can.reset();}impl_->rethrowFailure();} +PipelineStats CameraPipeline::stats()const { + std::lock_guard lock(impl_->stateMutex);auto count=impl_->published.load(); + return {impl_->captured.load(),count,impl_->gaps.load(),impl_->errorFrames.load(),count?impl_->totalLatency/count:0}; +} +} diff --git a/native/can_comm.cpp b/src/can_comm.cpp similarity index 91% rename from native/can_comm.cpp rename to src/can_comm.cpp index 84b1760..b363b97 100644 --- a/native/can_comm.cpp +++ b/src/can_comm.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "zcan.h" @@ -31,26 +32,27 @@ typedef struct int dev_idx; // 设备索引 int chn_idx; // 通道号 int total; // 接收总数 - int stop; // 线程结束标志 + std::atomic stop{0}; // 线程结束标志 } THREAD_CTX; /* CAN 运行标志, 由信号处理函数(Ctrl+C)或外部置位 */ static volatile bool g_can_running = true; -static int g_receive_count = 0; +static std::atomic g_receive_count{0}; /* ===== 仅供 can_start/can_stop 共享的文件内静态变量 ===== */ static int s_DevType = USBCANFD; static int s_DevIdx = 0; static THREAD_CTX s_rx_ctx[MAX_CHANNELS]; static pthread_t s_rx_threads[MAX_CHANNELS]; +static bool s_thread_started[MAX_CHANNELS]{}; static bool s_can_opened = false; namespace CanComm { bool is_open() { return s_can_opened; } -int receive_count() { return g_receive_count; } +int receive_count() { return g_receive_count.load(); } /* ===== 报文解析回调 (由 main.cpp 通过 set_msg_callback 注册) ===== */ static MsgCallback s_msg_cb = nullptr; @@ -218,7 +220,7 @@ bool can_start() // 初始化,启动通道 for (int i = 0; i < MAX_CHANNELS; i++) { - ZCAN_INIT init; // 波特率结构体,数据根据zcanpro的波特率计算器得出 + ZCAN_INIT init{}; // 波特率结构体,数据根据zcanpro的波特率计算器得出 init.clk = 60000000; // clock: 60M(V1.01) 80M(V1.03即以上) init.mode = 0; // 0-正常 @@ -237,7 +239,7 @@ bool can_start() if (!VCI_InitCAN(s_DevType, s_DevIdx, i, &init)) // 初始化通道 { printf("InitCAN(%d) fail\n", i); - return false; + can_stop();return false; } printf("InitCAN(%d) success\n", i); @@ -258,7 +260,7 @@ bool can_start() if (!VCI_StartCAN(s_DevType, s_DevIdx, i)) // 启动通道 { printf("StartCAN(%d) fail\n", i); - return false; + can_stop();return false; } printf("StartCAN(%d) success\n", i); @@ -270,12 +272,13 @@ bool can_start() if (IsMerge && i == 0) { - pthread_create(&s_rx_threads[i], NULL, rx_merge_thread, &s_rx_ctx[i]); // 合并接收线程 + s_thread_started[i]=pthread_create(&s_rx_threads[i], NULL, rx_merge_thread, &s_rx_ctx[i])==0; // 合并接收线程 } else { - pthread_create(&s_rx_threads[i], NULL, rx_thread, &s_rx_ctx[i]); // 创建接收线程 + s_thread_started[i]=pthread_create(&s_rx_threads[i], NULL, rx_thread, &s_rx_ctx[i])==0; // 创建接收线程 } + if(!s_thread_started[i]){can_stop();return false;} } return true; } @@ -293,7 +296,7 @@ void can_stop() for (int i = 0; i < MAX_CHANNELS; i++) { s_rx_ctx[i].stop = 1; - pthread_join(s_rx_threads[i], NULL); // 等待接收线程退出 + if(s_thread_started[i]){pthread_join(s_rx_threads[i], NULL);s_thread_started[i]=false;} // 等待接收线程退出 if (!VCI_ResetCAN(s_DevType, s_DevIdx, i)) // 复位通道 printf("ResetCAN(%d) fail\n", i); diff --git a/src/can_state.cpp b/src/can_state.cpp new file mode 100644 index 0000000..df0ab66 --- /dev/null +++ b/src/can_state.cpp @@ -0,0 +1,39 @@ +#include "can_state.hpp" +#include "can_comm.h" +#include "camera.hpp" +#include +#include +namespace cms { +namespace { +std::mutex mutex; +std::array positions{{-1,-1,-1,-1}}; +std::uint64_t timestamp{}; +bool owned{}; +void received(int64_t,uint32_t id,uint8_t const* data,uint32_t size) { + if(id!=0x391||size<4)return; + std::lock_guard lock(mutex); + for(int i=0;i<4;++i)positions[i]=data[i]; + timestamp=monotonicNs(); +} +} +CanInput::CanInput(bool enabled) { + {std::lock_guard lock(mutex);if(owned)throw std::logic_error("Only one CAN input may run per process");owned=true;positions.fill(-1);timestamp=0;} + if(enabled){CanComm::set_msg_callback(received);opened_=CanComm::can_start();} +} +CanInput::~CanInput() { + if(opened_)CanComm::can_stop(); + CanComm::set_msg_callback(nullptr); + std::lock_guard lock(mutex);owned=false; +} +CanSnapshot CanInput::snapshot(std::string const& side,double timeoutMs) const { + std::lock_guard lock(mutex);CanSnapshot s;s.positions=positions;s.side=side; + s.age_ms=timestamp?(monotonicNs()-timestamp)*1e-6:-1; + int offset=side=="left"?0:1; + s.valid=s.age_ms>=0&&s.age_ms<=timeoutMs&&s.positions[offset]>=0&&s.positions[offset+2]>=0;return s; +} +std::string activeDoor(CanSnapshot const& s) { + if(!s.valid)return "none"; + int i=s.side=="left"?0:1;bool front=s.positions[i]==0,rear=s.positions[i+2]==0; + return front&&rear?"both":front?"front":rear?"rear":"none"; +} +} diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..6a1db5d --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,29 @@ +#include "config.hpp" +#include "door_risk.hpp" +#include +#include + +namespace cms { +PipelineConfig loadConfig(std::filesystem::path const& path) { + std::ifstream file(path); + if(!file)throw std::runtime_error("Cannot read configuration: "+path.string()); + auto j=Json::parse(file);PipelineConfig config; + auto const& camera=j.at("camera"); + config.camera.device=camera.at("device");config.camera.width=camera.at("width"); + config.camera.height=camera.at("height");config.camera.fps=camera.at("fps"); + config.camera.buffers=camera.at("buffers");config.camera.timeout_ms=camera.at("timeout_ms"); + auto resolve=[&](std::string const& value){auto p=std::filesystem::path(value);return std::filesystem::absolute(p.is_absolute()?p:path.parent_path()/p);}; + auto const& inference=j.at("inference"); + config.engines=resolve(inference.at("engine_directory"));config.max_people=inference.at("max_people"); + config.detector_confidence=inference.at("detector_confidence");config.queue_capacity=inference.at("queue_capacity"); + auto const& door=j.at("door");config.door_mask=resolve(door.at("mask")); + config.warning_relative=door.at("warning_relative");config.release_relative=door.at("release_relative"); + config.door_mask_calibration_verified=door.at("calibration_verified"); + config.door_zone_config=resolve(door.at("zone_config")); + if(j.contains("can")) { + auto const& can=j.at("can");config.can_enabled=can.value("enabled",true); + config.can_side=can.value("side",std::string("left"));config.can_timeout_ms=can.value("timeout_ms",1000.0); + } + return config; +} +} diff --git a/native/detector.cpp b/src/detector.cpp similarity index 83% rename from native/detector.cpp rename to src/detector.cpp index 22c40eb..1298e71 100644 --- a/native/detector.cpp +++ b/src/detector.cpp @@ -56,4 +56,15 @@ std::vector Detector::infer(cv::Mat const& frame,cudaStream_t stream,int fi std::stable_sort(filtered.begin(),filtered.end(),[](auto const&a,auto const&b){return a.x1 const& boxes,size_t target) { + auto masked=frame.clone(); auto const&t=boxes.at(target); + double targetArea=std::max(double(t.x2-t.x1)*(t.y2-t.y1),1.0); + for(size_t i=0;i=0.5*targetArea) continue; + int x1=std::clamp(int(std::nearbyint(b.x1)),0,frame.cols-1),x2=std::clamp(int(std::nearbyint(b.x2)),0,frame.cols-1); + int y1=std::clamp(int(std::nearbyint(b.y1)),0,frame.rows-1),y2=std::clamp(int(std::nearbyint(b.y2)),0,frame.rows-1); + if(x2>=x1&&y2>=y1) cv::rectangle(masked,{x1,y1},{x2,y2},{124,116,104},-1); + } + return masked; +} } diff --git a/src/door_risk.cpp b/src/door_risk.cpp new file mode 100644 index 0000000..fbd8864 --- /dev/null +++ b/src/door_risk.cpp @@ -0,0 +1,137 @@ +#include "door_risk.hpp" +#include +#include +#include +#include +#include +#include +#include +namespace cms { +double rounded(double v, int d) { double s=std::pow(10.0,d); return std::nearbyint(v*s)/s; } +static std::string lower(std::string s) { for(auto& c:s) if(static_cast(c)<128) c=static_cast(std::tolower(c)); return s; } +static bool raster(std::filesystem::path const& p) { auto e=lower(p.extension().string()); return e==".png"||e==".bmp"||e==".tif"||e==".tiff"; } +DoorRisk::DoorRisk(DoorOptions o, cv::Size size):options_(std::move(o)),size_(size) { + if(options_.constant.empty()==options_.directory.empty()) throw std::runtime_error("Provide exactly one constant mask or door-mask directory"); + if(options_.warning<0 || options_.release<=options_.warning) throw std::runtime_error("Risk thresholds require 0 <= warning < release"); + format_=options_.format; + if(!options_.constant.empty()) { + if(!std::filesystem::is_regular_file(options_.constant)) throw std::runtime_error("Missing mask: "+options_.constant.string()); + if(format_=="auto") format_=lower(options_.constant.extension().string())==".json"?"xanylabeling":"raster"; + } else { + std::vector files; + for(auto const& e:std::filesystem::recursive_directory_iterator(options_.directory)) if(e.is_regular_file()) files.push_back(e.path()); + std::sort(files.begin(),files.end()); + if(format_=="auto") format_=std::any_of(files.begin(),files.end(),[](auto const& p){return p.extension()==".json";})?"xanylabeling":"raster"; + std::regex digits("[0-9]+"); + for(auto const& p:files) { + if(format_=="xanylabeling"?p.extension()!=".json":!raster(p)) continue; + int index=-1; std::string stem=p.stem().string(); + for(std::sregex_iterator i(stem.begin(),stem.end(),digits),end;i!=end;++i) index=std::stoi(i->str()); + if(index<0 && format_=="xanylabeling") { std::ifstream in(p); auto j=Json::parse(in); if(j.contains("frame_index")) index=j["frame_index"].get(); else if(j.contains("frameIndex")) index=j["frameIndex"].get(); } + if(index<0) continue; + if(!paths_.emplace(index,p).second) throw std::runtime_error("Duplicate mask frame: "+std::to_string(index)); + } + if(paths_.empty()) throw std::runtime_error("No indexed door masks in "+options_.directory.string()); + } + if(format_!="raster" && format_!="xanylabeling") throw std::runtime_error("Unknown door mask format"); +} +cv::Mat DoorRisk::rasterize(std::filesystem::path const& path) const { + cv::Mat mask(size_,CV_8U,cv::Scalar(0)); + if(format_=="raster") { + auto img=cv::imread(path.string(),cv::IMREAD_UNCHANGED); + if(img.empty()) throw std::runtime_error("Cannot read mask: "+path.string()); + if(img.channels()>1) { std::vector ch; cv::split(img,ch); img=ch[0]; for(size_t i=1;i0; + } + std::ifstream in(path); auto j=Json::parse(in); + double w=j.contains("imageWidth")&&!j["imageWidth"].is_null()?j["imageWidth"].get():size_.width; + double h=j.contains("imageHeight")&&!j["imageHeight"].is_null()?j["imageHeight"].get():size_.height; + if(w==0) w=size_.width; + if(h==0) h=size_.height; + float sx=static_cast(size_.width/std::max(w,1.0)),sy=static_cast(size_.height/std::max(h,1.0)); + for(auto const& s:j.value("shapes",Json::array())) { + if(!options_.labels.count(lower(s.value("label",std::string())))) continue; + std::vector points; + for(auto const& p:s.value("points",Json::array())) points.emplace_back(static_cast(std::nearbyint(p[0].get()*sx)),static_cast(std::nearbyint(p[1].get()*sy))); + auto type=lower(s.value("shape_type",std::string("polygon"))); + if(points.size()<2) continue; + if(type=="rectangle") cv::rectangle(mask,points[0],points[1],255,-1); + else if(type=="circle") cv::circle(mask,points[0],static_cast(std::nearbyint(cv::norm(points[1]-points[0]))),255,-1); + else if(points.size()>=3) cv::fillPoly(mask,std::vector>{points},255); + } + return mask; +} +void DoorRisk::load(int frame) { + if(cached_) return; + loadedPath_.clear(); mask_.release(); distance_.release(); + if(!options_.constant.empty()) loadedPath_=options_.constant; + else { auto it=paths_.find(frame+options_.offset); if(it!=paths_.end()) loadedPath_=it->second; } + if(loadedPath_.empty()) { + status_="missing"; + if(options_.require) throw std::runtime_error("No door mask for frame "+std::to_string(frame)); + return; + } + mask_=rasterize(loadedPath_); + status_=cv::countNonZero(mask_)?"loaded":"empty"; + if(status_=="loaded") cv::distanceTransform(mask_==0,distance_,cv::DIST_L2,cv::DIST_MASK_PRECISE); + cached_=!options_.constant.empty(); +} +Json DoorRisk::evaluate(int frame, Json& record) { + load(frame); + Json regions=Json::array(),fallbacks=Json::array(),nearest=nullptr; + std::set withHands; + auto measure=[&](cv::Mat const& region, std::string const& source,int track,std::string const& side,std::string const& state) { + Json m={{"status","door_mask_"+status_},{"risk_score",nullptr},{"risk_level","unavailable"},{"decision","EVIDENCE_UNAVAILABLE"},{"source",source},{"track_id",track},{"side",side}}; + if(status_=="loaded") { + int area=cv::countNonZero(region); cv::Mat intersection; cv::bitwise_and(region,mask_,intersection); int overlap=cv::countNonZero(intersection); + double distance=0; cv::minMaxLoc(distance_,&distance,nullptr,nullptr,nullptr,region); + double scale=std::max(std::sqrt(double(area)),1.0),relative=distance/scale; + double score=overlap?1:std::clamp(1-relative/options_.release,0.0,1.0); + std::string level=overlap?"danger":relative<=options_.warning?"warning":relative<=options_.release?"watch":"clear"; + std::string decision=overlap?"DANGER_INTERSECTION":relative<=options_.warning?"WARNING_NEAR":relative<=options_.release?"WATCH":"CLEAR"; + m={{"status","calculated"},{"risk_score",rounded(score,5)},{"risk_level",level},{"decision",decision},{"source",source},{"track_id",track},{"side",side},{"state",state},{"region_area_px",area},{"door_overlap_px",overlap},{"region_overlap_ratio",rounded(double(overlap)/std::max(area,1),6)},{"min_distance_px",rounded(distance,3)},{"region_scale_px",rounded(scale,3)},{"distance_relative",rounded(relative,6)}}; + if(nearest.is_null() || m["risk_score"].get()>nearest["risk_score"].get()) nearest=m; + } + regions.push_back(m); return m; + }; + auto const ids=record.value("track_ids",Json::array()); + auto trackFor=[&](int pos){return pos>=0&&pos():pos+1;}; + if(!record.contains("global_hands")) record["global_hands"]=Json::array(); + for(auto& hand:record["global_hands"]) { + int pos=hand.value("person_id",0)-1; std::vector points; + bool finite=true; + for(auto const& p:hand.value("keypoints",Json::array())) { + float x=p[0].get(),y=p[1].get(); + if(!std::isfinite(x)||!std::isfinite(y)) {finite=false;break;} + points.emplace_back(static_cast(std::nearbyint(x)),static_cast(std::nearbyint(y))); + } + if(!finite||points.size()<3) continue; + std::vector hull; cv::convexHull(points,hull); + cv::Mat region(size_,CV_8U,cv::Scalar(0)); cv::fillConvexPoly(region,hull,255); + if(!cv::countNonZero(region)) continue; + withHands.insert(pos); + hand["risk"]=measure(region,"RTMW_HAND_KEYPOINTS",trackFor(pos),hand.value("side",std::string("unknown")),hand.value("state",std::string("unknown"))); + } + auto const boxes=record.value("boxes",Json::array()); + for(int pos=0;pos())),0,size_.width-1),y1=std::clamp(int(std::nearbyint(b[1].get())),0,size_.height-1); + int x2=std::clamp(int(std::nearbyint(b[2].get())),0,size_.width-1),y2=std::clamp(int(std::nearbyint(b[3].get())),0,size_.height-1); + if(x1>x2) std::swap(x1,x2); + if(y1>y2) std::swap(y1,y2); + if(x2<=x1||y2<=y1) continue; + cv::Mat region(size_,CV_8U,cv::Scalar(0)); cv::rectangle(region,{x1,y1},{x2,y2},255,-1); + Json fallback={{"track_id",trackFor(pos)},{"side","body"},{"state","rtmw_hand_unavailable"},{"body_zone_box",b}}; + fallback["risk"]=measure(region,"YOLO_BODY_BOX_FALLBACK",trackFor(pos),"body","rtmw_hand_unavailable"); fallbacks.push_back(fallback); + } + record["body_fallbacks"]=fallbacks; + Json result={{"status",status_!="loaded"?"door_mask_"+status_:nearest.is_null()?"person_region_missing":"calculated"}}; + for(auto const* key:{"risk_score","risk_level","decision","source","track_id","side"}) result[key]=nearest.is_null()?Json(nullptr):nearest[key]; + if(nearest.is_null()) {result["risk_level"]="unavailable";result["decision"]="EVIDENCE_UNAVAILABLE";} + result["door_mask"]={{"status",status_},{"annotation_index",options_.constant.empty()?frame+options_.offset:frame},{"path",loadedPath_.empty()?Json(nullptr):Json(std::filesystem::absolute(loadedPath_).string())},{"format",format_},{"area_px",mask_.empty()?0:cv::countNonZero(mask_)}}; + result["warning_distance_relative"]=options_.warning; result["release_distance_relative"]=options_.release; result["regions"]=regions; + return result; +} +} diff --git a/src/door_zone.cpp b/src/door_zone.cpp new file mode 100644 index 0000000..025f762 --- /dev/null +++ b/src/door_zone.cpp @@ -0,0 +1,94 @@ +#include "door_zone.hpp" +#include +#include +#include +#include + +namespace cms { +namespace { +double median(std::deque const& values) { + std::vector sorted(values.begin(),values.end()); + std::sort(sorted.begin(),sorted.end());auto n=sorted.size(); + return n%2?sorted[n/2]:(sorted[n/2-1]+sorted[n/2])*0.5; +} +std::string upper(std::string value) { for(auto& c:value)c=char(std::toupper(static_cast(c)));return value; } +Json features(Json const& record,std::size_t position,cv::Size size,int& poseCount) { + auto const& b=record.at("boxes").at(position); + double w=std::max(size.width,1),h=std::max(size.height,1); + Json result={{"box_center_x",(b[0].get()+b[2].get())*.5/w},{"box_height",(b[3].get()-b[1].get())/h}}; + poseCount=0; + if(!record.contains("temporal_selected_keypoints")||position>=record["temporal_selected_keypoints"].size())return result; + auto const& points=record["temporal_selected_keypoints"][position]; + for(int k:{5,6,11,12})if(k>=int(points.size())||points[k].size()<2||!std::isfinite(points[k][0].get())||!std::isfinite(points[k][1].get()))return result; + double sx=(points[5][0].get()+points[6][0].get())*.5; + double sy=(points[5][1].get()+points[6][1].get())*.5; + double hy=(points[11][1].get()+points[12][1].get())*.5; + result["shoulder_center_x"]=sx/w;result["shoulder_center_y"]=sy/h; + result["shoulder_width"]=std::hypot(points[5][0].get()-points[6][0].get(),points[5][1].get()-points[6][1].get())/w; + result["torso_height"]=std::abs(hy-sy)/h;result["hip_center_y"]=hy/h;poseCount=5;return result; +} +Json baseRisk(std::string const& door,std::string const& level,Json score,std::string const& status,std::string const& decision,Json regions=Json::array()) { + return {{"door_id",door},{"status",status},{"risk_score",score},{"risk_level",level},{"decision",decision},{"risk_type",upper(door)+"_"+upper(level)},{"source",nullptr},{"track_id",nullptr},{"side",nullptr},{"regions",regions}}; +} +} +DoorZoneAssigner::DoorZoneAssigner(std::filesystem::path const& configuration) { + std::ifstream file(configuration);if(!file)throw std::runtime_error("Cannot read door-zone configuration: "+configuration.string()); + auto j=Json::parse(file);features_=j.at("features"); + frontMin_=j.value("front_probability_min",.72);rearMax_=j.value("rear_probability_max",.28); + minimumHeight_=j.value("minimum_person_height",.4);minimumPoseFeatures_=j.value("minimum_pose_features",4); + historyFrames_=j.value("history_frames",5);staleFrames_=j.value("stale_track_frames",120); + if(historyFrames_<1||staleFrames_<0||rearMax_<0||frontMin_>1||rearMax_>=frontMin_)throw std::invalid_argument("Invalid door-zone configuration"); +} +std::string DoorZoneAssigner::classify(double p) const {return p>=frontMin_?"front":p<=rearMax_?"rear":"transition";} +Json DoorZoneAssigner::update(int frame,Json const& record,cv::Size size) { + Json assignments=Json::array();auto const& boxes=record.at("boxes");auto ids=record.value("track_ids",Json::array()); + for(std::size_t i=0;i():int(i)+1,count=0; + auto f=features(record,i,size,count);double weighted=0,total=0;Json contributions=Json::object(); + for(auto const& entry:features_.items())if(f.contains(entry.key())) { + auto const& c=entry.value();double rear=c.at("rear"),front=c.at("front"),weight=c.at("weight"); + double score=front==rear?.5:std::clamp((f[entry.key()].get()-rear)/(front-rear),0.0,1.0); + contributions[entry.key()]=rounded(score,6);weighted+=score*weight;total+=weight; + } + double probability=total?weighted/total:.5;std::string rawZone,zone; + if(f["box_height"].get()historyFrames_)values.pop_front();zone=classify(median(values));} + auto it=history_.find(id);double smoothed=it==history_.end()||it->second.empty()?probability:median(it->second); + lastSeen_[id]=frame;for(auto& value:f.items())value.value()=rounded(value.value().get(),6); + assignments.push_back({{"track_id",id},{"person_index",i},{"door_zone",zone},{"raw_door_zone",rawZone},{"front_probability",rounded(probability,6)},{"smoothed_front_probability",rounded(smoothed,6)},{"confidence",rounded(std::abs(smoothed-.5)*2,6)},{"evidence_source","pose_primary_bbox_secondary"},{"pose_feature_count",count},{"features",f},{"feature_frontness",contributions},{"history_frames",it==history_.end()?0:it->second.size()}}); + } + for(auto it=lastSeen_.begin();it!=lastSeen_.end();)if(frame-it->second>staleFrames_){history_.erase(it->first);it=lastSeen_.erase(it);}else ++it; + return assignments; +} +DoorDecisions buildDoorRisks(Json const& raw,Json const& assignments,std::string const& active,bool canValid) { + std::map zones;for(auto const& a:assignments)zones[a.at("track_id").get()]=a.at("door_zone"); + Json regions=Json::array();for(auto region:raw.value("regions",Json::array())){int id=region.value("track_id",-1);region["door_zone"]=zones.count(id)?zones[id]:"unknown";regions.push_back(std::move(region));} + DoorDecisions result{Json::object(),Json::object()}; + for(std::string door:{"front","rear"}) { + if(active!="both"&&active!=door){result.doors[door]=baseRisk(door,"inactive",nullptr,"inactive","DOOR_CAN_NOT_ACTIVE");continue;} + Json exact=Json::array(),transition=Json::array(),nearest=nullptr; + for(auto const& r:regions){if(r["door_zone"]==door){exact.push_back(r);if(!r.at("risk_score").is_null()&&(nearest.is_null()||r["risk_score"].get()>nearest["risk_score"].get()))nearest=r;}if(r["door_zone"]=="transition")transition.push_back(r);} + if(!nearest.is_null()&&(transition.empty()||(nearest["risk_level"]!="clear"&&nearest["risk_level"]!="watch"))) { + auto level=nearest["risk_level"].get();auto r=baseRisk(door,level,nearest["risk_score"],"calculated",nearest["decision"],exact); + for(auto key:{"source","track_id","side"}){r[key]=nearest[key];} + result.doors[door]=std::move(r); + } else if(!transition.empty())result.doors[door]=baseRisk(door,"unknown",nullptr,"door_zone_ambiguous","EVIDENCE_UNAVAILABLE_TRANSITION_ZONE",transition); + else result.doors[door]=baseRisk(door,"clear",0.0,"calculated","CLEAR_NO_PERSON_ASSIGNED_TO_ACTIVE_DOOR"); + } + if(active=="front"||active=="rear")result.selected=result.doors[active]; + else if(active=="none") { + result.selected=baseRisk("none","inactive",nullptr,"inactive","NO_DOOR_CAN_ACTIVE");result.selected["door_id"]=nullptr;result.selected["risk_type"]="NO_ACTIVE_DOOR"; + } else { + std::map severity{{"danger",4},{"warning",3},{"watch",2},{"unknown",1},{"clear",0}}; + result.selected=result.doors["front"]; + if(severity[result.doors["rear"]["risk_level"]]>severity[result.selected["risk_level"]])result.selected=result.doors["rear"]; + } + for(auto key:{"door_mask","warning_distance_relative","release_distance_relative"})result.selected[key]=raw.at(key); + if(!canValid) { + for(auto& entry:result.doors.items()){auto& r=entry.value();r["risk_type"]=upper(entry.key())+"_UNKNOWN";r["risk_level"]="unknown";r["risk_score"]=nullptr;r["decision"]="CAN_UNAVAILABLE_OR_STALE";} + result.selected["risk_type"]="CAN_UNKNOWN";result.selected["risk_level"]="unknown";result.selected["risk_score"]=nullptr;result.selected["decision"]="CAN_UNAVAILABLE_OR_STALE"; + } + return result; +} +} diff --git a/native/gpu_preprocess.cu b/src/gpu_preprocess.cu similarity index 100% rename from native/gpu_preprocess.cu rename to src/gpu_preprocess.cu diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..b17156f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,69 @@ +#include "camera_pipeline.hpp" +#include "config.hpp" +#include "stop_signals.hpp" +#include "visualizer.hpp" +#include +#include +#include +#include +#include +#include + +int main(int argc,char** argv)try { + auto root=std::filesystem::canonical("/proc/self/exe").parent_path().parent_path(); + auto configPath=root/"config/camera.json"; + for(int i=1;i=argc)throw std::invalid_argument("--config needs a path");configPath=argv[i]; + } + for(int i=1;i=argc)throw std::invalid_argument("Missing option value");return std::string(argv[i]);}; + for(int i=1;i monitorStop{false}; + std::thread monitor([&] { + auto report=started; + while(!monitorStop&&pipeline.running()) { + auto now=std::chrono::steady_clock::now();double elapsed=std::chrono::duration(now-started).count(); + if(duration>0&&elapsed>=duration){pipeline.requestStop();break;} + if(std::chrono::duration(now-report).count()>=5) { + auto s=pipeline.stats();auto r=pipeline.latest(); + std::cout<<"frames="< + // if(result) { use(result->risk, result->door_risks, result->persons); } + pipeline.join(); + }catch(...){monitorStop=true;pipeline.requestStop();monitor.join();throw;} + monitorStop=true;monitor.join();auto stats=pipeline.stats(); + auto seconds=std::chrono::duration(std::chrono::steady_clock::now()-started).count(); + std::cout<<"frames="< + +#include +#include +#include +#include +#include +#include + +namespace fangjia { +namespace { + +constexpr int kInputWidth = 288; +constexpr int kInputHeight = 384; +constexpr int kKeypoints = 133; +constexpr int kSimccX = 576; +constexpr int kSimccY = 768; + +__global__ void poseNormalizeKernel( + float* output, std::uint8_t const* input, int people, int inputStep) { + int index = blockIdx.x * blockDim.x + threadIdx.x; + int pixels = kInputWidth * kInputHeight; + int total = people * pixels; + if (index >= total) return; + int person = index / pixels; + int pixel = index - person * pixels; + int y = pixel / kInputWidth; + int x = pixel - y * kInputWidth; + auto const* value = input + person * inputStep * kInputHeight + y * inputStep + x * 3; + float* destination = output + static_cast(person) * 3 * pixels; + destination[pixel] = static_cast((static_cast(value[0]) - 123.675) / 58.395); + destination[pixels + pixel] = static_cast((static_cast(value[1]) - 116.28) / 57.12); + destination[2 * pixels + pixel] = static_cast((static_cast(value[2]) - 103.53) / 57.375); +} + +float iou(Box const& a, Box const& b) { + float x1 = std::max(a.x1, b.x1); + float y1 = std::max(a.y1, b.y1); + float x2 = std::min(a.x2, b.x2); + float y2 = std::min(a.y2, b.y2); + float intersection = std::max(x2 - x1, 0.0F) * std::max(y2 - y1, 0.0F); + float areaA = std::max(a.x2 - a.x1, 0.0F) * std::max(a.y2 - a.y1, 0.0F); + float areaB = std::max(b.x2 - b.x1, 0.0F) * std::max(b.y2 - b.y1, 0.0F); + return intersection / std::max(areaA + areaB - intersection, 1.0F); +} + +float distance(float x1, float y1, float x2, float y2) { + return std::hypot(x1 - x2, y1 - y2); +} + +} // namespace + +std::vector BoxTracker::update( + std::vector& boxes, int frameIndex, int width, int height) { + // Tracks older than Python's max_age can never be matched again. + // Reclaim them to bound memory during continuous camera operation. + for(auto it=tracks_.begin();it!=tracks_.end();) { + if(frameIndex-it->second.lastFrame>30)it=tracks_.erase(it); + else ++it; + } + struct Pair { float score; int detection; int track; }; + std::vector pairs; + float imageDiagonal = std::hypot(static_cast(width), static_cast(height)); + for (int d = 0; d < static_cast(boxes.size()); ++d) { + float cx = 0.5F * (boxes[d].x1 + boxes[d].x2); + float cy = 0.5F * (boxes[d].y1 + boxes[d].y2); + for (auto const& [id, track] : tracks_) { + if (frameIndex - track.lastFrame > 30) continue; + float pcx = 0.5F * (track.box.x1 + track.box.x2); + float pcy = 0.5F * (track.box.y1 + track.box.y2); + float overlap = iou(boxes[d], track.box); + float centerDistance = distance(cx, cy, pcx, pcy) / imageDiagonal; + if (overlap >= 0.05F || centerDistance <= 0.18F) { + pairs.push_back({2.0F * overlap + std::max(0.0F, 1.0F - centerDistance / 0.18F), d, id}); + } + } + } + std::sort(pairs.begin(), pairs.end(), [](Pair const& a, Pair const& b) { + if (a.score != b.score) return a.score > b.score; + if (a.detection != b.detection) return a.detection > b.detection; + return a.track > b.track; + }); + std::vector result(boxes.size(), 0); + std::unordered_map assigned; + for (auto const& pair : pairs) { + if (result[pair.detection] || assigned[pair.track]) continue; + result[pair.detection] = pair.track; + assigned[pair.track] = true; + } + for (int d = 0; d < static_cast(boxes.size()); ++d) { + if (!result[d]) result[d] = nextId_++; + boxes[d].trackId = result[d]; + tracks_[result[d]] = {boxes[d], frameIndex}; + } + return result; +} + +RtmwEngine::RtmwEngine( + std::filesystem::path const& enginePath, TrtLogger& logger, + int maxSourceWidth, int maxSourceHeight, int maxPeople) + : engine_(enginePath, logger), maxSourceWidth_(maxSourceWidth), + maxSourceHeight_(maxSourceHeight), maxPeople_(maxPeople), + centersHost_(maxPeople * 2), scalesHost_(maxPeople * 2), + simccXHost_(static_cast(maxPeople) * kKeypoints * kSimccX), + simccYHost_(static_cast(maxPeople) * kKeypoints * kSimccY) { + engine_.setInputShape("input", nvinfer1::Dims4{1,3,kInputHeight,kInputWidth}); + engine_.prepare(); + auto xs=engine_.shape("simcc_x"),ys=engine_.shape("simcc_y"); + if(xs.nbDims!=3||ys.nbDims!=3||xs.d[0]!=1||ys.d[0]!=1||xs.d[1]!=kKeypoints||ys.d[1]!=kKeypoints||xs.d[2]!=kSimccX||ys.d[2]!=kSimccY) + throw std::runtime_error("RTMW engine I/O shape does not match the 133-point model"); + for(auto name:{"input","simcc_x","simcc_y"})if(engine_.dataType(name)!=nvinfer1::DataType::kFLOAT) + throw std::runtime_error("RTMW requires float32 I/O bindings"); + std::size_t const values = static_cast(maxPeople) * 3 * kInputWidth * kInputHeight; + CUDA_CHECK(cudaMalloc( + &cropsDevice_, static_cast(maxPeople_) * kInputWidth * kInputHeight * 3)); + CUDA_CHECK(cudaMalloc(&networkDevice_, values * sizeof(float))); + CUDA_CHECK(cudaMalloc(&sourceDevice_, static_cast(maxSourceWidth_)*maxSourceHeight_*3)); +} + +RtmwEngine::~RtmwEngine() { + if (sourceDevice_) cudaFree(sourceDevice_); + if (cropsDevice_) cudaFree(cropsDevice_); + if (networkDevice_) cudaFree(networkDevice_); +} + +void RtmwEngine::warmup(cudaStream_t stream) { + std::vector boxes{{0, 0, static_cast(maxSourceWidth_), static_cast(maxSourceHeight_), 1, 1}}; + cv::Mat black(maxSourceHeight_, maxSourceWidth_, CV_8UC3, cv::Scalar(0, 0, 0)); + for (int i = 0; i < 2; ++i) infer(black, boxes, stream); +} + +std::vector RtmwEngine::infer( + cv::Mat const& frame, std::vector const& boxes, cudaStream_t stream, + float* preprocessMs, float* inferenceMs, float* postprocessMs) { + int batch = std::min(static_cast(boxes.size()), maxPeople_); + if (batch <= 0) return {}; + auto const preprocessStarted = std::chrono::steady_clock::now(); + int const cropStep = kInputWidth * 3; + int const pixels = kInputWidth * kInputHeight; + if (frame.cols>maxSourceWidth_ || frame.rows>maxSourceHeight_) throw std::runtime_error("Pose input exceeds allocation"); + auto npp = cms::nppContext(stream); + CUDA_CHECK(cudaMemcpy2DAsync(sourceDevice_, frame.cols*3, frame.data, frame.step, frame.cols*3, frame.rows, cudaMemcpyHostToDevice, stream)); + for (int i = 0; i < batch; ++i) { + auto const& box = boxes[i]; + double width = (static_cast(box.x2) - static_cast(box.x1)) * 1.25; + double height = (static_cast(box.y2) - static_cast(box.y1)) * 1.25; + constexpr double aspect = static_cast(kInputWidth) / kInputHeight; + if (width > height * aspect) height = width / aspect; + else width = height * aspect; + centersHost_[i * 2] = 0.5 * (static_cast(box.x1) + static_cast(box.x2)); + centersHost_[i * 2 + 1] = 0.5 * (static_cast(box.y1) + static_cast(box.y2)); + scalesHost_[i * 2] = width; + scalesHost_[i * 2 + 1] = height; + + cv::Point2f source[3]{ + {static_cast(centersHost_[i * 2]), static_cast(centersHost_[i * 2 + 1])}, + {static_cast(centersHost_[i * 2]), static_cast(centersHost_[i * 2 + 1] - width * 0.5)}, + {static_cast(centersHost_[i * 2] - width * 0.5), static_cast(centersHost_[i * 2 + 1] - width * 0.5)}}; + cv::Point2f destination[3]{ + {kInputWidth * 0.5F, kInputHeight * 0.5F}, + {kInputWidth * 0.5F, kInputHeight * 0.5F - kInputWidth * 0.5F}, + {0.0F, kInputHeight * 0.5F - kInputWidth * 0.5F}}; + cv::Mat affine = cv::getAffineTransform(source, destination); + auto* cropDevice=cropsDevice_+static_cast(i)*cropStep*kInputHeight; + Npp8u fill[3]={0,0,0}; + cms::nppCheck(nppiSet_8u_C3R_Ctx(fill,cropDevice,cropStep,{kInputWidth,kInputHeight},npp)); + double coeff[2][3]; + for(int row=0;row<2;++row) for(int col=0;col<3;++col) coeff[row][col]=affine.at(row,col); + cms::nppCheck(nppiWarpAffine_8u_C3R_Ctx(sourceDevice_,{frame.cols,frame.rows},frame.cols*3,{0,0,frame.cols,frame.rows},cropDevice,cropStep,{0,0,kInputWidth,kInputHeight},coeff,NPPI_INTER_LINEAR,npp)); + } + int const totalPixels = batch * pixels; + poseNormalizeKernel<<<(totalPixels + 255) / 256, 256, 0, stream>>>( + networkDevice_, cropsDevice_, batch, cropStep); + CUDA_CHECK(cudaGetLastError()); + CUDA_CHECK(cudaStreamSynchronize(stream)); + if (preprocessMs) { + *preprocessMs = static_cast(std::chrono::duration( + std::chrono::steady_clock::now() - preprocessStarted).count()); + } + + // RTMPose's Python API invokes the ONNX/TensorRT session once per person. + // Preserve that execution contract instead of batching people together. + engine_.setInputShape("input", nvinfer1::Dims4{1, 3, kInputHeight, kInputWidth}); + engine_.prepare(); + CudaEventTimer timer; + float inferTime = 0.0F; + std::size_t const inputValues = static_cast(3) * pixels; + std::size_t const xValues = static_cast(kKeypoints) * kSimccX; + std::size_t const yValues = static_cast(kKeypoints) * kSimccY; + for (int person = 0; person < batch; ++person) { + CUDA_CHECK(cudaMemcpyAsync( + engine_.device("input"), networkDevice_ + static_cast(person) * inputValues, + inputValues * sizeof(float), cudaMemcpyDeviceToDevice, stream)); + timer.start(stream); + if (!engine_.enqueue(stream)) throw std::runtime_error("RTMW enqueue failed"); + inferTime += timer.stop(stream); + CUDA_CHECK(cudaMemcpyAsync( + simccXHost_.data() + static_cast(person) * xValues, + engine_.device("simcc_x"), xValues * sizeof(float), cudaMemcpyDeviceToHost, stream)); + CUDA_CHECK(cudaMemcpyAsync( + simccYHost_.data() + static_cast(person) * yValues, + engine_.device("simcc_y"), yValues * sizeof(float), cudaMemcpyDeviceToHost, stream)); + } + if (inferenceMs) *inferenceMs = inferTime; + + auto const postprocessStarted = std::chrono::steady_clock::now(); + CUDA_CHECK(cudaStreamSynchronize(stream)); + std::vector result(batch); + for (int person = 0; person < batch; ++person) { + result[person].box = boxes[person]; + for (int keypoint = 0; keypoint < kKeypoints; ++keypoint) { + auto xBegin = simccXHost_.begin() + (person * kKeypoints + keypoint) * kSimccX; + auto yBegin = simccYHost_.begin() + (person * kKeypoints + keypoint) * kSimccY; + auto xIt = std::max_element(xBegin, xBegin + kSimccX); + auto yIt = std::max_element(yBegin, yBegin + kSimccY); + float score = 0.5F * (*xIt + *yIt); + if(!std::isfinite(score))throw std::runtime_error("Non-finite RTMW output"); + float localX = score > 0.0F ? static_cast(xIt - xBegin) * 0.5F : -0.5F; + float localY = score > 0.0F ? static_cast(yIt - yBegin) * 0.5F : -0.5F; + double scaleX = scalesHost_[person * 2]; + double scaleY = scalesHost_[person * 2 + 1]; + result[person].keypoints[keypoint] = { + static_cast(static_cast(localX) / kInputWidth * scaleX + centersHost_[person * 2] - scaleX * 0.5), + static_cast(static_cast(localY) / kInputHeight * scaleY + centersHost_[person * 2 + 1] - scaleY * 0.5), + score}; + } + } + float post = static_cast(std::chrono::duration( + std::chrono::steady_clock::now() - postprocessStarted).count()); + if (postprocessMs) *postprocessMs = post; + return result; +} + +PoseFrameResult PoseFusionTracker::update( + std::vector const& rawPeople, + std::vector const& isolatedPeople, + int frameIndex) { + for(auto it=states_.begin();it!=states_.end();) { + if(it->second.lastFrame>=0&&frameIndex-it->second.lastFrame>30)it=states_.erase(it); + else ++it; + } + struct SideMeta { + bool limbConsensus{}; + bool handValid{}; + bool distanceEligible{}; + bool alarmEligible{}; + double handScore{}; + double linkToSelectedLimb{}; + std::string handSource; + std::string handState; + }; + struct FusedPerson { + PersonPose pose; + std::array meta; + }; + struct Measurement { + std::array hand{}; + bool precise{}; + bool provisional{}; + double quality{}; + std::string source{"model"}; + }; + auto vec = [](Keypoint const& point) { return Vec2{point.x, point.y}; }; + auto add = [](Vec2 a, Vec2 b) { return Vec2{a.x + b.x, a.y + b.y}; }; + auto sub = [](Vec2 a, Vec2 b) { return Vec2{a.x - b.x, a.y - b.y}; }; + auto mul = [](Vec2 a, double value) { return Vec2{a.x * value, a.y * value}; }; + auto norm = [](Vec2 a) { return std::hypot(a.x, a.y); }; + auto median = [](std::vector values) { + if (values.empty()) return 0.0; + std::sort(values.begin(), values.end()); + auto middle = values.size() / 2; + return values.size() % 2 ? values[middle] : 0.5 * (values[middle - 1] + values[middle]); + }; + auto percentile90 = [](std::vector values) { + if (values.empty()) return 0.0; + std::sort(values.begin(), values.end()); + double position = 0.9 * (values.size() - 1); + auto lower = static_cast(position); + auto upper = std::min(lower + 1, values.size() - 1); + return values[lower] * (upper - position) + values[upper] * (position - lower); + }; + auto boxDiagonal = [](Box const& box) { + return std::max(std::hypot(box.x2 - box.x1, box.y2 - box.y1), 1.0F); + }; + auto normalized = [](Keypoint const& point, Box const& box) { + return Vec2{ + (point.x - box.x1) / std::max(box.x2 - box.x1, 1.0F), + (point.y - box.y1) / std::max(box.y2 - box.y1, 1.0F)}; + }; + auto denormalized = [](Vec2 point, Box const& box) { + return Vec2{ + point.x * std::max(box.x2 - box.x1, 1.0F) + box.x1, + point.y * std::max(box.y2 - box.y1, 1.0F) + box.y1}; + }; + auto solveTwoBoneElbow = [&](Vec2 shoulder, Vec2 wrist, double upperLength, + double forearmLength, Vec2 reference) { + Vec2 direction = sub(wrist, shoulder); + double length = norm(direction); + if (length < 1e-6) return reference; + Vec2 unit = mul(direction, 1.0 / length); + if (length >= upperLength + forearmLength) return add(shoulder, mul(unit, upperLength)); + if (length <= std::abs(upperLength - forearmLength)) { + double sign = upperLength >= forearmLength ? 1.0 : -1.0; + return add(shoulder, mul(unit, upperLength * sign)); + } + double along = (upperLength * upperLength - forearmLength * forearmLength + length * length) / + (2.0 * length); + double height = std::sqrt(std::max(upperLength * upperLength - along * along, 0.0)); + Vec2 base = add(shoulder, mul(unit, along)); + Vec2 perpendicular{-unit.y, unit.x}; + Vec2 first = add(base, mul(perpendicular, height)); + Vec2 second = sub(base, mul(perpendicular, height)); + return norm(sub(first, reference)) <= norm(sub(second, reference)) ? first : second; + }; + struct Candidate { + PersonPose const* pose{}; + std::string source; + std::array limb{}; + std::array hand{}; + double limbScore{}; + double handScore{}; + double handSpan{}; + double wristLink{}; + bool limbValid{}; + bool handValid{}; + }; + auto buildCandidate = [&](PersonPose const& pose, int side, std::string source) { + int shoulder = side == 0 ? 5 : 6; + int elbow = side == 0 ? 7 : 8; + int wrist = side == 0 ? 9 : 10; + int handStart = side == 0 ? 91 : 112; + Candidate candidate; + candidate.pose = &pose; + candidate.source = std::move(source); + candidate.limb = {normalized(pose.keypoints[shoulder], pose.box), + normalized(pose.keypoints[elbow], pose.box), + normalized(pose.keypoints[wrist], pose.box)}; + double minX = pose.keypoints[handStart].x, maxX = minX; + double minY = pose.keypoints[handStart].y, maxY = minY; + for (int index = 0; index < 21; ++index) { + candidate.hand[index] = normalized(pose.keypoints[handStart + index], pose.box); + minX = std::min(minX, pose.keypoints[handStart + index].x); + maxX = std::max(maxX, pose.keypoints[handStart + index].x); + minY = std::min(minY, pose.keypoints[handStart + index].y); + maxY = std::max(maxY, pose.keypoints[handStart + index].y); + candidate.handScore += pose.keypoints[handStart + index].score; + } + candidate.handScore /= 21.0; + candidate.limbScore = (pose.keypoints[shoulder].score + pose.keypoints[elbow].score + pose.keypoints[wrist].score) / 3.0; + double upper = norm(sub(candidate.limb[0], candidate.limb[1])); + double forearm = norm(sub(candidate.limb[1], candidate.limb[2])); + candidate.handSpan = std::hypot(maxX - minX, maxY - minY) / boxDiagonal(pose.box); + candidate.wristLink = distance( + pose.keypoints[wrist].x, pose.keypoints[wrist].y, + pose.keypoints[handStart].x, pose.keypoints[handStart].y) / boxDiagonal(pose.box); + candidate.limbValid = upper >= 0.025 && upper <= 0.8 && forearm >= 0.025 && forearm <= 0.8; + candidate.handValid = candidate.handSpan >= 0.008 && candidate.handSpan <= 0.35 && candidate.wristLink <= 0.12; + return candidate; + }; + auto robustDistance = [&](auto const& points, auto const& target, bool available) { + if (!available) return 0.0; + std::vector distances; + for (std::size_t index = 0; index < points.size(); ++index) { + distances.push_back(norm(sub(points[index], target[index]))); + } + return median(std::move(distances)); + }; + + std::vector fused; + fused.reserve(rawPeople.size()); + for (std::size_t personIndex = 0; personIndex < rawPeople.size(); ++personIndex) { + auto const& raw = rawPeople[personIndex]; + auto const& isolated = personIndex < isolatedPeople.size() ? isolatedPeople[personIndex] : raw; + auto& state = states_[raw.box.trackId]; + FusedPerson output; + output.pose = raw; + for (int side = 0; side < 2; ++side) { + int shoulder = side == 0 ? 5 : 6; + int elbow = side == 0 ? 7 : 8; + int wrist = side == 0 ? 9 : 10; + int handStart = side == 0 ? 91 : 112; + auto& track = state.fusion[side]; + std::array candidates{ + buildCandidate(raw, side, "raw"), buildCandidate(isolated, side, "isolated")}; + int limbChoice = 0; + double bestLimbScore = -1e30; + for (int choice = 0; choice < 2; ++choice) { + std::array target{}; + if (track.hasLimbPrevious) { + for (int i = 0; i < 3; ++i) target[i] = add(track.limbPrevious[i], track.hasLimbVelocity ? mul(track.limbVelocity[i], 0.65) : Vec2{}); + } + double temporal = robustDistance(candidates[choice].limb, target, track.hasLimbPrevious); + double score = candidates[choice].limbScore + (candidates[choice].limbValid ? 1.2 : -5.0) + + (track.limbSource == candidates[choice].source ? 0.35 : 0.0) - 7.0 * temporal; + if (score > bestLimbScore) { bestLimbScore = score; limbChoice = choice; } + } + int handChoice = 0; + double bestHandScore = -1e30; + double chosenTemporal = 0.0; + double chosenLink = 1.0; + for (int choice = 0; choice < 2; ++choice) { + std::array target{}; + if (track.hasHandPrevious) { + for (int i = 0; i < 21; ++i) target[i] = add(track.handPrevious[i], track.hasHandVelocity ? mul(track.handVelocity[i], 0.65) : Vec2{}); + } + double temporal = robustDistance(candidates[choice].hand, target, track.hasHandPrevious); + auto const& limbPose = *candidates[limbChoice].pose; + double link = distance( + candidates[choice].pose->keypoints[handStart].x, + candidates[choice].pose->keypoints[handStart].y, + limbPose.keypoints[wrist].x, limbPose.keypoints[wrist].y) / boxDiagonal(raw.box); + double linkPenalty = 1.5 * std::min(std::max(link - 0.12, 0.0), 0.2); + double score = candidates[choice].handScore + (candidates[choice].handValid ? 3.0 : -7.0) + + (track.handSource == candidates[choice].source ? 0.45 : 0.0) + + (candidates[choice].source == candidates[limbChoice].source ? 0.15 : 0.0) + - linkPenalty - 8.0 * std::min(temporal, 0.15); + if (score > bestHandScore) { + bestHandScore = score; + handChoice = choice; + chosenTemporal = temporal; + chosenLink = link; + } + } + double endpointAdvantage = candidates[handChoice].handScore - candidates[1 - handChoice].handScore; + if (handChoice != limbChoice && candidates[handChoice].handValid && endpointAdvantage >= 0.6) { + limbChoice = handChoice; + // fuse_person() rewrites this link after its visible-endpoint + // override. Temporal recovery consumes the rewritten value. + chosenLink = candidates[handChoice].wristLink; + } + auto const& limb = candidates[limbChoice]; + auto const& hand = candidates[handChoice]; + for (int index : {shoulder, elbow, wrist}) output.pose.keypoints[index] = limb.pose->keypoints[index]; + for (int index = 0; index < 21; ++index) output.pose.keypoints[handStart + index] = hand.pose->keypoints[handStart + index]; + std::vector limbDistances, handDistances; + for (int index = 0; index < 3; ++index) limbDistances.push_back(norm(sub(candidates[0].limb[index], candidates[1].limb[index]))); + for (int index = 0; index < 21; ++index) handDistances.push_back(norm(sub(candidates[0].hand[index], candidates[1].hand[index]))); + double handDisagreement = percentile90(handDistances); + double scoreAdvantage = candidates[0].handScore - candidates[1].handScore; + auto& meta = output.meta[side]; + meta.limbConsensus = median(limbDistances) <= 0.05; + meta.handValid = hand.handValid; + meta.distanceEligible = hand.handValid && ( + handDisagreement <= 0.06 || + (hand.source == "raw" && scoreAdvantage >= 0.6 && chosenTemporal <= 0.08)); + meta.alarmEligible = meta.distanceEligible || (hand.handValid && hand.source == "raw" && scoreAdvantage >= 0.6); + meta.handScore = std::nearbyint(hand.handScore * 1e4) / 1e4; + meta.linkToSelectedLimb = std::nearbyint(chosenLink * 1e5) / 1e5; + meta.handSource = hand.source; + if (!hand.handValid) meta.handState = "rejected"; + else if (handDisagreement <= 0.06) meta.handState = "consensus"; + else if (hand.source == "raw" && scoreAdvantage >= 0.6 && chosenTemporal <= 0.08) meta.handState = "visible_endpoint"; + else if (hand.source == "isolated") meta.handState = "inferred"; + else meta.handState = "conflict"; + if (track.hasLimbPrevious) { + for (int i = 0; i < 3; ++i) { + auto delta = sub(limb.limb[i], track.limbPrevious[i]); + track.limbVelocity[i] = track.hasLimbVelocity ? add(mul(track.limbVelocity[i], 0.65), mul(delta, 0.35)) : delta; + } + track.hasLimbVelocity = true; + } + if (hand.handValid) { + if (track.hasHandPrevious) { + for (int i = 0; i < 21; ++i) { + auto delta = sub(hand.hand[i], track.handPrevious[i]); + track.handVelocity[i] = track.hasHandVelocity ? add(mul(track.handVelocity[i], 0.65), mul(delta, 0.35)) : delta; + } + track.hasHandVelocity = true; + } + track.handPrevious = hand.hand; + track.hasHandPrevious = true; + track.handSource = hand.source; + } + track.limbPrevious = limb.limb; + track.hasLimbPrevious = true; + track.limbSource = limb.source; + } + // Same-person duplicate suppression from the reference fusion. + auto handCenter = [&](int side) { + int start = side == 0 ? 91 : 112; + Vec2 center{}; + for (int i = 0; i < 21; ++i) center = add(center, vec(output.pose.keypoints[start + i])); + return mul(center, 1.0 / 21.0); + }; + if (norm(sub(handCenter(0), handCenter(1))) / boxDiagonal(raw.box) < 0.08) { + int weaker = output.meta[0].handScore <= output.meta[1].handScore ? 0 : 1; + output.meta[weaker].handState = "duplicate"; + output.meta[weaker].distanceEligible = false; + output.meta[weaker].alarmEligible = false; + } + // Motion-adaptive EMA in person-box coordinates. + std::array now{}; + for (int i = 0; i < 133; ++i) now[i] = normalized(output.pose.keypoints[i], raw.box); + if (!state.hasSmoothed) { state.smoothed = now; state.hasSmoothed = true; } + else { + for (int i = 0; i < 133; ++i) { + double displacement = norm(sub(now[i], state.smoothed[i])); + double alpha = 0.32 + 0.58 * std::clamp(displacement / 0.08, 0.0, 1.0); + state.smoothed[i] = add(mul(now[i], alpha), mul(state.smoothed[i], 1.0 - alpha)); + } + } + fused.push_back(std::move(output)); + } + + // Exact build_global_hands() evidence: selected fusion output, stable + // score ordering, then the same cross-person duplicate collapse. + std::vector globalCandidates; + for (std::size_t person = 0; person < fused.size(); ++person) { + for (int side = 0; side < 2; ++side) { + auto const& meta = fused[person].meta[side]; + if (!meta.alarmEligible) continue; + int const start = side == 0 ? 91 : 112; + GlobalHand hand; + hand.personId = static_cast(person) + 1; + hand.trackId = fused[person].pose.box.trackId; + hand.left = side == 0; + hand.source = meta.handSource; + hand.state = meta.handState; + hand.distanceEligible = meta.distanceEligible; + hand.alarmEligible = meta.alarmEligible; + hand.score = static_cast(meta.handScore); + float minX = fused[person].pose.keypoints[start].x; + float maxX = minX; + float minY = fused[person].pose.keypoints[start].y; + float maxY = minY; + for (int point = 0; point < 21; ++point) { + hand.keypoints[point] = fused[person].pose.keypoints[start + point]; + minX = std::min(minX, hand.keypoints[point].x); + maxX = std::max(maxX, hand.keypoints[point].x); + minY = std::min(minY, hand.keypoints[point].y); + maxY = std::max(maxY, hand.keypoints[point].y); + } + hand.spanPx = std::hypot(maxX - minX, maxY - minY); + globalCandidates.push_back(std::move(hand)); + } + } + std::stable_sort(globalCandidates.begin(), globalCandidates.end(), [](auto const& first, auto const& second) { + return first.score > second.score; + }); + std::vector globalHands; + for (auto& candidate : globalCandidates) { + Vec2 candidateCenter{}; + for (auto const& point : candidate.keypoints) candidateCenter = add(candidateCenter, vec(point)); + candidateCenter = mul(candidateCenter, 1.0 / 21.0); + auto duplicate = globalHands.end(); + for (auto iterator = globalHands.begin(); iterator != globalHands.end(); ++iterator) { + Vec2 existingCenter{}; + for (auto const& point : iterator->keypoints) existingCenter = add(existingCenter, vec(point)); + existingCenter = mul(existingCenter, 1.0 / 21.0); + double const threshold = std::clamp( + 0.45 * std::max(candidate.spanPx, iterator->spanPx), 18.0, 70.0); + if (norm(sub(candidateCenter, existingCenter)) <= threshold) { + duplicate = iterator; + break; + } + } + if (duplicate == globalHands.end()) globalHands.push_back(std::move(candidate)); + else duplicate->aliases.emplace_back(candidate.personId, candidate.left); + } + + // Build the model measurements, then collapse cross-person duplicates. + std::vector, 2>> measurements(fused.size()); + for (std::size_t person = 0; person < fused.size(); ++person) { + for (int side = 0; side < 2; ++side) { + if (!fused[person].meta[side].alarmEligible) continue; + Measurement measurement; + int start = side == 0 ? 91 : 112; + for (int i = 0; i < 21; ++i) measurement.hand[i] = vec(fused[person].pose.keypoints[start + i]); + measurement.precise = fused[person].meta[side].distanceEligible; + measurement.provisional = !measurement.precise; + measurement.quality = fused[person].meta[side].handScore; + measurements[person][side] = measurement; + } + } + auto handSpan = [&](Measurement const& measurement) { + double minX = measurement.hand[0].x, maxX = minX, minY = measurement.hand[0].y, maxY = minY; + for (auto point : measurement.hand) { minX = std::min(minX, point.x); maxX = std::max(maxX, point.x); minY = std::min(minY, point.y); maxY = std::max(maxY, point.y); } + return std::hypot(maxX - minX, maxY - minY); + }; + auto handCenter = [&](Measurement const& measurement) { + Vec2 center{}; for (auto point : measurement.hand) center = add(center, point); return mul(center, 1.0 / 21.0); + }; + for (std::size_t firstPerson = 0; firstPerson < fused.size(); ++firstPerson) { + for (int firstSide = 0; firstSide < 2; ++firstSide) { + if (!measurements[firstPerson][firstSide]) continue; + for (std::size_t secondPerson = firstPerson + 1; secondPerson < fused.size(); ++secondPerson) { + for (int secondSide = 0; secondSide < 2; ++secondSide) { + if (!measurements[firstPerson][firstSide] || !measurements[secondPerson][secondSide]) continue; + auto const& first = *measurements[firstPerson][firstSide]; + auto const& second = *measurements[secondPerson][secondSide]; + double firstSpan = handSpan(first), secondSpan = handSpan(second); + if (norm(sub(handCenter(first), handCenter(second))) > std::max(70.0, 0.5 * std::max(firstSpan, secondSpan))) continue; + double firstArea = std::max((fused[firstPerson].pose.box.x2 - fused[firstPerson].pose.box.x1) * (fused[firstPerson].pose.box.y2 - fused[firstPerson].pose.box.y1), 1.0F); + double secondArea = std::max((fused[secondPerson].pose.box.x2 - fused[secondPerson].pose.box.x1) * (fused[secondPerson].pose.box.y2 - fused[secondPerson].pose.box.y1), 1.0F); + if (firstArea >= 2.5 * secondArea && + (firstSpan >= 1.45 * secondSpan || first.quality >= second.quality + 0.6)) { + measurements[secondPerson][secondSide].reset(); + states_[fused[secondPerson].pose.box.trackId].endpoints[secondSide] = {}; + } else if (secondArea >= 2.5 * firstArea && + (secondSpan >= 1.45 * firstSpan || second.quality >= first.quality + 0.6)) { + measurements[firstPerson][firstSide].reset(); + states_[fused[firstPerson].pose.box.trackId].endpoints[firstSide] = {}; + } + } + } + } + } + + // Match resolve_global_hand_assignments(): a large foreground track may reclaim + // a nearby hand that RTMW emitted only from a smaller background crop. + struct Reassignment { + double normalizedDistance{}; + std::size_t targetPerson{}; + int targetSide{}; + std::size_t sourcePerson{}; + int sourceSide{}; + Vec2 sourceCenter{}; + }; + std::vector reassignments; + for (std::size_t targetPerson = 0; targetPerson < fused.size(); ++targetPerson) { + for (int targetSide = 0; targetSide < 2; ++targetSide) { + if (measurements[targetPerson][targetSide]) continue; + auto& targetEndpoint = states_[fused[targetPerson].pose.box.trackId].endpoints[targetSide]; + if (!targetEndpoint.hasWrist || !targetEndpoint.hasHandShape || targetEndpoint.missedFrames > 30) continue; + double targetArea = std::max( + (fused[targetPerson].pose.box.x2 - fused[targetPerson].pose.box.x1) * + (fused[targetPerson].pose.box.y2 - fused[targetPerson].pose.box.y1), 1.0F); + Vec2 shapeCenter{}; + for (auto point : targetEndpoint.handShape) shapeCenter = add(shapeCenter, point); + shapeCenter = mul(shapeCenter, 1.0 / 21.0); + Vec2 predictedCenter = add(targetEndpoint.wrist, shapeCenter); + Measurement targetShape; + for (int point = 0; point < 21; ++point) targetShape.hand[point] = targetEndpoint.handShape[point]; + double targetSpan = handSpan(targetShape); + double gate = 0.20 * boxDiagonal(fused[targetPerson].pose.box); + for (std::size_t sourcePerson = 0; sourcePerson < fused.size(); ++sourcePerson) { + if (sourcePerson == targetPerson) continue; + for (int sourceSide = 0; sourceSide < 2; ++sourceSide) { + auto const& source = measurements[sourcePerson][sourceSide]; + if (!source || source->source != "model") continue; + double sourceArea = std::max( + (fused[sourcePerson].pose.box.x2 - fused[sourcePerson].pose.box.x1) * + (fused[sourcePerson].pose.box.y2 - fused[sourcePerson].pose.box.y1), 1.0F); + if (targetArea < 2.5 * sourceArea) continue; + Vec2 sourceCenter = handCenter(*source); + double sourceSpan = handSpan(*source); + double candidateDistance = norm(sub(sourceCenter, predictedCenter)); + if (candidateDistance > gate || targetSpan < 1.45 * std::max(sourceSpan, 1.0)) continue; + reassignments.push_back({ + candidateDistance / std::max(gate, 1e-6), targetPerson, targetSide, + sourcePerson, sourceSide, sourceCenter}); + } + } + } + } + std::sort(reassignments.begin(), reassignments.end(), [](auto const& first, auto const& second) { + return std::tie(first.normalizedDistance, first.targetPerson, first.targetSide, + first.sourcePerson, first.sourceSide) < + std::tie(second.normalizedDistance, second.targetPerson, second.targetSide, + second.sourcePerson, second.sourceSide); + }); + std::set> usedTargets, usedSources; + for (auto const& item : reassignments) { + auto targetKey = std::make_pair(item.targetPerson, item.targetSide); + auto sourceKey = std::make_pair(item.sourcePerson, item.sourceSide); + if (usedTargets.count(targetKey) || usedSources.count(sourceKey) || + measurements[item.targetPerson][item.targetSide] || + !measurements[item.sourcePerson][item.sourceSide]) continue; + auto& targetEndpoint = states_[fused[item.targetPerson].pose.box.trackId].endpoints[item.targetSide]; + Vec2 shapeCenter{}; + for (auto point : targetEndpoint.handShape) shapeCenter = add(shapeCenter, point); + shapeCenter = mul(shapeCenter, 1.0 / 21.0); + Vec2 reassignedWrist = sub(item.sourceCenter, shapeCenter); + Measurement reassigned; + for (int point = 0; point < 21; ++point) { + reassigned.hand[point] = add(reassignedWrist, targetEndpoint.handShape[point]); + } + reassigned.provisional = true; + reassigned.source = "global_reassignment"; + reassigned.quality = measurements[item.sourcePerson][item.sourceSide]->quality; + measurements[item.targetPerson][item.targetSide] = reassigned; + measurements[item.sourcePerson][item.sourceSide].reset(); + auto& sourceEndpoint = states_[fused[item.sourcePerson].pose.box.trackId].endpoints[item.sourceSide]; + sourceEndpoint = {}; + usedTargets.insert(targetKey); + usedSources.insert(sourceKey); + } + + PoseFrameResult result; + result.globalHands = std::move(globalHands); + result.selectedPeople.reserve(fused.size()); + result.smoothedPeople.reserve(fused.size()); + result.v2People.reserve(fused.size()); + for (auto const& person : fused) { + std::array meta{}; + for (int side=0;side<2;++side) { + auto const& m=person.meta[side]; + meta[side]={m.limbConsensus,m.handValid,m.distanceEligible,m.alarmEligible,m.handScore,m.handSource,m.handState}; + } + result.metadata.push_back(meta); + result.selectedPeople.push_back(person.pose); + PersonPose smoothed = person.pose; + auto const& state = states_[person.pose.box.trackId]; + for (int keypoint = 0; keypoint < 133; ++keypoint) { + Vec2 point = denormalized(state.smoothed[keypoint], person.pose.box); + smoothed.keypoints[keypoint].x = static_cast(point.x); + smoothed.keypoints[keypoint].y = static_cast(point.y); + } + result.smoothedPeople.push_back(smoothed); + result.v2People.push_back(std::move(smoothed)); + } + for (std::size_t person = 0; person < fused.size(); ++person) { + auto& state = states_[fused[person].pose.box.trackId]; + int frameGap = state.lastFrame < 0 ? 0 : std::max(frameIndex - state.lastFrame - 1, 0); + for (auto& endpoint : state.endpoints) endpoint.missedFrames += frameGap; + state.lastFrame = frameIndex; + for (int side = 0; side < 2; ++side) { + if (!fused[person].meta[side].limbConsensus || !fused[person].meta[side].distanceEligible) continue; + int shoulderIndex = side == 0 ? 5 : 6; + int elbowIndex = side == 0 ? 7 : 8; + int wristIndex = side == 0 ? 9 : 10; + Vec2 shoulder = vec(fused[person].pose.keypoints[shoulderIndex]); + Vec2 elbow = vec(fused[person].pose.keypoints[elbowIndex]); + Vec2 wrist = vec(fused[person].pose.keypoints[wristIndex]); + double diagonal = boxDiagonal(fused[person].pose.box); + double upper = norm(sub(shoulder, elbow)) / diagonal; + double forearm = norm(sub(elbow, wrist)) / diagonal; + if (upper >= 0.04 && upper <= 0.65 && forearm >= 0.04 && forearm <= 0.65) { + auto& samples = state.boneSamples[side]; + samples.push_back({upper, forearm}); + while (samples.size() > 120) samples.pop_front(); + } + } + for (int side = 0; side < 2; ++side) { + int shoulderIndex = side == 0 ? 5 : 6; + int elbowIndex = side == 0 ? 7 : 8; + int wristIndex = side == 0 ? 9 : 10; + int handStart = side == 0 ? 91 : 112; + auto& endpoint = state.endpoints[side]; + auto measurement = measurements[person][side]; + // Reference temporal recovery gate. + bool const recoverableHandState = fused[person].meta[side].handState == "conflict" || + fused[person].meta[side].handState == "inferred"; + if (!measurement && endpoint.hasWrist && endpoint.hasHandShape && endpoint.missedFrames <= 30 && + recoverableHandState && fused[person].meta[side].handValid && + fused[person].meta[side].handScore >= 2.0 && + fused[person].meta[side].linkToSelectedLimb <= 0.08) { + Vec2 measured = vec(fused[person].pose.keypoints[handStart]); + Vec2 prediction = add(endpoint.wrist, endpoint.velocity); + double gate = std::min(0.20, 0.05 + 0.008 * std::max(endpoint.missedFrames, 1)) * boxDiagonal(fused[person].pose.box); + if (norm(sub(measured, prediction)) <= gate) { + Measurement recovered; + for (int i = 0; i < 21; ++i) recovered.hand[i] = vec(fused[person].pose.keypoints[handStart + i]); + recovered.provisional = true; recovered.quality = fused[person].meta[side].handScore; recovered.source = "temporal_recovery"; + measurement = recovered; + } + } + LimbState outputState; + std::array outputHand{}; + bool hasOutput = false; + bool returningAfterLong = measurement && endpoint.missedFrames > 12; + if (measurement) { + Vec2 measuredWrist = measurement->hand[0]; + std::array measuredShape{}; + for (int i = 0; i < 21; ++i) measuredShape[i] = sub(measurement->hand[i], measuredWrist); + if (!endpoint.hasWrist || endpoint.missedFrames > 12) { + endpoint.wrist = measuredWrist; endpoint.velocity = {}; endpoint.handShape = measuredShape; + } else { + Vec2 prediction = add(endpoint.wrist, endpoint.velocity); + Vec2 residual = sub(measuredWrist, prediction); + double alpha = measurement->precise ? 0.78 : 0.88; + double beta = measurement->precise ? 0.18 : 0.24; + endpoint.wrist = add(prediction, mul(residual, alpha)); + endpoint.velocity = add(mul(endpoint.velocity, 0.82), mul(residual, beta)); + double shapeAlpha = measurement->precise ? 0.55 : 0.70; + for (int i = 0; i < 21; ++i) endpoint.handShape[i] = add(mul(measuredShape[i], shapeAlpha), mul(endpoint.handShape[i], 1.0 - shapeAlpha)); + } + endpoint.hasWrist = endpoint.hasHandShape = true; + endpoint.lastObservedFrame = frameIndex; + endpoint.missedFrames = 0; + outputState = measurement->source == "global_reassignment" ? LimbState::Reassigned : measurement->source == "temporal_recovery" ? LimbState::Recovered : measurement->precise ? LimbState::Observed : measurement->provisional ? LimbState::Provisional : LimbState::Interpolated; + for (int i = 0; i < 21; ++i) outputHand[i] = add(endpoint.wrist, endpoint.handShape[i]); + hasOutput = true; + } else { + ++endpoint.missedFrames; + if (!endpoint.hasWrist || endpoint.missedFrames > 30) outputState = LimbState::ReachableLong; + else if (endpoint.missedFrames <= 12) { + endpoint.wrist = add(endpoint.wrist, endpoint.velocity); + endpoint.velocity = mul(endpoint.velocity, 0.86); + for (int i = 0; i < 21; ++i) outputHand[i] = add(endpoint.wrist, endpoint.handShape[i]); + hasOutput = true; outputState = LimbState::PredictedShort; + } else outputState = LimbState::ReachableMedium; + } + Vec2 smoothedShoulder = denormalized(state.smoothed[shoulderIndex], fused[person].pose.box); + Vec2 smoothedElbow = denormalized(state.smoothed[elbowIndex], fused[person].pose.box); + Keypoint shoulder = fused[person].pose.keypoints[shoulderIndex]; + shoulder.x = static_cast(smoothedShoulder.x); + shoulder.y = static_cast(smoothedShoulder.y); + Keypoint elbow = fused[person].pose.keypoints[elbowIndex]; + elbow.x = static_cast(smoothedElbow.x); + elbow.y = static_cast(smoothedElbow.y); + Keypoint wrist = result.smoothedPeople[person].keypoints[wristIndex]; + std::array hand{}; + for (int i = 0; i < 21; ++i) { + hand[i] = result.smoothedPeople[person].keypoints[handStart + i]; + } + if (hasOutput) { + auto& ownSamples = state.boneSamples[side]; + auto& oppositeSamples = state.boneSamples[1 - side]; + auto const& samples = ownSamples.size() < 10 && oppositeSamples.size() >= 10 + ? oppositeSamples : ownSamples; + double upperRatio = 0.24, forearmRatio = 0.22; + if (!samples.empty()) { + std::vector upperValues, forearmValues; + upperValues.reserve(samples.size()); + forearmValues.reserve(samples.size()); + for (auto const& sample : samples) { + upperValues.push_back(sample[0]); + forearmValues.push_back(sample[1]); + } + upperRatio = median(std::move(upperValues)); + forearmRatio = median(std::move(forearmValues)); + } + double diagonal = boxDiagonal(fused[person].pose.box); + double upperLength = upperRatio * diagonal; + double forearmLength = forearmRatio * diagonal; + double totalLength = upperLength + forearmLength; + if (totalLength > 0.95 * diagonal) { + double lengthScale = 0.95 * diagonal / totalLength; + upperLength *= lengthScale; + forearmLength *= lengthScale; + } + Vec2 referenceElbow = returningAfterLong || !endpoint.hasLastElbow + ? vec(fused[person].pose.keypoints[elbowIndex]) : endpoint.lastElbow; + if (!fused[person].meta[side].limbConsensus || outputState != LimbState::Observed) { + Vec2 solved = solveTwoBoneElbow( + smoothedShoulder, outputHand[0], upperLength, forearmLength, referenceElbow); + elbow.x = static_cast(solved.x); + elbow.y = static_cast(solved.y); + } + endpoint.lastElbow = vec(elbow); + endpoint.hasLastElbow = true; + Vec2 armDirection = sub(outputHand[0], smoothedShoulder); + if (norm(armDirection) > 1e-6) { + endpoint.lastDirection = mul(armDirection, 1.0 / norm(armDirection)); + endpoint.hasLastDirection = true; + } + wrist.x = outputHand[0].x; wrist.y = outputHand[0].y; + for (int i = 0; i < 21; ++i) { + hand[i].x = outputHand[i].x; hand[i].y = outputHand[i].y; + } + } + auto& v2Pose = result.v2People[person]; + v2Pose.keypoints[shoulderIndex] = shoulder; + v2Pose.keypoints[elbowIndex] = elbow; + v2Pose.keypoints[wristIndex] = wrist; + if (hasOutput) { + for (int point = 0; point < 21; ++point) v2Pose.keypoints[handStart + point] = hand[point]; + } + result.limbs.push_back({ + fused[person].pose.box.trackId, side == 0, outputState, + elbow, wrist, hand, fused[person].pose.box, + fused[person].meta[side].distanceEligible, + fused[person].meta[side].alarmEligible, + static_cast(fused[person].meta[side].handScore)}); + } + } + return result; +} + +} // namespace fangjia diff --git a/src/serialization.cpp b/src/serialization.cpp new file mode 100644 index 0000000..4cadfda --- /dev/null +++ b/src/serialization.cpp @@ -0,0 +1,38 @@ +#include "serialization.hpp" +namespace cms { +namespace { +template Json optional(std::optional const& x){return x?Json(*x):Json(nullptr);} +Json riskJson(RiskResult const& r) { + Json out={{"status",r.status},{"risk_score",optional(r.risk_score)},{"risk_level",r.risk_level},{"decision",r.decision},{"source",optional(r.source)},{"track_id",optional(r.track_id)},{"side",optional(r.side)},{"regions",Json::array()}}; + if(!r.risk_type.empty()){out["risk_type"]=r.risk_type;out["door_id"]=optional(r.door_id);} + if(!r.door_mask.status.empty()) { + auto const& d=r.door_mask;out["door_mask"]={{"status",d.status},{"annotation_index",d.annotation_index},{"path",optional(d.path)},{"format",d.format},{"area_px",d.area_px}}; + out["warning_distance_relative"]=r.warning_distance_relative;out["release_distance_relative"]=r.release_distance_relative; + } + for(auto const& v:r.regions) { + Json j={{"status",v.status},{"risk_score",optional(v.risk_score)},{"risk_level",v.risk_level},{"decision",v.decision},{"source",v.source},{"track_id",v.track_id},{"side",v.side}}; + if(v.state)j["state"]=*v.state; + if(!v.door_zone.empty())j["door_zone"]=v.door_zone; + if(v.region_area_px) { + j["region_area_px"]=optional(v.region_area_px);j["door_overlap_px"]=optional(v.door_overlap_px);j["region_overlap_ratio"]=optional(v.region_overlap_ratio); + j["min_distance_px"]=optional(v.min_distance_px);j["region_scale_px"]=optional(v.region_scale_px);j["distance_relative"]=optional(v.distance_relative); + } + out["regions"].push_back(std::move(j)); + } + return out; +} +} +Json serializeResult(FrameResult const& r) { + Json out={{"frame_index",r.frame_index},{"track_ids",r.track_ids},{"boxes",r.boxes},{"skeletons",Json::array()},{"active_door",r.active_door},{"door_assignments",Json::array()},{"door_risks",Json::object()},{"risk",riskJson(r.risk)},{"ungated_risk",riskJson(r.ungated_risk)}}; + for(std::size_t i=0;igetTensorDataType(name.c_str())); + if (item.external) { + if (item.capacity < item.required) { + throw std::runtime_error("External buffer is too small for " + name); + } + continue; + } if (item.capacity < item.required) { if (item.owned) CUDA_CHECK(cudaFree(item.owned)); CUDA_CHECK(cudaMalloc(&item.owned, item.required)); @@ -53,6 +59,19 @@ void TrtEngine::prepare() { } } +void TrtEngine::bindExternal(std::string const& name, void* pointer, std::size_t bytesValue) { + auto& item = buffer(name); + item.active = pointer; + item.capacity = bytesValue; + item.external = true; +} + +void TrtEngine::useOwned(std::string const& name) { + auto& item = buffer(name); + item.external = false; + item.active = item.owned; +} + bool TrtEngine::enqueue(cudaStream_t stream) { for (int index = 0; index < engine_->getNbIOTensors(); ++index) { std::string const name = engine_->getIOTensorName(index); diff --git a/native/uyvy_converter.cu b/src/uyvy_converter.cu similarity index 100% rename from native/uyvy_converter.cu rename to src/uyvy_converter.cu diff --git a/native/v4l2_capture.cpp b/src/v4l2_capture.cpp similarity index 100% rename from native/v4l2_capture.cpp rename to src/v4l2_capture.cpp diff --git a/src/visualizer.cpp b/src/visualizer.cpp new file mode 100644 index 0000000..525d096 --- /dev/null +++ b/src/visualizer.cpp @@ -0,0 +1,99 @@ +#include "visualizer.hpp" +#include "camera_pipeline.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cms { +namespace { +constexpr char windowName[]="CMS Camera Risk"; +cv::Point point(PosePoint const& p){return {int(std::nearbyint(p.x)),int(std::nearbyint(p.y))};} +cv::Scalar color(std::string const& level) { + if(level=="danger")return {40,40,255}; + if(level=="warning")return {0,160,255}; + if(level=="watch")return {0,240,240}; + if(level=="clear")return {80,220,90}; + return {190,190,190}; +} +void segment(cv::Mat& image,cv::Point a,cv::Point b,cv::Scalar c,int thickness,bool dashed) { + if(!dashed){cv::line(image,a,b,c,thickness,cv::LINE_AA);return;} + cv::Point2d d=b-a;double length=cv::norm(d);if(length<1)return;d*=1.0/length; + for(double x=0;x(image.pixels.data()),image.stride); + auto canvas=view.clone(); + if(r.door_mask_pixels) { + auto const& m=*r.door_mask_pixels; + if(m.width!=image.width||m.height!=image.height||m.stride(m.pixels.data()),m.stride); + auto tint=canvas.clone();tint.setTo(cv::Scalar(255,180,0),mask); + cv::addWeighted(canvas,0.75,tint,0.25,0,canvas); + std::vector> contours;cv::findContours(mask.clone(),contours,cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE); + cv::drawContours(canvas,contours,-1,{255,180,0},2); + } + for(auto const& person:r.persons) { + auto const& b=person.box; + cv::rectangle(canvas,{int(b[0]),int(b[1])},{int(b[2]),int(b[3])},{0,220,255},2); + std::ostringstream text;text<<"ID "<>{{5+s,7+s},{7+s,9+s}}) { + segment(canvas,point(k[e.first]),point(k[e.second]),state.limb_consensus?arm:cv::Scalar(0,140,255),3,!state.limb_consensus); + cv::circle(canvas,point(k[e.second]),4,arm,-1,cv::LINE_AA); + } + if(state.state=="rejected")continue; + int start=s==0?91:112; + auto hand=state.distance_eligible?(s==0?cv::Scalar(255,80,220):cv::Scalar(255,190,40)):cv::Scalar(0,140,255); + for(int f=0;f<5;++f)for(int j=0;j<4;++j){int a=j==0?start:start+f*4+j;int b=start+1+f*4+j;segment(canvas,point(k[a]),point(k[b]),hand,2,!state.distance_eligible);} + for(int j=0;j<21;++j)cv::circle(canvas,point(k[start+j]),3,hand,-1,cv::LINE_AA); + } + } + for(auto const& h:r.hand_regions) { + std::vector points,hull;for(auto const& p:h.keypoints)points.push_back(point(p));cv::convexHull(points,hull); + auto risk=std::find_if(r.risk.regions.begin(),r.risk.regions.end(),[&](auto const& item){return item.track_id==h.track_id&&item.side==h.side;}); + auto c=color(risk==r.risk.regions.end()?"unavailable":risk->risk_level); + cv::polylines(canvas,std::vector>{hull},true,c,2,cv::LINE_AA); + } + for(auto const& region:r.risk.regions)if(region.source=="YOLO_BODY_BOX_FALLBACK") { + auto it=std::find(r.track_ids.begin(),r.track_ids.end(),region.track_id); + if(it!=r.track_ids.end()){auto const& b=r.boxes.at(it-r.track_ids.begin());cv::putText(canvas,"BODY FALLBACK",{int(b[0])+4,int(b[1])+50},cv::FONT_HERSHEY_SIMPLEX,.65,{0,160,255},2,cv::LINE_AA);} + } + cv::rectangle(canvas,{0,0},{canvas.cols,148},{20,24,30},-1); + std::ostringstream headline;headline< +// SPDX-License-Identifier: MIT + +/****************************************************************************\ + * Note on documentation: The source files contain links to the online * + * documentation of the public API at https://json.nlohmann.me. This URL * + * contains the most recent documentation and should also be applicable to * + * previous versions; documentation for deprecated functions is not * + * removed, but marked deprecated. See "Generate documentation" section in * + * file docs/README.md. * +\****************************************************************************/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#ifndef JSON_NO_IO + #include // istream, ostream +#endif // JSON_NO_IO +#include // random_access_iterator_tag +#include // unique_ptr +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // nullptr_t +#include // exception +#if JSON_DIAGNOSTICS + #include // accumulate +#endif +#include // runtime_error +#include // to_string +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // uint8_t +#include // string + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // declval, pair +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +struct is_detected_lazy : is_detected { }; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-License-Identifier: MIT + +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 15 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(JSON_HEDLEY_MSVC_VERSION) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions (except those affecting ABI) +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// #include + + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +// if the user manually specified the used c++ version this is skipped +#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 + #endif + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 +#endif + +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + +#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) + #ifdef JSON_HAS_CPP_17 + #if defined(__cpp_lib_filesystem) + #define JSON_HAS_FILESYSTEM 1 + #elif defined(__cpp_lib_experimental_filesystem) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif !defined(__has_include) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #endif + + // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ + #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__clang_major__) && __clang_major__ < 7 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support + #if defined(_MSC_VER) && _MSC_VER < 1914 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before iOS 13 + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before macOS Catalina + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + #endif +#endif + +#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_FILESYSTEM + #define JSON_HAS_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #else + #define JSON_HAS_THREE_WAY_COMPARISON 0 + #endif +#endif + +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifndef JSON_HAS_STATIC_RTTI + #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0 + #define JSON_HAS_STATIC_RTTI 1 + #else + #define JSON_HAS_STATIC_RTTI 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" + #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#endif + +// allow disabling exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow overriding assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow to access some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType, \ + class CustomBaseClass> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +// inspired from https://stackoverflow.com/a/26745591 +// allows to call any std function as if (e.g. with begin): +// using std::begin; begin(x); +// +// it allows using the detected idiom to retrieve the return type +// of such an expression +#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ + namespace detail { \ + using std::std_name; \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + } \ + \ + namespace detail2 { \ + struct std_name##_tag \ + { \ + }; \ + \ + template \ + std_name##_tag std_name(T&&...); \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + \ + template \ + struct would_call_std_##std_name \ + { \ + static constexpr auto const value = ::nlohmann::detail:: \ + is_detected_exact::value; \ + }; \ + } /* namespace detail2 */ \ + \ + template \ + struct would_call_std_##std_name : detail2::would_call_std_##std_name \ + { \ + } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + +#ifndef JSON_USE_GLOBAL_UDLS + #define JSON_USE_GLOBAL_UDLS 1 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +template +inline void replace_substring(StringType& s, const StringType& f, + const StringType& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != StringType::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +template +inline StringType escape(StringType s) +{ + replace_substring(s, StringType{"~"}, StringType{"~0"}); + replace_substring(s, StringType{"/"}, StringType{"~1"}); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +template +static void unescape(StringType& s) +{ + replace_substring(s, StringType{"~1"}, StringType{"/"}); + replace_substring(s, StringType{"~0"}, StringType{"~"}); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2018 The Abseil Authors +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +using uncvref_t = typename std::remove_cv::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. + +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence +{ + using value_type = T; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template +using index_sequence = integer_sequence; + +namespace utility_internal +{ + +template +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template +struct Extend, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template +struct Extend, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence'. +// 'Gen::type' is an alias for 'integer_sequence'. +template +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template +struct Gen +{ + using type = integer_sequence; +}; + +} // namespace utility_internal + +// Compile-time sequences of integers + +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template +using make_integer_sequence = typename utility_internal::Gen::type; + +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template +using make_index_sequence = make_integer_sequence; + +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template +using index_sequence_for = make_index_sequence; + +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static JSON_INLINE_VARIABLE constexpr T value{}; +}; + +#ifndef JSON_HAS_CPP_17 + template + constexpr T static_const::value; +#endif + +template +inline constexpr std::array make_array(Args&& ... args) +{ + return std::array {{static_cast(std::forward(args))...}}; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval +#include // tuple +#include // char_traits + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // random_access_iterator_tag + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); + +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); + +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ + #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + #include // int64_t, uint64_t + #include // map + #include // allocator + #include // string + #include // vector + + // #include + + + /*! + @brief namespace for Niels Lohmann + @see https://github.com/nlohmann + @since version 1.0.0 + */ + NLOHMANN_JSON_NAMESPACE_BEGIN + + /*! + @brief default JSONSerializer template argument + + This serializer ignores the template arguments and uses ADL + ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) + for serialization. + */ + template + struct adl_serializer; + + /// a class to store JSON values + /// @sa https://json.nlohmann.me/api/basic_json/ + template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> + class basic_json; + + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document + /// @sa https://json.nlohmann.me/api/json_pointer/ + template + class json_pointer; + + /*! + @brief default specialization + @sa https://json.nlohmann.me/api/json/ + */ + using json = basic_json<>; + + /// @brief a minimal map-like container that preserves insertion order + /// @sa https://json.nlohmann.me/api/ordered_map/ + template + struct ordered_map; + + /// @brief specialization that maintains the insertion order of object keys + /// @sa https://json.nlohmann.me/api/ordered_json/ + using ordered_json = basic_json; + + NLOHMANN_JSON_NAMESPACE_END + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +NLOHMANN_JSON_NAMESPACE_BEGIN +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ + +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// used by exceptions create() member functions +// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t +// false_type otherwise +template +struct is_basic_json_context : + std::integral_constant < bool, + is_basic_json::type>::type>::value + || std::is_same::value > +{}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +template +using detect_key_compare = typename T::key_compare; + +template +struct has_key_compare : std::integral_constant::value> {}; + +// obtains the actual object key comparator +template +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template +using actual_object_comparator_t = typename actual_object_comparator::type; + +///////////////// +// char_traits // +///////////////// + +// Primary template of char_traits calls std char_traits +template +struct char_traits : std::char_traits +{}; + +// Explicitly define char traits for unsigned char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = unsigned char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(EOF); + } +}; + +// Explicitly define char traits for signed char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = signed char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(EOF); + } +}; + +/////////////////// +// is_ functions // +/////////////////// + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B { }; +template +struct conjunction +: std::conditional(B::value), conjunction, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template struct negation : std::integral_constant < bool, !B::value > { }; + +// Reimplementation of is_constructible and is_default_constructible, due to them being broken for +// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). +// This causes compile errors in e.g. clang 3.5 or gcc 4.9. +template +struct is_default_constructible : std::is_default_constructible {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_constructible : std::is_constructible {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +template +struct is_range +{ + private: + using t_ref = typename std::add_lvalue_reference::type; + + using iterator = detected_t; + using sentinel = detected_t; + + // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator + // and https://en.cppreference.com/w/cpp/iterator/sentinel_for + // but reimplementing these would be too much work, as a lot of other concepts are used underneath + static constexpr auto is_iterator_begin = + is_iterator_traits>::value; + + public: + static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; +}; + +template +using iterator_t = enable_if_t::value, result_of_begin())>>; + +template +using range_value_t = value_type_t>>; + +// The following implementation of is_complete_type is taken from +// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ +// and is written by Xiang Fan who agreed to using it in this library. + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + is_constructible::value && + is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type +{ + static constexpr auto value = + is_constructible::value; +}; + +template +struct is_constructible_string_type +{ + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + + static constexpr auto value = + conjunction < + is_constructible, + is_detected_exact>::value; +}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < + is_detected::value&& + is_iterator_traits>>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 + !std::is_same>::value >> +{ + static constexpr bool value = + is_constructible>::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + !is_compatible_string_type::value&& + is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_iterator_traits>>::value&& +is_detected::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 +!std::is_same>::value&& + is_complete_type < + detected_t>::value >> +{ + using value_type = range_value_t; + + static constexpr bool value = + std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, + value_type >::value; +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; + +template +struct is_json_iterator_of : std::false_type {}; + +template +struct is_json_iterator_of : std::true_type {}; + +template +struct is_json_iterator_of : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template