{ "schema_version": 1, "project": { "name": "fall-detection", "version": "1.0.0", "task": "XGBoost fall classification from 56-feature pose vectors (image → YOLO upstream)", "python": ">=3.10" }, "paths": { "pose_model": "../yolo26x-pose.pt", "classifier_model": "models/xgboost_priority1_fall_model.pkl", "output_dir": "output" }, "models": { "pose_model_type": "Ultralytics YOLO-Pose (upstream feature generation only, not classifier input)", "classifier_type": "XGBClassifier", "class_names": { "0": "Normal", "1": "Fall" }, "feature_count": 56, "classifier_feature_count": 56, "csv_total_columns": 58, "column_breakdown": "51 pose features + 5 additional Priority 1 features + 1 split + 1 label = 58 columns; XGBoost uses only 51 + 5 = 56 (columns 1-56)", "row_construction": "row = norm_kpts (51) + [aspect_ratio] + p1_features (4) + [split, label] → 51 + 5 + 1 + 1 = 58" }, "thresholds": { "yolo_confidence": 0.25, "keypoint_confidence": 0.5, "fall_probability": 0.7, "_notes": "yolo_confidence is upstream YOLO detection threshold (not a classifier parameter); keypoint_confidence < threshold → NaN x/y in 56-vector (preserved confidence); fall_probability is XGBoost P(Fall) decision threshold" }, "feature_schema": { "keypoint_count": 17, "values_per_keypoint": [ "x_normalized", "y_normalized", "confidence" ], "keypoint_feature_count": 51, "bounding_box_feature_count": 1, "priority1_feature_count": 4, "additional_feature_count": 5, "additional_features": [ "aspect_ratio", "nose_relative_y", "torso_angle", "norm_com_y", "head_hip_v_dist" ], "classifier_feature_count": 56, "classifier_columns": "1-56", "metadata_columns": [ "split", "label" ], "metadata_count": 2, "metadata_indices": { "split": 57, "label": 58 }, "csv_total_columns": 58, "csv_total_breakdown": "51 pose (cols 1-51) + 5 additional (cols 52-56) = 56 classifier features; + 1 split (col 57, train/val/test) + 1 label (col 58, 0=Normal/1=Fall) = 58", "row_construction": "pose_extract.py: row = norm_kpts (51) + [aspect_ratio] + p1_features (4) + [split, label] → 51 + 5 + 1 + 1 = 58; XGBoost uses only 51 + 5 = 56 (columns 1-56)", "column_indices": { "pose_features": "1-51", "additional_features": "52-56", "split": 57, "label": 58 }, "total_feature_count": 56, "low_confidence_coordinates": "NaN", "low_confidence_confidence_value": "preserved", "bounding_box_features": [ "aspect_ratio" ], "priority1_features": [ "nose_relative_y", "torso_angle", "norm_com_y", "head_hip_v_dist" ] }, "input": { "pipeline_csv_input": { "type": "pandas.DataFrame", "consumer": "scripts/run.py", "description": "Upstream delivers a DataFrame with 51 normalized keypoint features + bounding box (55 columns total) per row; run.py extracts the 5 Priority 1 features (NOT input), builds a 56-column DataFrame (51+5), and passes that DataFrame (not a CSV) to XGBoost", "keypoint_columns": "x_0,y_0,conf_0,...,x_16,y_16,conf_16 (51 columns, x/y normalized to bbox; NaN allowed for occluded)", "keypoint_feature_count": 51, "bbox_columns": ["x1", "y1", "x2", "y2"], "bbox_feature_count": 4, "input_column_count": 55, "input_breakdown": "51 keypoint (x_i,y_i,conf_i i=0..16) + 4 bbox (x1,y1,x2,y2) = 55 total input to run.py", "bbox_units": "pixels", "computed_in_run_py": ["aspect_ratio", "nose_relative_y", "torso_angle", "norm_com_y", "head_hip_v_dist"], "extracted_count": 5, "model_input": "pandas.DataFrame, n rows × 56 columns (FEATURE_COLS = 51 input + 5 extracted)", "model_feature_count": 56, "output_columns": ["prediction", "fall_probability"], "min_valid_keypoints": 5, "cli_note": "CSV path on CLI is only upstream transport; it is loaded into a DataFrame before feature extraction" }, "classifier_input": { "type": "feature_vector", "dimensions": 56, "dtype": "float", "missing_value": "NaN", "description": "Direct XGBoost input: 56-column pandas DataFrame (one row per person) = 51 pose features (17 keypoints × x_norm, y_norm, confidence) + 5 additional Priority 1 features; no image or CSV input at inference", "column_breakdown": "51 + 5 = 56 classifier features (cols 1-56); cols 57-58 (split, label) are metadata, not input", "very_important": "XGBoost does NOT use split or label as input features; label is supervision only" }, "upstream_image_input": { "_note": "Images are processed upstream by YOLO26x-Pose to generate the 56-vector; not direct classifier input", "formats": [ ".jpg", ".jpeg", ".png", ".bmp" ], "color_format": "BGR", "default_image": null } }, "output": { "classifier_output": { "type": "binary_label", "labels": ["Normal", "Fall"], "label_ids": {"Normal": 0, "Fall": 1}, "decision_rule": "P(Fall) >= fall_probability (0.7) → Fall, else Normal", "probabilities": ["P(Normal)", "P(Fall)"] }, "save_annotated_default": false, "filename_prefix": "annotated_" } }