DronePhotographer generates synthetic drone-camera views in Blender, annotates the rendered images with object detections, and trains a Qwen vision-language model to predict composition scores after a camera action.
The training task is:
image_i + action text (move + target orientation)image_jThe current configs default to rule-based bbox controllability targets derived from GroundingDINO detections. The codebase also supports subject-aware score keys if those fields are added to the annotations.
scripts/render_object.py: Blender rendering entry point. Produces RGB images, optional depth maps, annotations.json, and run_info.json.src/detectors/detector.py: GroundingDINO wrapper plus optional visualization helpers.src/scoring/: score extraction and normalization utilities.src/vlm_qwen25/: dataset construction, prompt formatting, rotation math, collator, JSON parsing, MPC planner, and composition objectives.scripts/train.py: Hugging Face Trainer entry point for Qwen image-text models.scripts/eval_qwen25_vl.py: offline evaluation with MAE/RMSE and JSON parse-failure tracking.scripts/predict_qwen25_vl.py: single-image prediction.scripts/infer_mpc.py: MPC-based camera planning using pre-rendered views.scripts/infer_mpc_blender.py: MPC planning with Blender re-rendering at each step.scripts/infer_mpc_*.sh: launcher scripts for specific composition presets.configs/: training configs for Qwen2.5-VL-7B plus Qwen3.5 2B and 9B variants.scripts/render_object.py.scripts/annotate_detections.py.score_* fields with scripts/score_annotations.py.image_ii to view jimage_jinfer_mpc.py or infer_mpc_blender.py using a trained model and a composition objective.Important: the default training configs use annotations_detected.json, not annotations_scored.json. For rule-based bbox targets, the dataset recomputes scores from the stored detections at load time.
Create an environment for training / scoring scripts and install:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
requirements.txt expects a PyTorch-capable environment and includes:
torchtransformers>=5.2.0qwen-vl-utilsacceleratedeepspeedtensorboardPillow, numpy, PyYAML, tqdmopencv-python, supervision, matplotlib, pycocotoolsDetection is optional for rendering, but required for the default training pipeline.
pip install --no-build-isolation -e ./repos/GroundingDINO
The detection scripts assume:
repos/GroundingDINO/groundingdino/config/GroundingDINO_SwinT_OGC.pyrepos/GroundingDINO/weights/groundingdino_swint_ogc.pthblender/blender./home/nas5/jungwooahn/datasets/DronePhotos/....Override those paths with flags or environment variables before running the launcher scripts.
bash scripts/smoke_render_object.sh
Supported environment overrides:
BLENDER_BINSCENE_PATHOUTPUT_DIRNUM_IMAGESGPU_BACKENDGPU_DEVICESbash scripts/render_object.sh
This wrapper defaults to a 10k-image DogWalk render and writes a run directory under outputs/.
Use one of these subject selection modes:
--object_position x y z--object_name <scene_object_name>--auto_place_object --input_object <path>Selection precedence in the renderer is:
--object_position--auto_place_object--object_nameExample:
blender/blender -b -P scripts/render_object.py -- \
--input_scene /abs/path/to/DogWalk.blend \
--output_dir outputs \
--run_name demo \
--object_position -0.011 0.0364 0.8 \
--num_images 200 \
--gpu_backend OPTIX \
--gpu_devices 6 7 \
--camera_radius_range 2 8 \
--hemisphere \
--camera_direction_offsets 15 15 0 \
--samples 32 \
--adaptive_sampling --adaptive_threshold 0.02 \
--max_bounces 2 --diffuse_bounces 1 --glossy_bounces 1 --transmission_bounces 1 \
--persistent_data
Useful renderer features:
images/img_XXXX.png.--render_depth also writes depth/img_XXXX.(png|exr).--gpu_backend supports AUTO, OPTIX, or CUDA.--auto_place_object imports .glb, .gltf, .fbx, .obj, or .blend assets and ray-casts for a flat placement region.24.012.89.61024x768Each render run creates:
images/depth/annotations.jsonrun_info.jsonannotations.json contains one record per rendered image. The base schema is:
[
{
"image": "images/img_0000.png",
"camera_position": [3.39, 4.94, 3.52],
"radius": 6.56,
"object_position": [-0.011, 0.0364, 0.8],
"base_forward": [-0.5194, -0.7477, -0.4138],
"base_up": [-0.2361, -0.3399, 0.9104],
"offsets_deg": {"yaw": -4.6563, "pitch": 13.8792, "roll": 0.0},
"final_forward": [-0.3124, -0.8689, -0.3840],
"final_up": [-0.3020, -0.2924, 0.9074],
"depth": "depth/img_0000.png"
}
]
Later stages may add:
promptdetectionsscore_<key>python scripts/annotate_detections.py \
--annotations_path outputs/DogWalk_v2_10k_260309_101152/annotations.json \
--image_root outputs/DogWalk_v2_10k_260309_101152 \
--caption "a white snowman" \
--device cuda \
--output_path outputs/DogWalk_v2_10k_260309_101152/annotations_detected.json
This appends:
promptdetections: [{"label": ..., "score": ..., "bbox_xyxy": [...]}]python scripts/visualize_detections.py \
--annotations_path outputs/DogWalk_v2_10k_260309_101152/annotations_detected.json \
--image_root outputs/DogWalk_v2_10k_260309_101152 \
--output_dir outputs/DogWalk_v2_10k_260309_101152/detection_viz \
--limit 20
python scripts/score_annotations.py \
--annotations_path outputs/DogWalk_v2_10k_260309_101152/annotations_detected.json \
--image_root outputs/DogWalk_v2_10k_260309_101152 \
--output_path outputs/DogWalk_v2_10k_260309_101152/annotations_scored.json
This computes score_* fields from the primary detected box.
Current rule-based keys are:
bbox_occupancy_ratiobbox_margin_topbbox_margin_bottombbox_margin_leftbbox_margin_rightbbox_aspect_ratiobbox_centroid_offsetNotes:
area * score when detection scores are available.bbox_aspect_ratio are clamped to [0, 1].Two score families are implemented.
This is the default path used by the provided configs. It only requires detections in the annotation file.
Supported subject-aware keys:
subject_dominancerule_of_thirdslead_roomfigure_ground_separationleading_linesavoiding_mergescenter_of_gravityclear_marginsIf you switch data.target_score_keys to subject-aware keys, the dataset expects matching fields such as score_subject_dominance to already exist in the annotation JSON. This repo includes the prompt builder, but does not include a batch subject-aware annotation script.
Training pairs are not stored on disk. DroneActionScoreDataset builds them from the view annotations at load time.
Pair construction rules:
> 0 and <= data.distance_thresholdj must contain detectionsdata.max_pairs_per_image caps sampled neighbors per source imagedata.zero_action_ratio adds self-pairs (i, i) with a zero actiondata.eval_ratio splits the pair list into train and eval subsetsAction text is generated from the relative transform between view i and view j.
The default configs now use data.rotation_representation: orientation_6d, which encodes the target camera orientation with target forward and up vectors instead of an axis-angle rotvec.
Camera-local format:
move_camera_local_m(right=0.2000, up=-0.1000, forward=0.0000); orient_camera_local_6d(fx=0.0000, fy=0.1000, fz=0.9950, ux=0.0000, uy=0.9950, uz=-0.1000)
World-frame format:
move_world_m(x=0.2000, y=-0.1000, z=0.0000); orient_world_6d(fx=0.0000, fy=0.1000, fz=0.9950, ux=0.0000, uy=0.9950, uz=-0.1000)
Legacy option:
data.rotation_representation: rotvec keeps the old rotate_*_axis_angle_rad(rx, ry, rz) format.+right, +up, +forward where +forward means view direction.final_forward and final_up vectors are world-frame unit vectors.+X = right, +Y = up, -Z = forward.data.action_frame can be camera_local or world.data.rotation_representation can be orientation_6d or rotvec.camera_local.Use the consistency checker after rotation-related changes:
python scripts/check_rotation_consistency.py \
--annotations_path outputs/DogWalk_v2_10k_260309_101152/annotations.json \
--max_views 1000 \
--pair_samples 500 \
--strict
python scripts/train.py --config configs/qwen25_vl_7b_full.yaml
Supported config sections:
modeldatatrainingThe provided configs are:
configs/qwen25_vl_7b_full.yamlconfigs/qwen25_vl_7b_2xh200.yamlconfigs/qwen35_vl_2b_1xh200.yamlconfigs/qwen35_vl_2b_4xa100_40g.yamlconfigs/qwen35_vl_9b_1xh200.yamlconfigs/qwen35_vl_9b_2xh200.yamlThe default data settings in those configs currently point to:
outputs/DogWalk_v2_10k_260309_101152/annotations_detected.jsonoutputs/DogWalk_v2_10k_260309_101152Qwen2.5-VL on 2xH200:
bash scripts/train_qwen25_vl_2_h200.sh
Qwen3.5-2B on 1xH200:
bash scripts/train_qwen35_vl_2b_1_h200.sh
Qwen3.5-9B on 2xH200:
bash scripts/train_qwen35_vl_9b_2_h200.sh
Qwen3.5-9B on 1xH200:
bash scripts/train_qwen35_vl_9b_1_h200.sh
Useful overrides:
GPU_ID=4 bash scripts/train_qwen35_vl_2b_1_h200.shGPU_ID=4 bash scripts/train_qwen35_vl_9b_1_h200.shMASTER_PORT=29601 bash scripts/train_qwen25_vl_2_h200.shThe 2-GPU launcher scripts auto-pick a free torchrun master port when MASTER_PORT is unset.
Successful training writes a run directory like:
runs/<timestamp>_<run_name>/
Expected contents:
config.yamlcheckpoints/final/summary.jsonsummary.json includes:
python scripts/eval_qwen25_vl.py \
--config configs/qwen25_vl_7b_full.yaml \
--model_path runs/<run_dir>/final
Outputs:
runs/qwen25_vl_eval_report.json by defaultmaermseparse_failure_rateIf data.eval_ratio > 0, evaluation runs on that split. Otherwise the full dataset is used.
python scripts/predict_qwen25_vl.py \
--model_path runs/<run_dir>/final \
--image_path outputs/DogWalk_v2_10k_260309_101152/images/img_0000.png \
--action_frame camera_local \
--rotation_representation orientation_6d \
--action_text "move_camera_local_m(right=0.2, up=-0.1, forward=0.0); orient_camera_local_6d(fx=0.0, fy=0.1, fz=0.9950, ux=0.0, uy=0.9950, uz=-0.1)"
The script prints:
Once a model is trained, MPC-based planning finds camera actions that optimize toward a target composition objective. Two planning modes are available.
infer_mpc.py generates candidate actions, scores them with the VLM, picks the best action, and snaps to the nearest pre-rendered view.
python scripts/infer_mpc.py \
--config configs/qwen35_vl_2b_1xh200.yaml \
--model_path runs/<run_dir>/final \
--start_index 0 \
--num_steps 8 \
--target_preset centered_50
infer_mpc_blender.py re-renders in Blender at each planning step instead of snapping to existing views. This is slower but produces realistic rollouts.
python scripts/infer_mpc_blender.py \
--run_dir outputs/DogWalk_v2_10k_260309_101152 \
--model_path runs/<run_dir>/final \
--config configs/qwen35_vl_2b_1xh200.yaml \
--blender_bin blender/blender \
--num_steps 16 \
--target_preset centered_50
Pass --evaluate_with_detector to run GroundingDINO on each rendered frame and log actual bbox scores alongside VLM predictions.
Target presets define photographic composition goals using high-level parameters (center_x, center_y, occupancy, aspect_ratio) that are automatically converted into margin-based score targets.
List available presets:
python scripts/infer_mpc.py --list_target_presets
Built-in presets:
| Preset | Description |
|---|---|
centered_50 | 50% occupancy, centered subject |
centered_square_medium | 30% occupancy, 1:1 aspect, centered |
centered_square_close | 55% occupancy, 1:1 aspect, centered |
centered_wide_18 | 1.8:1 wide format, centered |
centered_wide_close_18 | 48% occupancy, 1.8:1 wide, centered |
cinematic_center_wide | 35% occupancy, 1.8:1 wide, centered |
centered_portrait_medium | 30% occupancy, 0.67:1 portrait, centered |
centered_portrait_close | 40% occupancy, 0.67:1 portrait, centered |
top_right_thirds_medium | 25% occupancy, 1:1 aspect, rule-of-thirds placement |
top_right_thirds_portrait_medium | 20% occupancy, 0.67:1 portrait, rule-of-thirds |
Use --target_json to pass a custom target instead of a preset:
python scripts/infer_mpc_blender.py \
--run_dir outputs/DogWalk_v2_10k_260309_101152 \
--model_path runs/<run_dir>/final \
--target_json '{"bbox_occupancy_ratio":0.67,"bbox_margin_top":0.02,"bbox_margin_bottom":0.0,"bbox_margin_left":0.16,"bbox_margin_right":0.16,"bbox_aspect_ratio":0.69}'
Override per-key optimization weights with --score_weights_json:
--score_weights_json '{"bbox_occupancy_ratio":2.0,"bbox_centroid_offset":2.0}'
Default weights assign 2.0 to bbox_occupancy_ratio and bbox_centroid_offset, 1.0 to all others.
Pre-configured shell scripts are provided for common composition goals:
bash scripts/infer_mpc_centered_50.sh
bash scripts/infer_mpc_cinematic_center_wide.sh
bash scripts/infer_mpc_top_right_thirds_medium.sh
bash scripts/infer_mpc_aggressive_closeup_crop.sh
# ... and more under scripts/infer_mpc_*.sh
Each script sets RUN_DIR, MODEL_PATH, score weights, and target JSON. Override environment variables before running:
CANDIDATE_BATCH_SIZE (default: 96)INITIAL_SEED (default: 721)SCORE_WEIGHTS_JSONEach rollout writes to runs/infer_mpc/ (or runs/infer_mpc_blender/):
<timestamp>_mpc_rollout/
├── trajectory.json # full rollout log: steps, candidates, scores, errors
├── frames/ # per-step frame images
└── rollout.mp4 # (or rollout.gif fallback)
trajectory.json includes initial and final target error, per-step top-K candidates with predicted scores, and snap cost for offline MPC.
--num_steps: number of planning steps (default: 8 offline, 16 Blender)--translation_values_m: discrete translation grid (CSV, default: -0.25,0,0.25)--rotation_values_deg: discrete rotation grid (CSV, default: -6,0,6)--max_translation_norm_m: filter out candidates exceeding this norm--max_rotation_norm_deg: filter out candidates exceeding this rotation--translation_penalty_weight: regularization on translation magnitude--rotation_penalty_weight: regularization on rotation magnitude--parse_failure_penalty: penalty for candidates where JSON parsing fails--candidate_batch_size: VLM batch size for scoring candidatesdata.distance_threshold: maximum source-target camera distance used to form a pairdata.max_pairs_per_image: cap on neighbor samples per source imagedata.zero_action_ratio: fraction of no-op self-pairs added to the final datasetdata.target_score_keys: output JSON keys and orderingdata.action_frame: camera_local or worlddata.rotation_representation: orientation_6d or rotvectraining.max_length: token budget for the image-text sequencetraining.deepspeed_config: optional ZeRO-3 configtraining.resume_from_checkpoint: resume path passed to the HF trainerdocs/figures/token_report_qwen25.json contains a tokenization/debug snapshot for one sample.transformers in the training environment.HTML
96.8%
Python
3.1%
DronePhotographer generates synthetic drone-camera views in Blender, annotates the rendered images with object detections, and trains a Qwen vision-language model to predict composition scores after a camera action.
The training task is:
image_i + action text (move + target orientation)image_jThe current configs default to rule-based bbox controllability targets derived from GroundingDINO detections. The codebase also supports subject-aware score keys if those fields are added to the annotations.
scripts/render_object.py: Blender rendering entry point. Produces RGB images, optional depth maps, annotations.json, and run_info.json.src/detectors/detector.py: GroundingDINO wrapper plus optional visualization helpers.src/scoring/: score extraction and normalization utilities.src/vlm_qwen25/: dataset construction, prompt formatting, rotation math, collator, JSON parsing, MPC planner, and composition objectives.scripts/train.py: Hugging Face Trainer entry point for Qwen image-text models.scripts/eval_qwen25_vl.py: offline evaluation with MAE/RMSE and JSON parse-failure tracking.scripts/predict_qwen25_vl.py: single-image prediction.scripts/infer_mpc.py: MPC-based camera planning using pre-rendered views.scripts/infer_mpc_blender.py: MPC planning with Blender re-rendering at each step.scripts/infer_mpc_*.sh: launcher scripts for specific composition presets.configs/: training configs for Qwen2.5-VL-7B plus Qwen3.5 2B and 9B variants.scripts/render_object.py.scripts/annotate_detections.py.score_* fields with scripts/score_annotations.py.image_ii to view jimage_jinfer_mpc.py or infer_mpc_blender.py using a trained model and a composition objective.Important: the default training configs use annotations_detected.json, not annotations_scored.json. For rule-based bbox targets, the dataset recomputes scores from the stored detections at load time.
Create an environment for training / scoring scripts and install:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
requirements.txt expects a PyTorch-capable environment and includes:
torchtransformers>=5.2.0qwen-vl-utilsacceleratedeepspeedtensorboardPillow, numpy, PyYAML, tqdmopencv-python, supervision, matplotlib, pycocotoolsDetection is optional for rendering, but required for the default training pipeline.
pip install --no-build-isolation -e ./repos/GroundingDINO
The detection scripts assume:
repos/GroundingDINO/groundingdino/config/GroundingDINO_SwinT_OGC.pyrepos/GroundingDINO/weights/groundingdino_swint_ogc.pthblender/blender./home/nas5/jungwooahn/datasets/DronePhotos/....Override those paths with flags or environment variables before running the launcher scripts.
bash scripts/smoke_render_object.sh
Supported environment overrides:
BLENDER_BINSCENE_PATHOUTPUT_DIRNUM_IMAGESGPU_BACKENDGPU_DEVICESbash scripts/render_object.sh
This wrapper defaults to a 10k-image DogWalk render and writes a run directory under outputs/.
Use one of these subject selection modes:
--object_position x y z--object_name <scene_object_name>--auto_place_object --input_object <path>Selection precedence in the renderer is:
--object_position--auto_place_object--object_nameExample:
blender/blender -b -P scripts/render_object.py -- \
--input_scene /abs/path/to/DogWalk.blend \
--output_dir outputs \
--run_name demo \
--object_position -0.011 0.0364 0.8 \
--num_images 200 \
--gpu_backend OPTIX \
--gpu_devices 6 7 \
--camera_radius_range 2 8 \
--hemisphere \
--camera_direction_offsets 15 15 0 \
--samples 32 \
--adaptive_sampling --adaptive_threshold 0.02 \
--max_bounces 2 --diffuse_bounces 1 --glossy_bounces 1 --transmission_bounces 1 \
--persistent_data
Useful renderer features:
images/img_XXXX.png.--render_depth also writes depth/img_XXXX.(png|exr).--gpu_backend supports AUTO, OPTIX, or CUDA.--auto_place_object imports .glb, .gltf, .fbx, .obj, or .blend assets and ray-casts for a flat placement region.24.012.89.61024x768Each render run creates:
images/depth/annotations.jsonrun_info.jsonannotations.json contains one record per rendered image. The base schema is:
[
{
"image": "images/img_0000.png",
"camera_position": [3.39, 4.94, 3.52],
"radius": 6.56,
"object_position": [-0.011, 0.0364, 0.8],
"base_forward": [-0.5194, -0.7477, -0.4138],
"base_up": [-0.2361, -0.3399, 0.9104],
"offsets_deg": {"yaw": -4.6563, "pitch": 13.8792, "roll": 0.0},
"final_forward": [-0.3124, -0.8689, -0.3840],
"final_up": [-0.3020, -0.2924, 0.9074],
"depth": "depth/img_0000.png"
}
]
Later stages may add:
promptdetectionsscore_<key>python scripts/annotate_detections.py \
--annotations_path outputs/DogWalk_v2_10k_260309_101152/annotations.json \
--image_root outputs/DogWalk_v2_10k_260309_101152 \
--caption "a white snowman" \
--device cuda \
--output_path outputs/DogWalk_v2_10k_260309_101152/annotations_detected.json
This appends:
promptdetections: [{"label": ..., "score": ..., "bbox_xyxy": [...]}]python scripts/visualize_detections.py \
--annotations_path outputs/DogWalk_v2_10k_260309_101152/annotations_detected.json \
--image_root outputs/DogWalk_v2_10k_260309_101152 \
--output_dir outputs/DogWalk_v2_10k_260309_101152/detection_viz \
--limit 20
python scripts/score_annotations.py \
--annotations_path outputs/DogWalk_v2_10k_260309_101152/annotations_detected.json \
--image_root outputs/DogWalk_v2_10k_260309_101152 \
--output_path outputs/DogWalk_v2_10k_260309_101152/annotations_scored.json
This computes score_* fields from the primary detected box.
Current rule-based keys are:
bbox_occupancy_ratiobbox_margin_topbbox_margin_bottombbox_margin_leftbbox_margin_rightbbox_aspect_ratiobbox_centroid_offsetNotes:
area * score when detection scores are available.bbox_aspect_ratio are clamped to [0, 1].Two score families are implemented.
This is the default path used by the provided configs. It only requires detections in the annotation file.
Supported subject-aware keys:
subject_dominancerule_of_thirdslead_roomfigure_ground_separationleading_linesavoiding_mergescenter_of_gravityclear_marginsIf you switch data.target_score_keys to subject-aware keys, the dataset expects matching fields such as score_subject_dominance to already exist in the annotation JSON. This repo includes the prompt builder, but does not include a batch subject-aware annotation script.
Training pairs are not stored on disk. DroneActionScoreDataset builds them from the view annotations at load time.
Pair construction rules:
> 0 and <= data.distance_thresholdj must contain detectionsdata.max_pairs_per_image caps sampled neighbors per source imagedata.zero_action_ratio adds self-pairs (i, i) with a zero actiondata.eval_ratio splits the pair list into train and eval subsetsAction text is generated from the relative transform between view i and view j.
The default configs now use data.rotation_representation: orientation_6d, which encodes the target camera orientation with target forward and up vectors instead of an axis-angle rotvec.
Camera-local format:
move_camera_local_m(right=0.2000, up=-0.1000, forward=0.0000); orient_camera_local_6d(fx=0.0000, fy=0.1000, fz=0.9950, ux=0.0000, uy=0.9950, uz=-0.1000)
World-frame format:
move_world_m(x=0.2000, y=-0.1000, z=0.0000); orient_world_6d(fx=0.0000, fy=0.1000, fz=0.9950, ux=0.0000, uy=0.9950, uz=-0.1000)
Legacy option:
data.rotation_representation: rotvec keeps the old rotate_*_axis_angle_rad(rx, ry, rz) format.+right, +up, +forward where +forward means view direction.final_forward and final_up vectors are world-frame unit vectors.+X = right, +Y = up, -Z = forward.data.action_frame can be camera_local or world.data.rotation_representation can be orientation_6d or rotvec.camera_local.Use the consistency checker after rotation-related changes:
python scripts/check_rotation_consistency.py \
--annotations_path outputs/DogWalk_v2_10k_260309_101152/annotations.json \
--max_views 1000 \
--pair_samples 500 \
--strict
python scripts/train.py --config configs/qwen25_vl_7b_full.yaml
Supported config sections:
modeldatatrainingThe provided configs are:
configs/qwen25_vl_7b_full.yamlconfigs/qwen25_vl_7b_2xh200.yamlconfigs/qwen35_vl_2b_1xh200.yamlconfigs/qwen35_vl_2b_4xa100_40g.yamlconfigs/qwen35_vl_9b_1xh200.yamlconfigs/qwen35_vl_9b_2xh200.yamlThe default data settings in those configs currently point to:
outputs/DogWalk_v2_10k_260309_101152/annotations_detected.jsonoutputs/DogWalk_v2_10k_260309_101152Qwen2.5-VL on 2xH200:
bash scripts/train_qwen25_vl_2_h200.sh
Qwen3.5-2B on 1xH200:
bash scripts/train_qwen35_vl_2b_1_h200.sh
Qwen3.5-9B on 2xH200:
bash scripts/train_qwen35_vl_9b_2_h200.sh
Qwen3.5-9B on 1xH200:
bash scripts/train_qwen35_vl_9b_1_h200.sh
Useful overrides:
GPU_ID=4 bash scripts/train_qwen35_vl_2b_1_h200.shGPU_ID=4 bash scripts/train_qwen35_vl_9b_1_h200.shMASTER_PORT=29601 bash scripts/train_qwen25_vl_2_h200.shThe 2-GPU launcher scripts auto-pick a free torchrun master port when MASTER_PORT is unset.
Successful training writes a run directory like:
runs/<timestamp>_<run_name>/
Expected contents:
config.yamlcheckpoints/final/summary.jsonsummary.json includes:
python scripts/eval_qwen25_vl.py \
--config configs/qwen25_vl_7b_full.yaml \
--model_path runs/<run_dir>/final
Outputs:
runs/qwen25_vl_eval_report.json by defaultmaermseparse_failure_rateIf data.eval_ratio > 0, evaluation runs on that split. Otherwise the full dataset is used.
python scripts/predict_qwen25_vl.py \
--model_path runs/<run_dir>/final \
--image_path outputs/DogWalk_v2_10k_260309_101152/images/img_0000.png \
--action_frame camera_local \
--rotation_representation orientation_6d \
--action_text "move_camera_local_m(right=0.2, up=-0.1, forward=0.0); orient_camera_local_6d(fx=0.0, fy=0.1, fz=0.9950, ux=0.0, uy=0.9950, uz=-0.1)"
The script prints:
Once a model is trained, MPC-based planning finds camera actions that optimize toward a target composition objective. Two planning modes are available.
infer_mpc.py generates candidate actions, scores them with the VLM, picks the best action, and snaps to the nearest pre-rendered view.
python scripts/infer_mpc.py \
--config configs/qwen35_vl_2b_1xh200.yaml \
--model_path runs/<run_dir>/final \
--start_index 0 \
--num_steps 8 \
--target_preset centered_50
infer_mpc_blender.py re-renders in Blender at each planning step instead of snapping to existing views. This is slower but produces realistic rollouts.
python scripts/infer_mpc_blender.py \
--run_dir outputs/DogWalk_v2_10k_260309_101152 \
--model_path runs/<run_dir>/final \
--config configs/qwen35_vl_2b_1xh200.yaml \
--blender_bin blender/blender \
--num_steps 16 \
--target_preset centered_50
Pass --evaluate_with_detector to run GroundingDINO on each rendered frame and log actual bbox scores alongside VLM predictions.
Target presets define photographic composition goals using high-level parameters (center_x, center_y, occupancy, aspect_ratio) that are automatically converted into margin-based score targets.
List available presets:
python scripts/infer_mpc.py --list_target_presets
Built-in presets:
| Preset | Description |
|---|---|
centered_50 | 50% occupancy, centered subject |
centered_square_medium | 30% occupancy, 1:1 aspect, centered |
centered_square_close | 55% occupancy, 1:1 aspect, centered |
centered_wide_18 | 1.8:1 wide format, centered |
centered_wide_close_18 | 48% occupancy, 1.8:1 wide, centered |
cinematic_center_wide | 35% occupancy, 1.8:1 wide, centered |
centered_portrait_medium | 30% occupancy, 0.67:1 portrait, centered |
centered_portrait_close | 40% occupancy, 0.67:1 portrait, centered |
top_right_thirds_medium | 25% occupancy, 1:1 aspect, rule-of-thirds placement |
top_right_thirds_portrait_medium | 20% occupancy, 0.67:1 portrait, rule-of-thirds |
Use --target_json to pass a custom target instead of a preset:
python scripts/infer_mpc_blender.py \
--run_dir outputs/DogWalk_v2_10k_260309_101152 \
--model_path runs/<run_dir>/final \
--target_json '{"bbox_occupancy_ratio":0.67,"bbox_margin_top":0.02,"bbox_margin_bottom":0.0,"bbox_margin_left":0.16,"bbox_margin_right":0.16,"bbox_aspect_ratio":0.69}'
Override per-key optimization weights with --score_weights_json:
--score_weights_json '{"bbox_occupancy_ratio":2.0,"bbox_centroid_offset":2.0}'
Default weights assign 2.0 to bbox_occupancy_ratio and bbox_centroid_offset, 1.0 to all others.
Pre-configured shell scripts are provided for common composition goals:
bash scripts/infer_mpc_centered_50.sh
bash scripts/infer_mpc_cinematic_center_wide.sh
bash scripts/infer_mpc_top_right_thirds_medium.sh
bash scripts/infer_mpc_aggressive_closeup_crop.sh
# ... and more under scripts/infer_mpc_*.sh
Each script sets RUN_DIR, MODEL_PATH, score weights, and target JSON. Override environment variables before running:
CANDIDATE_BATCH_SIZE (default: 96)INITIAL_SEED (default: 721)SCORE_WEIGHTS_JSONEach rollout writes to runs/infer_mpc/ (or runs/infer_mpc_blender/):
<timestamp>_mpc_rollout/
├── trajectory.json # full rollout log: steps, candidates, scores, errors
├── frames/ # per-step frame images
└── rollout.mp4 # (or rollout.gif fallback)
trajectory.json includes initial and final target error, per-step top-K candidates with predicted scores, and snap cost for offline MPC.
--num_steps: number of planning steps (default: 8 offline, 16 Blender)--translation_values_m: discrete translation grid (CSV, default: -0.25,0,0.25)--rotation_values_deg: discrete rotation grid (CSV, default: -6,0,6)--max_translation_norm_m: filter out candidates exceeding this norm--max_rotation_norm_deg: filter out candidates exceeding this rotation--translation_penalty_weight: regularization on translation magnitude--rotation_penalty_weight: regularization on rotation magnitude--parse_failure_penalty: penalty for candidates where JSON parsing fails--candidate_batch_size: VLM batch size for scoring candidatesdata.distance_threshold: maximum source-target camera distance used to form a pairdata.max_pairs_per_image: cap on neighbor samples per source imagedata.zero_action_ratio: fraction of no-op self-pairs added to the final datasetdata.target_score_keys: output JSON keys and orderingdata.action_frame: camera_local or worlddata.rotation_representation: orientation_6d or rotvectraining.max_length: token budget for the image-text sequencetraining.deepspeed_config: optional ZeRO-3 configtraining.resume_from_checkpoint: resume path passed to the HF trainerdocs/figures/token_report_qwen25.json contains a tokenization/debug snapshot for one sample.transformers in the training environment.HTML
96.8%
Python
3.1%