ChronoEdit provides an automated editing labeling script to generate high-quality editing instructions from pairs of images (before and after editing). The script uses state-of-the-art vision-language models to analyze image pairs and generate precise editing prompts with Chain-of-Thought (CoT) reasoning.
The CoT reasoning output is the actual prompt used as input to the ChronoEdit model. It contains detailed information about what should be edited and what should be preserved (appearance, pose, style, composition, etc.).
Generate editing labels for an image pair:
python scripts/data_captioning.py \
--input-image ./assets/images/input.jpg \
--output-image ./assets/images/output.jpg \
--output-file ./assets/captions/caption.txt \
--generate-cot| Argument | Type | Default | Description |
|---|---|---|---|
--input-image |
str | ./assets/images/input.jpg |
Path to the input (original) image |
--output-image |
str | ./assets/images/output.jpg |
Path to the output (edited) image |
--output-file |
str | ./assets/captions/caption.txt |
Path to save the generated caption |
--model |
str | Qwen/Qwen3-VL-30B-A3B-Instruct |
Vision-language model to use for generation |
--max-resolution |
int | 1080 |
Maximum resolution for the shortest edge (pixels) |
--generate-cot |
flag | False |
Generate Chain-of-Thought reasoning (the actual model input prompt) |
The script supports three vision-language models:
Qwen/Qwen2.5-VL-7B-Instruct- Smaller, faster model suitable for quick iterationsQwen/Qwen3-VL-30B-A3B-Instruct- Default model, balanced quality and speed
Example with different model:
python scripts/data_captioning.py \
--input-image ./assets/images/input.jpg \
--output-image ./assets/images/output.jpg \
--model Qwen/Qwen2.5-VL-7B-InstructThe script uses a two-stage process:
-
Stage 1 - Caption Generation: Analyzes the image pair and generates a concise editing instruction describing the main changes.
- Example: "Move the knight's shield to his right hand"
-
Stage 2 - CoT Reasoning (with
--generate-cot): Expands the caption into a detailed prompt that specifies:- What should be edited (the changes)
- What must be preserved (appearance, pose, style, composition)
- Motion and spatial details
- Visual style consistency
- Example: "The user wants to move the shield from the left hand to the right hand. The knight should maintain the same defensive posture and stance, with the shield now gripped firmly in the right hand. The armor reflections, proportions, and medieval style should remain consistent..."
The CoT reasoning (Stage 2) is the actual prompt fed into ChronoEdit for training and inference.
When using --generate-cot, the script saves a JSON file containing both outputs:
{
"caption": "Move the knight's shield to his right hand",
"caption_cot": "The user wants to move the shield from the left hand to the right hand. The knight should maintain the same defensive posture and stance, with the shield now gripped firmly in the right hand. The armor reflections, proportions, and medieval style should remain consistent, emphasizing a powerful defensive stance."
}caption: Concise editing instruction (intermediate output)caption_cot: Detailed reasoning prompt (actual model input, 80-100 words)
Without --generate-cot, only a plain text file with the caption is saved.
The CoT reasoning prompt includes:
- Change description: What specific edits should be made
- Preservation details: What must stay unchanged (pose, appearance, clothing, expression, skin tone, age)
- Spatial information: Location and positioning details
- Visual style: Genre consistency (anime, CG, cinematic, etc.)
- Motion context: Natural human motion and interactions when relevant
- Composition: Shot type and camera angle preservation
This comprehensive information helps the editing model understand both what to change and what constraints to respect.
We provide an example dataset to demonstrate the expected format for training ChronoEdit. The dataset is available at:
https://huggingface.co/datasets/nvidia/ChronoEdit-Example-Dataset/tree/main/difix_dataset
The dataset consists of three main components:
difix_dataset/
├── metadata.csv # Main metadata file with prompts and paths
├── videos/ # Video clips directory
│ └── *.mp4 # Individual video clips
└── umt5/ # Pre-computed UMT5 embeddings (optional)
└── *.pkl # Embedding pickle files
The metadata.csv file contains the following columns:
| Column | Description |
|---|---|
key |
Unique identifier for each sample |
video |
Relative path to the video file (e.g., videos/sample_001.mp4) |
prompt |
The CoT reasoning prompt (actual model input) |
umt5 |
Relative path to pre-computed UMT5 embedding file (optional, used for full model finetuning) |
Example entry from metadata.csv:
key,video,prompt,umt5
sample_001,videos/sample_001.mp4,The user wants to remove visible neural rendering artifacts while keeping the core visual content entirely unchanged.,umt5/sample_001.pklNote: The prompt column contains the CoT reasoning text generated by the captioning script (Stage 2 output).
For full model finetuning, ChronoEdit pre-computes UMT5 embeddings from prompts to improve training efficiency. This step is optional but recommended for large-scale training.
Run the extraction script on your prepared metadata CSV:
python scripts/extract_umt5.py --csv_path YOUR_CSV_PATHExample:
python scripts/extract_umt5.py --csv_path ./data/my_dataset/metadata.csv- Reads the metadata CSV - Loads all entries with video paths and prompts
- Computes UMT5 embeddings - Processes each prompt through the UMT5 model
- Saves embeddings - Stores each embedding as a
.pklfile inumt5/directory - Updates CSV - Adds/updates the
umt5column with paths to embedding files
After running the script:
- A
umt5/directory is created in the same location as your CSV - Each prompt gets a corresponding
.pklfile with the UMT5 embedding - The
metadata.csvis updated with theumt5column populated
Example output structure:
data/my_dataset/
├── metadata.csv # Updated with umt5 column
├── videos/
│ └── sample_001.mp4
└── umt5/ # Newly created
└── sample_001.pkl # Pre-computed embedding
- ✅ Use for: Full model finetuning with large datasets
- ✅ Benefit: Significantly speeds up training by avoiding repeated text encoding
- ❌ Skip for: LoRA training or small-scale experiments (embeddings computed on-the-fly)
To prepare a custom dataset for ChronoEdit training:
- Collect image pairs - Organize your input (before) and output (after) images
- Convert image pairs to videos - Create 2-frame videos from each pair
- Generate prompts - Use
data_captioning.pywith--generate-cotto create CoT prompts - Create metadata.csv - Organize data in the format shown above
- (Optional) Extract UMT5 embeddings - Run
extract_umt5.pyfor full model finetuning
ChronoEdit expects video files as input. For image editing tasks, create a 2-frame video where:
- Frame 1: Input/source image (before editing)
- Frame 2: Output/target image (after editing)
Here's how to convert image pairs to videos:
import os
import imageio
# Paths to your images
source_image_path = "path/to/input.jpg"
target_image_path = "path/to/output.jpg"
key = "sample_001" # Unique identifier
# Create video from image pair
output_dir = "./data/my_dataset"
video_path = os.path.join(output_dir, f"videos/{key}.mp4")
os.makedirs(os.path.dirname(video_path), exist_ok=True)
with imageio.get_writer(video_path, fps=1) as writer:
for image_path in [source_image_path, target_image_path]:
image = imageio.imread(image_path)
writer.append_data(image)Key points:
- Use
fps=1(1 frame per second) for 2-frame videos - First frame is the source (input) image
- Second frame is the target (output) image
- Each video should have a unique identifier (
key)
For batch processing multiple image pairs:
import os
import imageio
# List of image pairs: (source_path, target_path, unique_key)
image_pairs = [
("inputs/image_001.jpg", "outputs/image_001.jpg", "sample_001"),
("inputs/image_002.jpg", "outputs/image_002.jpg", "sample_002"),
# ... more pairs
]
output_dir = "./data/my_dataset"
for source_path, target_path, key in image_pairs:
video_path = os.path.join(output_dir, f"videos/{key}.mp4")
os.makedirs(os.path.dirname(video_path), exist_ok=True)
with imageio.get_writer(video_path, fps=1) as writer:
for image_path in [source_path, target_path]:
image = imageio.imread(image_path)
writer.append_data(image)
print(f"Created video: {video_path}")- For training data preparation, always use
--generate-cotto generate the actual model input prompts - Images are automatically resized if their shortest edge exceeds
--max-resolution - If no changes are detected between images, the output will be "no change"
- CoT is only generated when changes are detected (not for "no change" cases)