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 0000000..fac012a Binary files /dev/null and b/orin/build/camera_risk differ diff --git a/orin/build/libvision_native.so b/orin/build/libvision_native.so deleted file mode 100755 index c2d27d4..0000000 Binary files a/orin/build/libvision_native.so and /dev/null differ 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