diff --git a/workspace/logs/enhanced_train_14687215_1.out b/workspace/logs/enhanced_train_14687215_1.out new file mode 100644 index 0000000000000000000000000000000000000000..a56b14fba053606f842b4792a11358092cd5eaad --- /dev/null +++ b/workspace/logs/enhanced_train_14687215_1.out @@ -0,0 +1,34 @@ += = = = = = = = = = = = = = = = = = +DoVLA-Attention-Enhanced: SOTA Training += = = = = = = = = = = = = = = = = = + +Architecture Components: + 1. Hierarchical Attention (local + global) + 2. Graph Neural Network (action relationships) + 3. Contrastive Learning (better embeddings) + 4. Task-Adaptive Layers (multi-task) + +Dataset: 3,500 groups (fair comparison) +Seed: 1 + +Expected: 44-47% success (vs 38.43% baseline) +Improvement: +5.5-8.5% + +====================================================================== +Enhanced DoVLA-Attention Training (CVPR) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Architecture: Hierarchical + Graph + Contrastive + Task-Adaptive +Hidden: 256, Heads: 4, Layers: 3 +Seed: 1 + +Loading dataset... +Total: 3500, Train: 2800, Val: 700 + +Observation dim: 70, Action dim: 32 + +Model parameters: 4,374,401 + +Starting training... + diff --git a/workspace/logs/enhanced_train_14687215_2.err b/workspace/logs/enhanced_train_14687215_2.err new file mode 100644 index 0000000000000000000000000000000000000000..153dd4bb5d19cb13c2cef618c177dfabc944081e --- /dev/null +++ b/workspace/logs/enhanced_train_14687215_2.err @@ -0,0 +1,20 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/train_dovla_enhanced.py", line 407, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_dovla_enhanced.py", line 353, in main + train_metrics = train_epoch(model, train_loader, optimizer, device, args.contrastive_weight) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_dovla_enhanced.py", line 170, in train_epoch + scores, contrastive_loss = model(obs, actions, task_ids, rewards) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1778, in _wrapped_call_impl + return self._call_impl(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1789, in _call_impl + return forward_call(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/models/dovla_attention_enhanced.py", line 452, in forward + cos_sim = F.cosine_similarity(h_i, h_j, dim=-1, keepdim=True) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: cosine_similarity() got an unexpected keyword argument 'keepdim' diff --git a/workspace/logs/enhanced_train_14687215_2.out b/workspace/logs/enhanced_train_14687215_2.out new file mode 100644 index 0000000000000000000000000000000000000000..66433983cf0286f903e05867e07985adf8781acd --- /dev/null +++ b/workspace/logs/enhanced_train_14687215_2.out @@ -0,0 +1,34 @@ += = = = = = = = = = = = = = = = = = +DoVLA-Attention-Enhanced: SOTA Training += = = = = = = = = = = = = = = = = = + +Architecture Components: + 1. Hierarchical Attention (local + global) + 2. Graph Neural Network (action relationships) + 3. Contrastive Learning (better embeddings) + 4. Task-Adaptive Layers (multi-task) + +Dataset: 3,500 groups (fair comparison) +Seed: 2 + +Expected: 44-47% success (vs 38.43% baseline) +Improvement: +5.5-8.5% + +====================================================================== +Enhanced DoVLA-Attention Training (CVPR) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Architecture: Hierarchical + Graph + Contrastive + Task-Adaptive +Hidden: 256, Heads: 4, Layers: 3 +Seed: 2 + +Loading dataset... +Total: 3500, Train: 2800, Val: 700 + +Observation dim: 70, Action dim: 32 + +Model parameters: 4,374,401 + +Starting training... + diff --git a/workspace/logs/enhanced_train_14687360_0.err b/workspace/logs/enhanced_train_14687360_0.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/enhanced_train_14687360_0.out b/workspace/logs/enhanced_train_14687360_0.out new file mode 100644 index 0000000000000000000000000000000000000000..549d842b1a6d174609f970e98617a7afb6888710 --- /dev/null +++ b/workspace/logs/enhanced_train_14687360_0.out @@ -0,0 +1,90 @@ += = = = = = = = = = = = = = = = = = +DoVLA-Attention-Enhanced: SOTA Training += = = = = = = = = = = = = = = = = = + +Architecture Components: + 1. Hierarchical Attention (local + global) + 2. Graph Neural Network (action relationships) + 3. Contrastive Learning (better embeddings) + 4. Task-Adaptive Layers (multi-task) + +Dataset: 3,500 groups (fair comparison) +Seed: 0 + +Expected: 44-47% success (vs 38.43% baseline) +Improvement: +5.5-8.5% + +====================================================================== +Enhanced DoVLA-Attention Training (CVPR) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Architecture: Hierarchical + Graph + Contrastive + Task-Adaptive +Hidden: 256, Heads: 4, Layers: 3 +Seed: 0 + +Loading dataset... +Total: 3500, Train: 2800, Val: 700 + +Observation dim: 70, Action dim: 32 + +Model parameters: 4,374,401 + +Starting training... + +Epoch 1/50: rank_loss=0.6980, contr_loss=1.2153, val_acc=0.5000 +Epoch 2/50: rank_loss=0.6943, contr_loss=0.9573, val_acc=0.5000 +Epoch 3/50: rank_loss=0.6940, contr_loss=0.9303, val_acc=0.5000 +Epoch 4/50: rank_loss=0.6937, contr_loss=0.8816, val_acc=0.5000 +Epoch 5/50: rank_loss=0.6935, contr_loss=0.8862, val_acc=0.5000 +Epoch 6/50: rank_loss=0.6935, contr_loss=0.8504, val_acc=0.5000 +Epoch 7/50: rank_loss=0.6934, contr_loss=0.7998, val_acc=0.5000 +Epoch 8/50: rank_loss=0.6934, contr_loss=0.8008, val_acc=0.5000 +Epoch 9/50: rank_loss=0.6933, contr_loss=0.8075, val_acc=0.5000 +Epoch 10/50: rank_loss=0.6933, contr_loss=0.8081, val_acc=0.5000 +Epoch 11/50: rank_loss=0.6933, contr_loss=0.8006, val_acc=0.5000 +Epoch 12/50: rank_loss=0.6933, contr_loss=0.8085, val_acc=0.5000 +Epoch 13/50: rank_loss=0.6932, contr_loss=0.7825, val_acc=0.5000 +Epoch 14/50: rank_loss=0.6932, contr_loss=0.8122, val_acc=0.5000 +Epoch 15/50: rank_loss=0.6932, contr_loss=0.8361, val_acc=0.5000 +Epoch 16/50: rank_loss=0.6932, contr_loss=0.8358, val_acc=0.5000 +Epoch 17/50: rank_loss=0.6932, contr_loss=0.8506, val_acc=0.5000 +Epoch 18/50: rank_loss=0.6932, contr_loss=0.8049, val_acc=0.5000 +Epoch 19/50: rank_loss=0.6932, contr_loss=0.7821, val_acc=0.5000 +Epoch 20/50: rank_loss=0.6932, contr_loss=0.8272, val_acc=0.5000 +Epoch 21/50: rank_loss=0.6932, contr_loss=0.7854, val_acc=0.5000 +Epoch 22/50: rank_loss=0.6932, contr_loss=0.7700, val_acc=0.5000 +Epoch 23/50: rank_loss=0.6932, contr_loss=0.7742, val_acc=0.5000 +Epoch 24/50: rank_loss=0.6932, contr_loss=0.7773, val_acc=0.5000 +Epoch 25/50: rank_loss=0.6932, contr_loss=0.7843, val_acc=0.5000 +Epoch 26/50: rank_loss=0.6932, contr_loss=0.7495, val_acc=0.5000 +Epoch 27/50: rank_loss=0.6932, contr_loss=0.7527, val_acc=0.5000 +Epoch 28/50: rank_loss=0.6932, contr_loss=0.7521, val_acc=0.5000 +Epoch 29/50: rank_loss=0.6932, contr_loss=0.7476, val_acc=0.5000 +Epoch 30/50: rank_loss=0.6932, contr_loss=0.7416, val_acc=0.5000 +Epoch 31/50: rank_loss=0.6932, contr_loss=0.7506, val_acc=0.5000 +Epoch 32/50: rank_loss=0.6932, contr_loss=0.7348, val_acc=0.5000 +Epoch 33/50: rank_loss=0.6932, contr_loss=0.7364, val_acc=0.5000 +Epoch 34/50: rank_loss=0.6932, contr_loss=0.7411, val_acc=0.5000 +Epoch 35/50: rank_loss=0.6932, contr_loss=0.7443, val_acc=0.5000 +Epoch 36/50: rank_loss=0.6932, contr_loss=0.7310, val_acc=0.5000 +Epoch 37/50: rank_loss=0.6931, contr_loss=0.7176, val_acc=0.5000 +Epoch 38/50: rank_loss=0.6932, contr_loss=0.7172, val_acc=0.5000 +Epoch 39/50: rank_loss=0.6932, contr_loss=0.7241, val_acc=0.5000 +Epoch 40/50: rank_loss=0.6932, contr_loss=0.7246, val_acc=0.5000 +Epoch 41/50: rank_loss=0.6932, contr_loss=0.7208, val_acc=0.5000 +Epoch 42/50: rank_loss=0.6932, contr_loss=0.7213, val_acc=0.5000 +Epoch 43/50: rank_loss=0.6932, contr_loss=0.7230, val_acc=0.5000 +Epoch 44/50: rank_loss=0.6932, contr_loss=0.7242, val_acc=0.5000 +Epoch 45/50: rank_loss=0.6932, contr_loss=0.7251, val_acc=0.5000 +Epoch 46/50: rank_loss=0.6932, contr_loss=0.7129, val_acc=0.5000 +Epoch 47/50: rank_loss=0.6932, contr_loss=0.7098, val_acc=0.5000 +Epoch 48/50: rank_loss=0.6932, contr_loss=0.7206, val_acc=0.5000 +Epoch 49/50: rank_loss=0.6932, contr_loss=0.7259, val_acc=0.5000 +Epoch 50/50: rank_loss=0.6932, contr_loss=0.7190, val_acc=0.5000 + +✅ Training complete! Best val accuracy: 0.5000 + +✅ Enhanced training complete (seed 0) + +Next: Evaluate and compare with baseline diff --git a/workspace/logs/enhanced_train_14687360_1.err b/workspace/logs/enhanced_train_14687360_1.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/enhanced_train_14687360_1.out b/workspace/logs/enhanced_train_14687360_1.out new file mode 100644 index 0000000000000000000000000000000000000000..c51906bc4643caba5330c74dee1563cb5f6f0975 --- /dev/null +++ b/workspace/logs/enhanced_train_14687360_1.out @@ -0,0 +1,90 @@ += = = = = = = = = = = = = = = = = = +DoVLA-Attention-Enhanced: SOTA Training += = = = = = = = = = = = = = = = = = + +Architecture Components: + 1. Hierarchical Attention (local + global) + 2. Graph Neural Network (action relationships) + 3. Contrastive Learning (better embeddings) + 4. Task-Adaptive Layers (multi-task) + +Dataset: 3,500 groups (fair comparison) +Seed: 1 + +Expected: 44-47% success (vs 38.43% baseline) +Improvement: +5.5-8.5% + +====================================================================== +Enhanced DoVLA-Attention Training (CVPR) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Architecture: Hierarchical + Graph + Contrastive + Task-Adaptive +Hidden: 256, Heads: 4, Layers: 3 +Seed: 1 + +Loading dataset... +Total: 3500, Train: 2800, Val: 700 + +Observation dim: 70, Action dim: 32 + +Model parameters: 4,374,401 + +Starting training... + +Epoch 1/50: rank_loss=0.7001, contr_loss=1.1553, val_acc=0.5000 +Epoch 2/50: rank_loss=0.6943, contr_loss=0.9068, val_acc=0.5000 +Epoch 3/50: rank_loss=0.6938, contr_loss=0.8833, val_acc=0.5000 +Epoch 4/50: rank_loss=0.6936, contr_loss=0.8913, val_acc=0.5000 +Epoch 5/50: rank_loss=0.6935, contr_loss=0.8622, val_acc=0.5000 +Epoch 6/50: rank_loss=0.6934, contr_loss=0.8331, val_acc=0.5000 +Epoch 7/50: rank_loss=0.6934, contr_loss=0.8409, val_acc=0.5000 +Epoch 8/50: rank_loss=0.6933, contr_loss=0.8098, val_acc=0.5000 +Epoch 9/50: rank_loss=0.6933, contr_loss=0.7961, val_acc=0.5000 +Epoch 10/50: rank_loss=0.6933, contr_loss=0.8179, val_acc=0.5000 +Epoch 11/50: rank_loss=0.6932, contr_loss=0.8008, val_acc=0.5000 +Epoch 12/50: rank_loss=0.6932, contr_loss=0.7937, val_acc=0.5000 +Epoch 13/50: rank_loss=0.6932, contr_loss=0.8018, val_acc=0.5000 +Epoch 14/50: rank_loss=0.6932, contr_loss=0.8028, val_acc=0.5000 +Epoch 15/50: rank_loss=0.6932, contr_loss=0.7858, val_acc=0.5000 +Epoch 16/50: rank_loss=0.6932, contr_loss=0.7927, val_acc=0.5000 +Epoch 17/50: rank_loss=0.6932, contr_loss=0.7666, val_acc=0.5000 +Epoch 18/50: rank_loss=0.6932, contr_loss=0.7621, val_acc=0.5000 +Epoch 19/50: rank_loss=0.6932, contr_loss=0.7803, val_acc=0.5000 +Epoch 20/50: rank_loss=0.6932, contr_loss=0.7816, val_acc=0.5000 +Epoch 21/50: rank_loss=0.6932, contr_loss=0.7781, val_acc=0.5000 +Epoch 22/50: rank_loss=0.6932, contr_loss=0.7827, val_acc=0.5000 +Epoch 23/50: rank_loss=0.6932, contr_loss=0.7614, val_acc=0.5000 +Epoch 24/50: rank_loss=0.6932, contr_loss=0.7608, val_acc=0.5000 +Epoch 25/50: rank_loss=0.6932, contr_loss=0.7596, val_acc=0.5000 +Epoch 26/50: rank_loss=0.6932, contr_loss=0.7664, val_acc=0.5000 +Epoch 27/50: rank_loss=0.6932, contr_loss=0.7411, val_acc=0.5000 +Epoch 28/50: rank_loss=0.6932, contr_loss=0.7323, val_acc=0.5000 +Epoch 29/50: rank_loss=0.6932, contr_loss=0.7416, val_acc=0.5000 +Epoch 30/50: rank_loss=0.6932, contr_loss=0.7378, val_acc=0.5000 +Epoch 31/50: rank_loss=0.6932, contr_loss=0.7480, val_acc=0.5000 +Epoch 32/50: rank_loss=0.6932, contr_loss=0.7435, val_acc=0.5000 +Epoch 33/50: rank_loss=0.6932, contr_loss=0.7372, val_acc=0.5000 +Epoch 34/50: rank_loss=0.6932, contr_loss=0.7290, val_acc=0.5000 +Epoch 35/50: rank_loss=0.6932, contr_loss=0.7248, val_acc=0.5000 +Epoch 36/50: rank_loss=0.6932, contr_loss=0.7199, val_acc=0.5000 +Epoch 37/50: rank_loss=0.6932, contr_loss=0.7109, val_acc=0.5000 +Epoch 38/50: rank_loss=0.6932, contr_loss=0.7272, val_acc=0.5000 +Epoch 39/50: rank_loss=0.6932, contr_loss=0.7206, val_acc=0.5000 +Epoch 40/50: rank_loss=0.6931, contr_loss=0.7030, val_acc=0.5000 +Epoch 41/50: rank_loss=0.6932, contr_loss=0.7069, val_acc=0.5000 +Epoch 42/50: rank_loss=0.6932, contr_loss=0.7082, val_acc=0.5000 +Epoch 43/50: rank_loss=0.6932, contr_loss=0.7044, val_acc=0.5000 +Epoch 44/50: rank_loss=0.6932, contr_loss=0.6986, val_acc=0.5000 +Epoch 45/50: rank_loss=0.6932, contr_loss=0.7028, val_acc=0.5000 +Epoch 46/50: rank_loss=0.6932, contr_loss=0.6937, val_acc=0.5000 +Epoch 47/50: rank_loss=0.6932, contr_loss=0.7020, val_acc=0.5000 +Epoch 48/50: rank_loss=0.6932, contr_loss=0.7054, val_acc=0.5000 +Epoch 49/50: rank_loss=0.6932, contr_loss=0.7040, val_acc=0.5000 +Epoch 50/50: rank_loss=0.6931, contr_loss=0.6985, val_acc=0.5000 + +✅ Training complete! Best val accuracy: 0.5000 + +✅ Enhanced training complete (seed 1) + +Next: Evaluate and compare with baseline diff --git a/workspace/logs/enhanced_train_14687360_2.err b/workspace/logs/enhanced_train_14687360_2.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/enhanced_train_14687360_2.out b/workspace/logs/enhanced_train_14687360_2.out new file mode 100644 index 0000000000000000000000000000000000000000..e82aae455b1cb0283cba9c60b5e0b19747db7fa5 --- /dev/null +++ b/workspace/logs/enhanced_train_14687360_2.out @@ -0,0 +1,90 @@ += = = = = = = = = = = = = = = = = = +DoVLA-Attention-Enhanced: SOTA Training += = = = = = = = = = = = = = = = = = + +Architecture Components: + 1. Hierarchical Attention (local + global) + 2. Graph Neural Network (action relationships) + 3. Contrastive Learning (better embeddings) + 4. Task-Adaptive Layers (multi-task) + +Dataset: 3,500 groups (fair comparison) +Seed: 2 + +Expected: 44-47% success (vs 38.43% baseline) +Improvement: +5.5-8.5% + +====================================================================== +Enhanced DoVLA-Attention Training (CVPR) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Architecture: Hierarchical + Graph + Contrastive + Task-Adaptive +Hidden: 256, Heads: 4, Layers: 3 +Seed: 2 + +Loading dataset... +Total: 3500, Train: 2800, Val: 700 + +Observation dim: 70, Action dim: 32 + +Model parameters: 4,374,401 + +Starting training... + +Epoch 1/50: rank_loss=0.6992, contr_loss=1.1632, val_acc=0.5000 +Epoch 2/50: rank_loss=0.6944, contr_loss=0.9737, val_acc=0.5000 +Epoch 3/50: rank_loss=0.6939, contr_loss=0.8806, val_acc=0.5000 +Epoch 4/50: rank_loss=0.6936, contr_loss=0.8334, val_acc=0.5000 +Epoch 5/50: rank_loss=0.6935, contr_loss=0.8105, val_acc=0.5000 +Epoch 6/50: rank_loss=0.6934, contr_loss=0.7987, val_acc=0.5000 +Epoch 7/50: rank_loss=0.6933, contr_loss=0.8100, val_acc=0.5000 +Epoch 8/50: rank_loss=0.6933, contr_loss=0.8002, val_acc=0.5000 +Epoch 9/50: rank_loss=0.6933, contr_loss=0.7978, val_acc=0.5000 +Epoch 10/50: rank_loss=0.6933, contr_loss=0.7791, val_acc=0.5000 +Epoch 11/50: rank_loss=0.6933, contr_loss=0.7786, val_acc=0.5000 +Epoch 12/50: rank_loss=0.6932, contr_loss=0.7700, val_acc=0.5000 +Epoch 13/50: rank_loss=0.6932, contr_loss=0.7748, val_acc=0.5000 +Epoch 14/50: rank_loss=0.6932, contr_loss=0.7871, val_acc=0.5000 +Epoch 15/50: rank_loss=0.6932, contr_loss=0.7829, val_acc=0.5000 +Epoch 16/50: rank_loss=0.6932, contr_loss=0.7693, val_acc=0.5000 +Epoch 17/50: rank_loss=0.6932, contr_loss=0.7735, val_acc=0.5000 +Epoch 18/50: rank_loss=0.6932, contr_loss=0.7906, val_acc=0.5000 +Epoch 19/50: rank_loss=0.6932, contr_loss=0.7690, val_acc=0.5000 +Epoch 20/50: rank_loss=0.6932, contr_loss=0.7729, val_acc=0.5000 +Epoch 21/50: rank_loss=0.6932, contr_loss=0.7561, val_acc=0.5000 +Epoch 22/50: rank_loss=0.6932, contr_loss=0.7520, val_acc=0.5000 +Epoch 23/50: rank_loss=0.6932, contr_loss=0.7322, val_acc=0.5000 +Epoch 24/50: rank_loss=0.6932, contr_loss=0.7429, val_acc=0.5000 +Epoch 25/50: rank_loss=0.6932, contr_loss=0.7537, val_acc=0.5000 +Epoch 26/50: rank_loss=0.6932, contr_loss=0.7484, val_acc=0.5000 +Epoch 27/50: rank_loss=0.6932, contr_loss=0.7296, val_acc=0.5000 +Epoch 28/50: rank_loss=0.6932, contr_loss=0.7449, val_acc=0.5000 +Epoch 29/50: rank_loss=0.6932, contr_loss=0.7376, val_acc=0.5000 +Epoch 30/50: rank_loss=0.6932, contr_loss=0.7225, val_acc=0.5000 +Epoch 31/50: rank_loss=0.6932, contr_loss=0.7342, val_acc=0.5000 +Epoch 32/50: rank_loss=0.6932, contr_loss=0.7216, val_acc=0.5000 +Epoch 33/50: rank_loss=0.6932, contr_loss=0.7252, val_acc=0.5000 +Epoch 34/50: rank_loss=0.6932, contr_loss=0.7211, val_acc=0.5000 +Epoch 35/50: rank_loss=0.6932, contr_loss=0.7251, val_acc=0.5000 +Epoch 36/50: rank_loss=0.6932, contr_loss=0.7277, val_acc=0.5000 +Epoch 37/50: rank_loss=0.6932, contr_loss=0.7172, val_acc=0.5000 +Epoch 38/50: rank_loss=0.6932, contr_loss=0.7202, val_acc=0.5000 +Epoch 39/50: rank_loss=0.6932, contr_loss=0.7221, val_acc=0.5000 +Epoch 40/50: rank_loss=0.6932, contr_loss=0.7178, val_acc=0.5000 +Epoch 41/50: rank_loss=0.6932, contr_loss=0.7090, val_acc=0.5000 +Epoch 42/50: rank_loss=0.6931, contr_loss=0.7153, val_acc=0.5000 +Epoch 43/50: rank_loss=0.6932, contr_loss=0.7104, val_acc=0.5000 +Epoch 44/50: rank_loss=0.6932, contr_loss=0.7146, val_acc=0.5000 +Epoch 45/50: rank_loss=0.6932, contr_loss=0.7022, val_acc=0.5000 +Epoch 46/50: rank_loss=0.6932, contr_loss=0.7082, val_acc=0.5000 +Epoch 47/50: rank_loss=0.6932, contr_loss=0.7097, val_acc=0.5000 +Epoch 48/50: rank_loss=0.6932, contr_loss=0.7035, val_acc=0.5000 +Epoch 49/50: rank_loss=0.6931, contr_loss=0.7078, val_acc=0.5000 +Epoch 50/50: rank_loss=0.6931, contr_loss=0.7112, val_acc=0.5000 + +✅ Training complete! Best val accuracy: 0.5000 + +✅ Enhanced training complete (seed 2) + +Next: Evaluate and compare with baseline diff --git a/workspace/logs/eval_a1_revised_14664453.err b/workspace/logs/eval_a1_revised_14664453.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_a1_revised_14664453.out b/workspace/logs/eval_a1_revised_14664453.out new file mode 100644 index 0000000000000000000000000000000000000000..582ee858ffc139723d9cb2ca9f4fa232e96bbe2b --- /dev/null +++ b/workspace/logs/eval_a1_revised_14664453.out @@ -0,0 +1,484 @@ +=== Evaluating Phase A1-Revised Enhanced Models === + +Evaluating seed 0... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a1_revised_enhanced/seed_0/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8507781576673159, + "top1_action_selection": 0.6114285714285714, + "selected_success_rate": 0.37857142857142856, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9746324906196052, + "potential_edge_mae": 0.29218482069354706, + "effect_prediction_mae": 0.025904718118875455, + "selection_regret": 0.07466521533977773, + "selected_candidate_type_counts": { + "expert": 1862, + "near_miss": 611, + "no_op": 419, + "random_negative": 30, + "wrong_direction": 205, + "wrong_gripper": 373 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8372760104370064, + "top1_action_selection": 0.572, + "selected_success_rate": 0.404, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9673779491057297, + "potential_edge_mae": 0.3259476305709723, + "effect_prediction_mae": 0.02046124974363364, + "selection_regret": 0.1150644493997097, + "selected_candidate_type_counts": { + "expert": 351, + "near_miss": 81, + "no_op": 3, + "random_negative": 2, + "wrong_direction": 20, + "wrong_gripper": 43 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8198839574510653, + "top1_action_selection": 0.682, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9737146401786494, + "potential_edge_mae": 0.17428899767333667, + "effect_prediction_mae": 0.04585162171532977, + "selection_regret": 0.03477694494090974, + "selected_candidate_type_counts": { + "expert": 284, + "near_miss": 14, + "wrong_direction": 7, + "wrong_gripper": 195 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8755635632280231, + "top1_action_selection": 0.524, + "selected_success_rate": 0.318, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.975776432575271, + "potential_edge_mae": 0.2910050306161701, + "effect_prediction_mae": 0.020499905351573436, + "selection_regret": 0.09195959524065256, + "selected_candidate_type_counts": { + "expert": 400, + "near_miss": 163, + "no_op": 408, + "wrong_direction": 27, + "wrong_gripper": 2 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8435432413335332, + "top1_action_selection": 0.698, + "selected_success_rate": 0.608, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9813948972377147, + "potential_edge_mae": 0.27489662176823304, + "effect_prediction_mae": 0.019396800087932185, + "selection_regret": 0.045078758046030995, + "selected_candidate_type_counts": { + "expert": 259, + "near_miss": 135, + "random_negative": 12, + "wrong_direction": 75, + "wrong_gripper": 19 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8616394420824942, + "top1_action_selection": 0.718, + "selected_success_rate": 0.652, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9781340173951859, + "potential_edge_mae": 0.38579861025676476, + "effect_prediction_mae": 0.022150712490473203, + "selection_regret": 0.05170745313167572, + "selected_candidate_type_counts": { + "expert": 264, + "near_miss": 83, + "no_op": 3, + "random_negative": 16, + "wrong_direction": 44, + "wrong_gripper": 90 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8412163868729899, + "top1_action_selection": 0.562, + "selected_success_rate": 0.34, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9702530652693935, + "potential_edge_mae": 0.30978307795222954, + "effect_prediction_mae": 0.032472832091612794, + "selection_regret": 0.09210971137881278, + "selected_candidate_type_counts": { + "expert": 304, + "near_miss": 135, + "no_op": 5, + "wrong_direction": 32, + "wrong_gripper": 24 + } + } + } +} +✅ Seed 0 complete + Success: 0.3786 | Top1: 0.6114 | Rank: 0.8508 + +Evaluating seed 1... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a1_revised_enhanced/seed_1/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 1, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8470038977170514, + "top1_action_selection": 0.596, + "selected_success_rate": 0.37657142857142856, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9720251036674814, + "potential_edge_mae": 0.2730792666363643, + "effect_prediction_mae": 0.02844222002923243, + "selection_regret": 0.07795452343299986, + "selected_candidate_type_counts": { + "expert": 1979, + "near_miss": 616, + "no_op": 377, + "random_negative": 101, + "wrong_direction": 139, + "wrong_gripper": 288 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8315736724784434, + "top1_action_selection": 0.554, + "selected_success_rate": 0.4, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9650526053407107, + "potential_edge_mae": 0.28923494204159383, + "effect_prediction_mae": 0.02350660307392276, + "selection_regret": 0.11971647167205811, + "selected_candidate_type_counts": { + "expert": 288, + "near_miss": 151, + "random_negative": 4, + "wrong_direction": 13, + "wrong_gripper": 44 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8188669178698856, + "top1_action_selection": 0.684, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9759227468818652, + "potential_edge_mae": 0.1475369496848387, + "effect_prediction_mae": 0.04776298720370394, + "selection_regret": 0.032691791389137505, + "selected_candidate_type_counts": { + "expert": 295, + "near_miss": 8, + "wrong_direction": 7, + "wrong_gripper": 190 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8706779518980977, + "top1_action_selection": 0.498, + "selected_success_rate": 0.316, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9707502646546345, + "potential_edge_mae": 0.2958169730574824, + "effect_prediction_mae": 0.02143634675771565, + "selection_regret": 0.09696282734721899, + "selected_candidate_type_counts": { + "expert": 410, + "near_miss": 179, + "no_op": 362, + "wrong_direction": 27, + "wrong_gripper": 22 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8472046763591204, + "top1_action_selection": 0.706, + "selected_success_rate": 0.612, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9831143135233674, + "potential_edge_mae": 0.20503641737197578, + "effect_prediction_mae": 0.02327047512314227, + "selection_regret": 0.03612557476013899, + "selected_candidate_type_counts": { + "expert": 277, + "near_miss": 88, + "no_op": 6, + "random_negative": 90, + "wrong_direction": 36, + "wrong_gripper": 3 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.862627731781905, + "top1_action_selection": 0.688, + "selected_success_rate": 0.65, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9744540449221311, + "potential_edge_mae": 0.3605412413564324, + "effect_prediction_mae": 0.02591101919445702, + "selection_regret": 0.05477676001191139, + "selected_candidate_type_counts": { + "expert": 385, + "near_miss": 65, + "no_op": 7, + "random_negative": 7, + "wrong_direction": 32, + "wrong_gripper": 4 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8286214618868815, + "top1_action_selection": 0.544, + "selected_success_rate": 0.332, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9641314856950247, + "potential_edge_mae": 0.3129677290859356, + "effect_prediction_mae": 0.0357717620939687, + "selection_regret": 0.10844541150331498, + "selected_candidate_type_counts": { + "expert": 324, + "near_miss": 125, + "no_op": 2, + "wrong_direction": 24, + "wrong_gripper": 25 + } + } + } +} +✅ Seed 1 complete + Success: 0.3766 | Top1: 0.5960 | Rank: 0.8470 + +Evaluating seed 2... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a1_revised_enhanced/seed_2/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 2, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8469988586516973, + "top1_action_selection": 0.5908571428571429, + "selected_success_rate": 0.37514285714285717, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9715571386731926, + "potential_edge_mae": 0.2852598237931919, + "effect_prediction_mae": 0.027762871967903367, + "selection_regret": 0.07962946100586227, + "selected_candidate_type_counts": { + "expert": 2035, + "near_miss": 469, + "no_op": 445, + "random_negative": 46, + "wrong_direction": 107, + "wrong_gripper": 398 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8251455824160633, + "top1_action_selection": 0.52, + "selected_success_rate": 0.406, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9645054386640626, + "potential_edge_mae": 0.3259366197214057, + "effect_prediction_mae": 0.023472823263874003, + "selection_regret": 0.113965540766716, + "selected_candidate_type_counts": { + "expert": 326, + "near_miss": 88, + "no_op": 3, + "random_negative": 3, + "wrong_direction": 4, + "wrong_gripper": 76 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8153489612858048, + "top1_action_selection": 0.692, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.974927145263329, + "potential_edge_mae": 0.17851244468993016, + "effect_prediction_mae": 0.04580902023741111, + "selection_regret": 0.03166495469585061, + "selected_candidate_type_counts": { + "expert": 287, + "near_miss": 18, + "wrong_direction": 6, + "wrong_gripper": 189 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8718176485376686, + "top1_action_selection": 0.5, + "selected_success_rate": 0.314, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9714412659412455, + "potential_edge_mae": 0.29148840261455466, + "effect_prediction_mae": 0.020944095825184347, + "selection_regret": 0.09970813875645398, + "selected_candidate_type_counts": { + "expert": 469, + "near_miss": 138, + "no_op": 363, + "wrong_direction": 24, + "wrong_gripper": 6 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8521722373295078, + "top1_action_selection": 0.706, + "selected_success_rate": 0.61, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9807441941789684, + "potential_edge_mae": 0.22187108794733792, + "effect_prediction_mae": 0.022020053274346, + "selection_regret": 0.03959757074713707, + "selected_candidate_type_counts": { + "expert": 271, + "near_miss": 52, + "no_op": 70, + "random_negative": 40, + "wrong_direction": 22, + "wrong_gripper": 45 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8634668456776311, + "top1_action_selection": 0.686, + "selected_success_rate": 0.65, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.974837883365155, + "potential_edge_mae": 0.37560185345073566, + "effect_prediction_mae": 0.02527257962382685, + "selection_regret": 0.05547860264778137, + "selected_candidate_type_counts": { + "expert": 346, + "near_miss": 55, + "no_op": 3, + "random_negative": 3, + "wrong_direction": 36, + "wrong_gripper": 57 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8314502685682533, + "top1_action_selection": 0.532, + "selected_success_rate": 0.322, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9630027773583476, + "potential_edge_mae": 0.3091836632192614, + "effect_prediction_mae": 0.03587743572549566, + "selection_regret": 0.11728328067064285, + "selected_candidate_type_counts": { + "expert": 336, + "near_miss": 118, + "no_op": 6, + "wrong_direction": 15, + "wrong_gripper": 25 + } + } + } +} +✅ Seed 2 complete + Success: 0.3751 | Top1: 0.5909 | Rank: 0.8470 + +✅ All Phase A1-Revised evaluations complete! diff --git a/workspace/logs/eval_enhanced_14706209_0.err b/workspace/logs/eval_enhanced_14706209_0.err new file mode 100644 index 0000000000000000000000000000000000000000..b7936ebcdddfacbf832d477d2ba9c63a1e7461fa --- /dev/null +++ b/workspace/logs/eval_enhanced_14706209_0.err @@ -0,0 +1,22 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_enhanced_checkpoint.py", line 173, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_enhanced_checkpoint.py", line 161, in main + result = evaluate_enhanced_checkpoint( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_enhanced_checkpoint.py", line 147, in evaluate_enhanced_checkpoint + write_json(output_path, result) + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/utils/io.py", line 23, in write_json + target = Path(path) + ^^^^^^^^^^ + File "/cvmfs/soft.computecanada.ca/gentoo/2023/x86-64-v3/usr/lib/python3.11/pathlib.py", line 871, in __new__ + self = cls._from_parts(args) + ^^^^^^^^^^^^^^^^^^^^^ + File "/cvmfs/soft.computecanada.ca/gentoo/2023/x86-64-v3/usr/lib/python3.11/pathlib.py", line 509, in _from_parts + drv, root, parts = self._parse_args(args) + ^^^^^^^^^^^^^^^^^^^^^^ + File "/cvmfs/soft.computecanada.ca/gentoo/2023/x86-64-v3/usr/lib/python3.11/pathlib.py", line 493, in _parse_args + a = os.fspath(a) + ^^^^^^^^^^^^ +TypeError: expected str, bytes or os.PathLike object, not dict diff --git a/workspace/logs/eval_enhanced_14706209_0.out b/workspace/logs/eval_enhanced_14706209_0.out new file mode 100644 index 0000000000000000000000000000000000000000..df99387165ff28529dd98d2c27875e09795cc60d --- /dev/null +++ b/workspace/logs/eval_enhanced_14706209_0.out @@ -0,0 +1,3 @@ +=== Evaluating Enhanced Model Seed 0 === +Checkpoint: /scratch/knguy52/dovla/experiments/cvpr_enhanced_model/seed_0/best.pt + diff --git a/workspace/logs/eval_enhanced_14706209_1.err b/workspace/logs/eval_enhanced_14706209_1.err new file mode 100644 index 0000000000000000000000000000000000000000..b7936ebcdddfacbf832d477d2ba9c63a1e7461fa --- /dev/null +++ b/workspace/logs/eval_enhanced_14706209_1.err @@ -0,0 +1,22 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_enhanced_checkpoint.py", line 173, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_enhanced_checkpoint.py", line 161, in main + result = evaluate_enhanced_checkpoint( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_enhanced_checkpoint.py", line 147, in evaluate_enhanced_checkpoint + write_json(output_path, result) + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/utils/io.py", line 23, in write_json + target = Path(path) + ^^^^^^^^^^ + File "/cvmfs/soft.computecanada.ca/gentoo/2023/x86-64-v3/usr/lib/python3.11/pathlib.py", line 871, in __new__ + self = cls._from_parts(args) + ^^^^^^^^^^^^^^^^^^^^^ + File "/cvmfs/soft.computecanada.ca/gentoo/2023/x86-64-v3/usr/lib/python3.11/pathlib.py", line 509, in _from_parts + drv, root, parts = self._parse_args(args) + ^^^^^^^^^^^^^^^^^^^^^^ + File "/cvmfs/soft.computecanada.ca/gentoo/2023/x86-64-v3/usr/lib/python3.11/pathlib.py", line 493, in _parse_args + a = os.fspath(a) + ^^^^^^^^^^^^ +TypeError: expected str, bytes or os.PathLike object, not dict diff --git a/workspace/logs/eval_enhanced_14706209_1.out b/workspace/logs/eval_enhanced_14706209_1.out new file mode 100644 index 0000000000000000000000000000000000000000..3cca4539cd433a5c349791ea096ce6c9c081c585 --- /dev/null +++ b/workspace/logs/eval_enhanced_14706209_1.out @@ -0,0 +1,3 @@ +=== Evaluating Enhanced Model Seed 1 === +Checkpoint: /scratch/knguy52/dovla/experiments/cvpr_enhanced_model/seed_1/best.pt + diff --git a/workspace/logs/eval_enhanced_14706209_2.err b/workspace/logs/eval_enhanced_14706209_2.err new file mode 100644 index 0000000000000000000000000000000000000000..b7936ebcdddfacbf832d477d2ba9c63a1e7461fa --- /dev/null +++ b/workspace/logs/eval_enhanced_14706209_2.err @@ -0,0 +1,22 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_enhanced_checkpoint.py", line 173, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_enhanced_checkpoint.py", line 161, in main + result = evaluate_enhanced_checkpoint( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_enhanced_checkpoint.py", line 147, in evaluate_enhanced_checkpoint + write_json(output_path, result) + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/utils/io.py", line 23, in write_json + target = Path(path) + ^^^^^^^^^^ + File "/cvmfs/soft.computecanada.ca/gentoo/2023/x86-64-v3/usr/lib/python3.11/pathlib.py", line 871, in __new__ + self = cls._from_parts(args) + ^^^^^^^^^^^^^^^^^^^^^ + File "/cvmfs/soft.computecanada.ca/gentoo/2023/x86-64-v3/usr/lib/python3.11/pathlib.py", line 509, in _from_parts + drv, root, parts = self._parse_args(args) + ^^^^^^^^^^^^^^^^^^^^^^ + File "/cvmfs/soft.computecanada.ca/gentoo/2023/x86-64-v3/usr/lib/python3.11/pathlib.py", line 493, in _parse_args + a = os.fspath(a) + ^^^^^^^^^^^^ +TypeError: expected str, bytes or os.PathLike object, not dict diff --git a/workspace/logs/eval_enhanced_14706209_2.out b/workspace/logs/eval_enhanced_14706209_2.out new file mode 100644 index 0000000000000000000000000000000000000000..0e3090d1a619ce1a9fbeeb79ffa3ff8bec6f21f7 --- /dev/null +++ b/workspace/logs/eval_enhanced_14706209_2.out @@ -0,0 +1,3 @@ +=== Evaluating Enhanced Model Seed 2 === +Checkpoint: /scratch/knguy52/dovla/experiments/cvpr_enhanced_model/seed_2/best.pt + diff --git a/workspace/logs/eval_enhanced_14706804_0.err b/workspace/logs/eval_enhanced_14706804_0.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_enhanced_14706804_0.out b/workspace/logs/eval_enhanced_14706804_0.out new file mode 100644 index 0000000000000000000000000000000000000000..09385ee8a5cd9c78f0b051e4f55aadb6418aa315 --- /dev/null +++ b/workspace/logs/eval_enhanced_14706804_0.out @@ -0,0 +1,10 @@ +=== Evaluating Enhanced Model Seed 0 === +Checkpoint: /scratch/knguy52/dovla/experiments/cvpr_enhanced_model/seed_0/best.pt + +Selected success rate: 0.3631 +Top-1 selection: 0.6291 +Oracle success: 0.4257 + +✅ Evaluation complete for seed 0 +Selected success: 0.3631 +Top-1: 0.6291 diff --git a/workspace/logs/eval_enhanced_14706804_1.err b/workspace/logs/eval_enhanced_14706804_1.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_enhanced_14706804_1.out b/workspace/logs/eval_enhanced_14706804_1.out new file mode 100644 index 0000000000000000000000000000000000000000..a8e80f0c88b64b01772e6da9a6308d0284804979 --- /dev/null +++ b/workspace/logs/eval_enhanced_14706804_1.out @@ -0,0 +1,10 @@ +=== Evaluating Enhanced Model Seed 1 === +Checkpoint: /scratch/knguy52/dovla/experiments/cvpr_enhanced_model/seed_1/best.pt + +Selected success rate: 0.3631 +Top-1 selection: 0.6291 +Oracle success: 0.4257 + +✅ Evaluation complete for seed 1 +Selected success: 0.3631 +Top-1: 0.6291 diff --git a/workspace/logs/eval_enhanced_14706804_2.err b/workspace/logs/eval_enhanced_14706804_2.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_enhanced_14706804_2.out b/workspace/logs/eval_enhanced_14706804_2.out new file mode 100644 index 0000000000000000000000000000000000000000..2dcfccc9dfd6964a3a18b2996d124bceb989da7f --- /dev/null +++ b/workspace/logs/eval_enhanced_14706804_2.out @@ -0,0 +1,10 @@ +=== Evaluating Enhanced Model Seed 2 === +Checkpoint: /scratch/knguy52/dovla/experiments/cvpr_enhanced_model/seed_2/best.pt + +Selected success rate: 0.3631 +Top-1 selection: 0.6291 +Oracle success: 0.4257 + +✅ Evaluation complete for seed 2 +Selected success: 0.3631 +Top-1: 0.6291 diff --git a/workspace/logs/eval_hybrid_14720661_0.err b/workspace/logs/eval_hybrid_14720661_0.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_hybrid_14720661_0.out b/workspace/logs/eval_hybrid_14720661_0.out new file mode 100644 index 0000000000000000000000000000000000000000..c4f998d6df58ab21ee704faf7982152da8f13289 --- /dev/null +++ b/workspace/logs/eval_hybrid_14720661_0.out @@ -0,0 +1,8 @@ +=== Evaluating Hybrid Direct Model === +Seed: 0 + +Selected success rate: 0.3831 +Top-1 selection: 0.5931 +Oracle success: 0.4257 + +✅ Evaluation complete diff --git a/workspace/logs/eval_hybrid_14720661_1.err b/workspace/logs/eval_hybrid_14720661_1.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_hybrid_14720661_1.out b/workspace/logs/eval_hybrid_14720661_1.out new file mode 100644 index 0000000000000000000000000000000000000000..ccf5fc013743317537b5f548e25d582331281655 --- /dev/null +++ b/workspace/logs/eval_hybrid_14720661_1.out @@ -0,0 +1,8 @@ +=== Evaluating Hybrid Direct Model === +Seed: 1 + +Selected success rate: 0.3737 +Top-1 selection: 0.6131 +Oracle success: 0.4257 + +✅ Evaluation complete diff --git a/workspace/logs/eval_hybrid_14720661_2.err b/workspace/logs/eval_hybrid_14720661_2.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_hybrid_14720661_2.out b/workspace/logs/eval_hybrid_14720661_2.out new file mode 100644 index 0000000000000000000000000000000000000000..6b5131df13579a8b69e9c9035c76a3c4ed996d17 --- /dev/null +++ b/workspace/logs/eval_hybrid_14720661_2.out @@ -0,0 +1,8 @@ +=== Evaluating Hybrid Direct Model === +Seed: 2 + +Selected success rate: 0.3663 +Top-1 selection: 0.6006 +Oracle success: 0.4257 + +✅ Evaluation complete diff --git a/workspace/logs/eval_phase_a2_14639576.err b/workspace/logs/eval_phase_a2_14639576.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_phase_a2_14639576.out b/workspace/logs/eval_phase_a2_14639576.out new file mode 100644 index 0000000000000000000000000000000000000000..62fe365a6aa14d619db47ff2addbd2e40c1c68b3 --- /dev/null +++ b/workspace/logs/eval_phase_a2_14639576.out @@ -0,0 +1,486 @@ +=== Evaluating Phase A2 Large Models === + +Evaluating seed 0... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a2_large_model/seed_0/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8455677640911163, + "top1_action_selection": 0.5922857142857143, + "selected_success_rate": 0.3802857142857143, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9734645227544839, + "potential_edge_mae": 0.236036569314635, + "effect_prediction_mae": 0.026717467124909064, + "selection_regret": 0.07209627389215997, + "selected_candidate_type_counts": { + "expert": 1911, + "near_miss": 536, + "no_op": 457, + "random_negative": 102, + "wrong_direction": 132, + "wrong_gripper": 362 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8333189334900036, + "top1_action_selection": 0.516, + "selected_success_rate": 0.406, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.967029355561237, + "potential_edge_mae": 0.2937231201658752, + "effect_prediction_mae": 0.022073940630489883, + "selection_regret": 0.1129412483870983, + "selected_candidate_type_counts": { + "expert": 279, + "near_miss": 146, + "no_op": 1, + "random_negative": 6, + "wrong_direction": 12, + "wrong_gripper": 56 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8061122411550902, + "top1_action_selection": 0.688, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9735705815535792, + "potential_edge_mae": 0.11254235439014912, + "effect_prediction_mae": 0.04541783430588086, + "selection_regret": 0.03372641992941499, + "selected_candidate_type_counts": { + "expert": 311, + "wrong_gripper": 189 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8712058996061343, + "top1_action_selection": 0.513, + "selected_success_rate": 0.33, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9757036176487941, + "potential_edge_mae": 0.25102665239871225, + "effect_prediction_mae": 0.02073188594128252, + "selection_regret": 0.07792561732977629, + "selected_candidate_type_counts": { + "expert": 374, + "near_miss": 135, + "no_op": 445, + "wrong_direction": 45, + "wrong_gripper": 1 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8495171616384386, + "top1_action_selection": 0.704, + "selected_success_rate": 0.614, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.982102532828267, + "potential_edge_mae": 0.18490334437113579, + "effect_prediction_mae": 0.021206451333471026, + "selection_regret": 0.033426615230739115, + "selected_candidate_type_counts": { + "expert": 319, + "near_miss": 28, + "no_op": 4, + "random_negative": 91, + "wrong_direction": 16, + "wrong_gripper": 42 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8625158499291415, + "top1_action_selection": 0.682, + "selected_success_rate": 0.652, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9772097045095216, + "potential_edge_mae": 0.29795804891718364, + "effect_prediction_mae": 0.02397853401356292, + "selection_regret": 0.050853123515844346, + "selected_candidate_type_counts": { + "expert": 314, + "near_miss": 95, + "no_op": 5, + "random_negative": 3, + "wrong_direction": 36, + "wrong_gripper": 47 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8274259543013016, + "top1_action_selection": 0.53, + "selected_success_rate": 0.32, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9629322495312019, + "potential_edge_mae": 0.2587199100076356, + "effect_prediction_mae": 0.03288173770839239, + "selection_regret": 0.11787527552247047, + "selected_candidate_type_counts": { + "expert": 314, + "near_miss": 132, + "no_op": 2, + "random_negative": 2, + "wrong_direction": 23, + "wrong_gripper": 27 + } + } + } +} +✅ Seed 0 complete + Success: 0.3802857142857143 + +Evaluating seed 1... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a2_large_model/seed_1/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 1, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8601457801606958, + "top1_action_selection": 0.6134285714285714, + "selected_success_rate": 0.3797142857142857, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.975449575504389, + "potential_edge_mae": 0.2309461761861737, + "effect_prediction_mae": 0.024799873811406088, + "selection_regret": 0.07129849195879485, + "selected_candidate_type_counts": { + "expert": 2224, + "near_miss": 578, + "no_op": 103, + "random_negative": 118, + "wrong_direction": 187, + "wrong_gripper": 290 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8447754488431166, + "top1_action_selection": 0.574, + "selected_success_rate": 0.406, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9706239617760732, + "potential_edge_mae": 0.2652246011765074, + "effect_prediction_mae": 0.019311041167065165, + "selection_regret": 0.11269975239038467, + "selected_candidate_type_counts": { + "expert": 296, + "near_miss": 120, + "random_negative": 4, + "wrong_direction": 18, + "wrong_gripper": 62 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.823685351295475, + "top1_action_selection": 0.674, + "selected_success_rate": 0.012, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9741373922436591, + "potential_edge_mae": 0.10459398084650508, + "effect_prediction_mae": 0.043773863052136035, + "selection_regret": 0.03218391541205347, + "selected_candidate_type_counts": { + "expert": 272, + "near_miss": 22, + "wrong_direction": 10, + "wrong_gripper": 196 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8811028241012319, + "top1_action_selection": 0.519, + "selected_success_rate": 0.305, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9710455957520074, + "potential_edge_mae": 0.24937264457920053, + "effect_prediction_mae": 0.01913724916576849, + "selection_regret": 0.10442701446264982, + "selected_candidate_type_counts": { + "expert": 640, + "near_miss": 227, + "no_op": 89, + "wrong_direction": 36, + "wrong_gripper": 8 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8572254459028328, + "top1_action_selection": 0.716, + "selected_success_rate": 0.612, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9838359893059918, + "potential_edge_mae": 0.18399476665715153, + "effect_prediction_mae": 0.018029450088052436, + "selection_regret": 0.035458510018885135, + "selected_candidate_type_counts": { + "expert": 345, + "near_miss": 21, + "no_op": 12, + "random_negative": 92, + "wrong_direction": 19, + "wrong_gripper": 11 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8734056835981204, + "top1_action_selection": 0.698, + "selected_success_rate": 0.658, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9804412066611682, + "potential_edge_mae": 0.29355131632093134, + "effect_prediction_mae": 0.021161105101050775, + "selection_regret": 0.04121021604537964, + "selected_candidate_type_counts": { + "expert": 306, + "near_miss": 102, + "no_op": 1, + "random_negative": 16, + "wrong_direction": 72, + "wrong_gripper": 3 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8601592887571773, + "top1_action_selection": 0.594, + "selected_success_rate": 0.36, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9770172870398178, + "potential_edge_mae": 0.2685150524126529, + "effect_prediction_mae": 0.03304915894000212, + "selection_regret": 0.06868302091956138, + "selected_candidate_type_counts": { + "expert": 365, + "near_miss": 86, + "no_op": 1, + "random_negative": 6, + "wrong_direction": 32, + "wrong_gripper": 10 + } + } + } +} +✅ Seed 1 complete + Success: 0.3797142857142857 + +Evaluating seed 2... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a2_large_model/seed_2/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 2, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8434815910344949, + "top1_action_selection": 0.5922857142857143, + "selected_success_rate": 0.376, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9709595629536277, + "potential_edge_mae": 0.242036414944211, + "effect_prediction_mae": 0.027635291527002132, + "selection_regret": 0.07854472978785634, + "selected_candidate_type_counts": { + "expert": 2116, + "near_miss": 494, + "no_op": 378, + "random_negative": 81, + "wrong_direction": 145, + "wrong_gripper": 286 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.828670664063175, + "top1_action_selection": 0.55, + "selected_success_rate": 0.41, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9650907628622645, + "potential_edge_mae": 0.28116600280476817, + "effect_prediction_mae": 0.023020425341441705, + "selection_regret": 0.1072309367954731, + "selected_candidate_type_counts": { + "expert": 368, + "near_miss": 75, + "no_op": 2, + "random_negative": 3, + "wrong_direction": 3, + "wrong_gripper": 49 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8050285104538331, + "top1_action_selection": 0.682, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9726981014121375, + "potential_edge_mae": 0.12772678205132834, + "effect_prediction_mae": 0.046266445637896725, + "selection_regret": 0.03435434534028173, + "selected_candidate_type_counts": { + "expert": 298, + "near_miss": 4, + "wrong_direction": 4, + "wrong_gripper": 194 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8705438699405011, + "top1_action_selection": 0.504, + "selected_success_rate": 0.311, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9710229779636876, + "potential_edge_mae": 0.251372649468657, + "effect_prediction_mae": 0.020782845048889737, + "selection_regret": 0.10136570649594069, + "selected_candidate_type_counts": { + "expert": 466, + "near_miss": 154, + "no_op": 334, + "wrong_direction": 45, + "wrong_gripper": 1 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8482752713958418, + "top1_action_selection": 0.706, + "selected_success_rate": 0.612, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9809909574622121, + "potential_edge_mae": 0.19062138684834176, + "effect_prediction_mae": 0.022138593745818433, + "selection_regret": 0.03628189332038164, + "selected_candidate_type_counts": { + "expert": 272, + "near_miss": 86, + "no_op": 34, + "random_negative": 74, + "wrong_direction": 25, + "wrong_gripper": 9 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8562877601253077, + "top1_action_selection": 0.684, + "selected_success_rate": 0.65, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9738112716460825, + "potential_edge_mae": 0.3244631636030308, + "effect_prediction_mae": 0.02439411786038935, + "selection_regret": 0.055388800501823425, + "selected_candidate_type_counts": { + "expert": 396, + "near_miss": 50, + "no_op": 3, + "random_negative": 3, + "wrong_direction": 47, + "wrong_gripper": 1 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8270386771961138, + "top1_action_selection": 0.516, + "selected_success_rate": 0.328, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9620798913653245, + "potential_edge_mae": 0.26659241516066745, + "effect_prediction_mae": 0.03606176800568822, + "selection_regret": 0.11382571956515312, + "selected_candidate_type_counts": { + "expert": 316, + "near_miss": 125, + "no_op": 5, + "random_negative": 1, + "wrong_direction": 21, + "wrong_gripper": 32 + } + } + } +} +✅ Seed 2 complete + Success: 0.376 + +✅ All Phase A2 evaluations complete! diff --git a/workspace/logs/eval_phase_a4_14647112.err b/workspace/logs/eval_phase_a4_14647112.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_phase_a4_14647112.out b/workspace/logs/eval_phase_a4_14647112.out new file mode 100644 index 0000000000000000000000000000000000000000..427faa4c70ed5cb13ebef8b0547b71516bb224c0 --- /dev/null +++ b/workspace/logs/eval_phase_a4_14647112.out @@ -0,0 +1,1431 @@ +=== Evaluating Phase A4 All Configs (GPU) === +Config dir: /scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection + +Evaluating lr0.0001_h1024... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0001_h1024/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8533052489424262, + "top1_action_selection": 0.6191428571428571, + "selected_success_rate": 0.38057142857142856, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9744854377624829, + "potential_edge_mae": 0.3301121477668643, + "effect_prediction_mae": 0.031717197984592736, + "selection_regret": 0.07116628329775163, + "selected_candidate_type_counts": { + "expert": 1963, + "near_miss": 629, + "no_op": 290, + "random_negative": 49, + "wrong_direction": 188, + "wrong_gripper": 381 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8393323080644882, + "top1_action_selection": 0.554, + "selected_success_rate": 0.412, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9694871715038119, + "potential_edge_mae": 0.37222844718282877, + "effect_prediction_mae": 0.02813377743123782, + "selection_regret": 0.10487956964969634, + "selected_candidate_type_counts": { + "expert": 289, + "near_miss": 116, + "random_negative": 2, + "wrong_direction": 13, + "wrong_gripper": 80 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8229850945346627, + "top1_action_selection": 0.678, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9745385858293178, + "potential_edge_mae": 0.24939746355367523, + "effect_prediction_mae": 0.04762054854080379, + "selection_regret": 0.034055997341871265, + "selected_candidate_type_counts": { + "expert": 283, + "near_miss": 15, + "wrong_direction": 11, + "wrong_gripper": 191 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8773988100226263, + "top1_action_selection": 0.566, + "selected_success_rate": 0.32, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9758736018886763, + "potential_edge_mae": 0.34097748786728616, + "effect_prediction_mae": 0.023769170602733695, + "selection_regret": 0.08316323102265596, + "selected_candidate_type_counts": { + "expert": 542, + "near_miss": 186, + "no_op": 233, + "wrong_direction": 37, + "wrong_gripper": 2 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8501166948590027, + "top1_action_selection": 0.702, + "selected_success_rate": 0.61, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9807683267663477, + "potential_edge_mae": 0.2495405717921957, + "effect_prediction_mae": 0.02615263636906166, + "selection_regret": 0.042707648448646066, + "selected_candidate_type_counts": { + "expert": 308, + "near_miss": 56, + "no_op": 55, + "random_negative": 36, + "wrong_direction": 32, + "wrong_gripper": 13 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8679794137390915, + "top1_action_selection": 0.704, + "selected_success_rate": 0.654, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9776504425422342, + "potential_edge_mae": 0.3834761563197952, + "effect_prediction_mae": 0.030241692979396575, + "selection_regret": 0.048684894561767576, + "selected_candidate_type_counts": { + "expert": 291, + "near_miss": 68, + "no_op": 1, + "random_negative": 11, + "wrong_direction": 45, + "wrong_gripper": 84 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.838387580191618, + "top1_action_selection": 0.564, + "selected_success_rate": 0.338, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9672063339182894, + "potential_edge_mae": 0.36392920469866236, + "effect_prediction_mae": 0.04233338936618127, + "selection_regret": 0.10150941103696823, + "selected_candidate_type_counts": { + "expert": 250, + "near_miss": 188, + "no_op": 1, + "wrong_direction": 50, + "wrong_gripper": 11 + } + } + } +} + ✅ Complete +Evaluating lr0.0001_h256... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0001_h256/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8546305231305698, + "top1_action_selection": 0.6017142857142858, + "selected_success_rate": 0.37942857142857145, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9749137980081676, + "potential_edge_mae": 0.3099070172759672, + "effect_prediction_mae": 0.028634389283933512, + "selection_regret": 0.07513375774105745, + "selected_candidate_type_counts": { + "expert": 1839, + "near_miss": 479, + "no_op": 525, + "random_negative": 128, + "wrong_direction": 115, + "wrong_gripper": 414 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8431684263275215, + "top1_action_selection": 0.564, + "selected_success_rate": 0.408, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9699546980612324, + "potential_edge_mae": 0.38448416580697453, + "effect_prediction_mae": 0.023410354449074546, + "selection_regret": 0.11005465108156204, + "selected_candidate_type_counts": { + "expert": 253, + "near_miss": 176, + "no_op": 1, + "random_negative": 2, + "wrong_direction": 32, + "wrong_gripper": 36 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8213511620927674, + "top1_action_selection": 0.672, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9736383339199919, + "potential_edge_mae": 0.18715317591096853, + "effect_prediction_mae": 0.04738139071297154, + "selection_regret": 0.03486216971836984, + "selected_candidate_type_counts": { + "expert": 272, + "near_miss": 22, + "wrong_direction": 13, + "wrong_gripper": 193 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8751110366211347, + "top1_action_selection": 0.485, + "selected_success_rate": 0.319, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9735882877455342, + "potential_edge_mae": 0.3345455800345687, + "effect_prediction_mae": 0.02196135628797628, + "selection_regret": 0.09737784077972174, + "selected_candidate_type_counts": { + "expert": 355, + "near_miss": 120, + "no_op": 514, + "wrong_direction": 10, + "wrong_gripper": 1 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8534141275721046, + "top1_action_selection": 0.706, + "selected_success_rate": 0.612, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9834001332561987, + "potential_edge_mae": 0.2551841262163316, + "effect_prediction_mae": 0.02148250060567993, + "selection_regret": 0.036189094796776775, + "selected_candidate_type_counts": { + "expert": 290, + "near_miss": 57, + "no_op": 1, + "random_negative": 88, + "wrong_direction": 23, + "wrong_gripper": 41 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8681472365182368, + "top1_action_selection": 0.728, + "selected_success_rate": 0.652, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.98101418247597, + "potential_edge_mae": 0.3613338892184103, + "effect_prediction_mae": 0.02592257785865129, + "selection_regret": 0.050353113740682603, + "selected_candidate_type_counts": { + "expert": 300, + "near_miss": 10, + "no_op": 1, + "random_negative": 31, + "wrong_direction": 22, + "wrong_gripper": 136 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8470087053157992, + "top1_action_selection": 0.572, + "selected_success_rate": 0.336, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.969212662852709, + "potential_edge_mae": 0.30829660008418086, + "effect_prediction_mae": 0.03832118878520358, + "selection_regret": 0.0997215932905674, + "selected_candidate_type_counts": { + "expert": 369, + "near_miss": 94, + "no_op": 8, + "random_negative": 7, + "wrong_direction": 15, + "wrong_gripper": 7 + } + } + } +} + ✅ Complete +Evaluating lr0.0001_h512... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0001_h512/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8460540338977927, + "top1_action_selection": 0.5917142857142857, + "selected_success_rate": 0.3617142857142857, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9672160590064904, + "potential_edge_mae": 0.2945582225588176, + "effect_prediction_mae": 0.03368794577856568, + "selection_regret": 0.09571207055183394, + "selected_candidate_type_counts": { + "expert": 2257, + "near_miss": 584, + "no_op": 51, + "random_negative": 112, + "wrong_direction": 164, + "wrong_gripper": 332 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8326623006341691, + "top1_action_selection": 0.524, + "selected_success_rate": 0.394, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9642711790468801, + "potential_edge_mae": 0.33132932179215696, + "effect_prediction_mae": 0.03081560057793187, + "selection_regret": 0.12836373773217202, + "selected_candidate_type_counts": { + "expert": 269, + "near_miss": 155, + "no_op": 1, + "random_negative": 2, + "wrong_direction": 10, + "wrong_gripper": 63 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.816532728667178, + "top1_action_selection": 0.696, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9747586613473478, + "potential_edge_mae": 0.18727906713804388, + "effect_prediction_mae": 0.05063540337178578, + "selection_regret": 0.03215906191244721, + "selected_candidate_type_counts": { + "expert": 302, + "near_miss": 7, + "wrong_direction": 2, + "wrong_gripper": 189 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8714908237660269, + "top1_action_selection": 0.492, + "selected_success_rate": 0.284, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.957668208745683, + "potential_edge_mae": 0.33035079169998766, + "effect_prediction_mae": 0.026629596806634525, + "selection_regret": 0.13361719042062759, + "selected_candidate_type_counts": { + "expert": 622, + "near_miss": 275, + "no_op": 42, + "wrong_direction": 48, + "wrong_gripper": 13 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.849731280645783, + "top1_action_selection": 0.698, + "selected_success_rate": 0.606, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9801882901214901, + "potential_edge_mae": 0.2161685740739312, + "effect_prediction_mae": 0.028652405746174076, + "selection_regret": 0.04885206139832735, + "selected_candidate_type_counts": { + "expert": 350, + "near_miss": 18, + "no_op": 6, + "random_negative": 96, + "wrong_direction": 22, + "wrong_gripper": 8 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8591034534198553, + "top1_action_selection": 0.71, + "selected_success_rate": 0.656, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9766712733483381, + "potential_edge_mae": 0.35147075808203515, + "effect_prediction_mae": 0.03188743274973553, + "selection_regret": 0.04478717613220215, + "selected_candidate_type_counts": { + "expert": 309, + "near_miss": 66, + "random_negative": 14, + "wrong_direction": 66, + "wrong_gripper": 45 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8231322298742191, + "top1_action_selection": 0.53, + "selected_success_rate": 0.298, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9592865916900153, + "potential_edge_mae": 0.30540548253338196, + "effect_prediction_mae": 0.04056558439106387, + "selection_regret": 0.14858807584643363, + "selected_candidate_type_counts": { + "expert": 405, + "near_miss": 63, + "no_op": 2, + "wrong_direction": 16, + "wrong_gripper": 14 + } + } + } +} + ✅ Complete +Evaluating lr0.0003_h1024... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0003_h1024/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8589263263449896, + "top1_action_selection": 0.6168571428571429, + "selected_success_rate": 0.38371428571428573, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9754980835417467, + "potential_edge_mae": 0.3058828205852706, + "effect_prediction_mae": 0.025145684622448906, + "selection_regret": 0.06594205010522688, + "selected_candidate_type_counts": { + "expert": 1929, + "near_miss": 707, + "no_op": 215, + "random_negative": 70, + "wrong_direction": 175, + "wrong_gripper": 404 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8434794629434432, + "top1_action_selection": 0.556, + "selected_success_rate": 0.408, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.969148287703904, + "potential_edge_mae": 0.3224464018827123, + "effect_prediction_mae": 0.02021031202654655, + "selection_regret": 0.11005249100923538, + "selected_candidate_type_counts": { + "expert": 278, + "near_miss": 166, + "no_op": 3, + "random_negative": 2, + "wrong_direction": 19, + "wrong_gripper": 32 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8305045183233852, + "top1_action_selection": 0.69, + "selected_success_rate": 0.012, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9773535799488441, + "potential_edge_mae": 0.1984737422437245, + "effect_prediction_mae": 0.044405310620696935, + "selection_regret": 0.02953313312307, + "selected_candidate_type_counts": { + "expert": 307, + "near_miss": 2, + "wrong_direction": 2, + "wrong_gripper": 189 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8814212687505237, + "top1_action_selection": 0.529, + "selected_success_rate": 0.317, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9743485785068674, + "potential_edge_mae": 0.33544318505214676, + "effect_prediction_mae": 0.01886101258951324, + "selection_regret": 0.08900124264508485, + "selected_candidate_type_counts": { + "expert": 566, + "near_miss": 202, + "no_op": 205, + "wrong_direction": 24, + "wrong_gripper": 3 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8559193199580327, + "top1_action_selection": 0.708, + "selected_success_rate": 0.616, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9804295819257342, + "potential_edge_mae": 0.2819488027624982, + "effect_prediction_mae": 0.01982430264436949, + "selection_regret": 0.03510657256096601, + "selected_candidate_type_counts": { + "expert": 184, + "near_miss": 147, + "no_op": 1, + "random_negative": 56, + "wrong_direction": 67, + "wrong_gripper": 45 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8698068173342284, + "top1_action_selection": 0.718, + "selected_success_rate": 0.66, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9795507310497178, + "potential_edge_mae": 0.3614990874971274, + "effect_prediction_mae": 0.02168413483905245, + "selection_regret": 0.03699221247434616, + "selected_candidate_type_counts": { + "expert": 300, + "near_miss": 41, + "no_op": 1, + "random_negative": 5, + "wrong_direction": 26, + "wrong_gripper": 127 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.850022731482261, + "top1_action_selection": 0.588, + "selected_success_rate": 0.356, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9733072471502868, + "potential_edge_mae": 0.30742171950764124, + "effect_prediction_mae": 0.03217370704745119, + "selection_regret": 0.07190745627880096, + "selected_candidate_type_counts": { + "expert": 294, + "near_miss": 149, + "no_op": 5, + "random_negative": 7, + "wrong_direction": 37, + "wrong_gripper": 8 + } + } + } +} + ✅ Complete +Evaluating lr0.0003_h256... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0003_h256/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8546884723821425, + "top1_action_selection": 0.6185714285714285, + "selected_success_rate": 0.3842857142857143, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9757918655852275, + "potential_edge_mae": 0.30116573954627207, + "effect_prediction_mae": 0.025571396784792488, + "selection_regret": 0.06712791710746076, + "selected_candidate_type_counts": { + "expert": 1744, + "near_miss": 602, + "no_op": 432, + "random_negative": 136, + "wrong_direction": 184, + "wrong_gripper": 402 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8431684263275215, + "top1_action_selection": 0.578, + "selected_success_rate": 0.406, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9695485770154603, + "potential_edge_mae": 0.3678041250593472, + "effect_prediction_mae": 0.020283942064171227, + "selection_regret": 0.11259605020284652, + "selected_candidate_type_counts": { + "expert": 280, + "near_miss": 129, + "no_op": 3, + "random_negative": 5, + "wrong_direction": 28, + "wrong_gripper": 55 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8232351862349528, + "top1_action_selection": 0.694, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9722609785099039, + "potential_edge_mae": 0.16588159309051775, + "effect_prediction_mae": 0.04346210667804156, + "selection_regret": 0.037038692271336916, + "selected_candidate_type_counts": { + "expert": 330, + "near_miss": 18, + "wrong_direction": 10, + "wrong_gripper": 142 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8777675354060169, + "top1_action_selection": 0.535, + "selected_success_rate": 0.333, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9785328165603038, + "potential_edge_mae": 0.3148331526974693, + "effect_prediction_mae": 0.01956397667920472, + "selection_regret": 0.07130561644583941, + "selected_candidate_type_counts": { + "expert": 390, + "near_miss": 166, + "no_op": 411, + "wrong_direction": 25, + "wrong_gripper": 8 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8370982592124703, + "top1_action_selection": 0.694, + "selected_success_rate": 0.608, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9789420686253112, + "potential_edge_mae": 0.2529799819984579, + "effect_prediction_mae": 0.01990097236313997, + "selection_regret": 0.04657359145581722, + "selected_candidate_type_counts": { + "expert": 133, + "near_miss": 150, + "no_op": 5, + "random_negative": 100, + "wrong_direction": 74, + "wrong_gripper": 38 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8698068173342284, + "top1_action_selection": 0.732, + "selected_success_rate": 0.66, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9815190380418491, + "potential_edge_mae": 0.37485219451968793, + "effect_prediction_mae": 0.022943312991103026, + "selection_regret": 0.036074809312820436, + "selected_candidate_type_counts": { + "expert": 257, + "near_miss": 43, + "no_op": 4, + "random_negative": 27, + "wrong_direction": 21, + "wrong_gripper": 148 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8514876492279715, + "top1_action_selection": 0.562, + "selected_success_rate": 0.34, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9712067637834442, + "potential_edge_mae": 0.31674896782432, + "effect_prediction_mae": 0.03328149003868241, + "selection_regret": 0.09500104361772538, + "selected_candidate_type_counts": { + "expert": 354, + "near_miss": 96, + "no_op": 9, + "random_negative": 4, + "wrong_direction": 26, + "wrong_gripper": 11 + } + } + } +} + ✅ Complete +Evaluating lr0.0003_h512... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0003_h512/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.848873390963444, + "top1_action_selection": 0.6065714285714285, + "selected_success_rate": 0.37942857142857145, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9729356993298082, + "potential_edge_mae": 0.2817135802084327, + "effect_prediction_mae": 0.027282091901553684, + "selection_regret": 0.07330002299270459, + "selected_candidate_type_counts": { + "expert": 2029, + "near_miss": 454, + "no_op": 408, + "random_negative": 115, + "wrong_direction": 136, + "wrong_gripper": 358 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.834614919389677, + "top1_action_selection": 0.546, + "selected_success_rate": 0.406, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9656286678043069, + "potential_edge_mae": 0.330484778162696, + "effect_prediction_mae": 0.021617836275652554, + "selection_regret": 0.11216536232829094, + "selected_candidate_type_counts": { + "expert": 382, + "near_miss": 77, + "no_op": 3, + "random_negative": 3, + "wrong_direction": 2, + "wrong_gripper": 33 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8157157624462302, + "top1_action_selection": 0.684, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9709639036788248, + "potential_edge_mae": 0.1802397970459396, + "effect_prediction_mae": 0.04605580753744575, + "selection_regret": 0.0350611395612359, + "selected_candidate_type_counts": { + "expert": 313, + "wrong_gripper": 187 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8742478840191067, + "top1_action_selection": 0.516, + "selected_success_rate": 0.329, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9761679921374685, + "potential_edge_mae": 0.30123000960816737, + "effect_prediction_mae": 0.022024564853054648, + "selection_regret": 0.07833491713553667, + "selected_candidate_type_counts": { + "expert": 378, + "near_miss": 165, + "no_op": 397, + "wrong_direction": 59, + "wrong_gripper": 1 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8497955163479862, + "top1_action_selection": 0.712, + "selected_success_rate": 0.608, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9807627407096544, + "potential_edge_mae": 0.23148925279860208, + "effect_prediction_mae": 0.02198729162654722, + "selection_regret": 0.044035196483135225, + "selected_candidate_type_counts": { + "expert": 346, + "near_miss": 10, + "no_op": 1, + "random_negative": 104, + "wrong_direction": 6, + "wrong_gripper": 33 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8656298948310585, + "top1_action_selection": 0.712, + "selected_success_rate": 0.652, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9773934709952478, + "potential_edge_mae": 0.3385302822398105, + "effect_prediction_mae": 0.024117636678057382, + "selection_regret": 0.05143456473946571, + "selected_candidate_type_counts": { + "expert": 299, + "near_miss": 56, + "no_op": 3, + "random_negative": 8, + "wrong_direction": 43, + "wrong_gripper": 91 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8294128542322652, + "top1_action_selection": 0.56, + "selected_success_rate": 0.322, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9634651278456848, + "potential_edge_mae": 0.28564565038845224, + "effect_prediction_mae": 0.03314694148706474, + "selection_regret": 0.11373406356573104, + "selected_candidate_type_counts": { + "expert": 311, + "near_miss": 146, + "no_op": 4, + "wrong_direction": 26, + "wrong_gripper": 13 + } + } + } +} + ✅ Complete +Evaluating lr0.001_h1024... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.001_h1024/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8434815910344949, + "top1_action_selection": 0.604, + "selected_success_rate": 0.38057142857142856, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9730876491517635, + "potential_edge_mae": 0.2698472616256089, + "effect_prediction_mae": 0.030579289966498117, + "selection_regret": 0.07103479064841356, + "selected_candidate_type_counts": { + "expert": 2213, + "near_miss": 457, + "no_op": 416, + "random_negative": 19, + "wrong_direction": 92, + "wrong_gripper": 303 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8308306405626307, + "top1_action_selection": 0.54, + "selected_success_rate": 0.406, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9660322974903598, + "potential_edge_mae": 0.3137131099487799, + "effect_prediction_mae": 0.02394908003827207, + "selection_regret": 0.11257329866290093, + "selected_candidate_type_counts": { + "expert": 371, + "near_miss": 75, + "no_op": 1, + "random_negative": 3, + "wrong_direction": 3, + "wrong_gripper": 47 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8094801427189969, + "top1_action_selection": 0.688, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9733121872704474, + "potential_edge_mae": 0.16557472402521875, + "effect_prediction_mae": 0.05283002683380975, + "selection_regret": 0.03372641992941499, + "selected_candidate_type_counts": { + "expert": 311, + "wrong_gripper": 189 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8727143216290958, + "top1_action_selection": 0.546, + "selected_success_rate": 0.337, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9785930782533997, + "potential_edge_mae": 0.2800411258507225, + "effect_prediction_mae": 0.024340884746986778, + "selection_regret": 0.06354818376153708, + "selected_candidate_type_counts": { + "expert": 528, + "near_miss": 152, + "no_op": 301, + "wrong_direction": 15, + "wrong_gripper": 4 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8477185619767467, + "top1_action_selection": 0.698, + "selected_success_rate": 0.61, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9815710762702458, + "potential_edge_mae": 0.21821787461055844, + "effect_prediction_mae": 0.02772435398163279, + "selection_regret": 0.03969677586853504, + "selected_candidate_type_counts": { + "expert": 335, + "near_miss": 10, + "no_op": 99, + "random_negative": 8, + "wrong_direction": 10, + "wrong_gripper": 38 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8583762213768926, + "top1_action_selection": 0.68, + "selected_success_rate": 0.652, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9756178630873861, + "potential_edge_mae": 0.3387245252715125, + "effect_prediction_mae": 0.02652445964278251, + "selection_regret": 0.05166086229681969, + "selected_candidate_type_counts": { + "expert": 361, + "near_miss": 72, + "no_op": 2, + "random_negative": 7, + "wrong_direction": 51, + "wrong_gripper": 7 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8146289716950951, + "top1_action_selection": 0.53, + "selected_success_rate": 0.312, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9578939634370929, + "potential_edge_mae": 0.29033188505527935, + "effect_prediction_mae": 0.0343453397750186, + "selection_regret": 0.1324898102581501, + "selected_candidate_type_counts": { + "expert": 307, + "near_miss": 148, + "no_op": 13, + "random_negative": 1, + "wrong_direction": 13, + "wrong_gripper": 18 + } + } + } +} + ✅ Complete +Evaluating lr0.001_h256... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.001_h256/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8457542095092202, + "top1_action_selection": 0.6085714285714285, + "selected_success_rate": 0.3811428571428571, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.973310139352499, + "potential_edge_mae": 0.27466379023723564, + "effect_prediction_mae": 0.027333826815782235, + "selection_regret": 0.07030650216580502, + "selected_candidate_type_counts": { + "expert": 2067, + "near_miss": 496, + "no_op": 392, + "random_negative": 98, + "wrong_direction": 109, + "wrong_gripper": 338 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8347013184496552, + "top1_action_selection": 0.542, + "selected_success_rate": 0.406, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9659351211811302, + "potential_edge_mae": 0.3184371621186663, + "effect_prediction_mae": 0.021791804283196124, + "selection_regret": 0.11241285344958306, + "selected_candidate_type_counts": { + "expert": 357, + "near_miss": 65, + "no_op": 1, + "wrong_direction": 2, + "wrong_gripper": 75 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8062122778352062, + "top1_action_selection": 0.696, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9714111646415328, + "potential_edge_mae": 0.16441658506721096, + "effect_prediction_mae": 0.047290608005391595, + "selection_regret": 0.03618285569734871, + "selected_candidate_type_counts": { + "expert": 325, + "wrong_gripper": 175 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8749182938070896, + "top1_action_selection": 0.551, + "selected_success_rate": 0.332, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9781619371359341, + "potential_edge_mae": 0.2922379194550684, + "effect_prediction_mae": 0.021420027887971895, + "selection_regret": 0.07034030451625585, + "selected_candidate_type_counts": { + "expert": 456, + "near_miss": 142, + "no_op": 373, + "wrong_direction": 25, + "wrong_gripper": 4 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8435004175320643, + "top1_action_selection": 0.706, + "selected_success_rate": 0.612, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9810346379461297, + "potential_edge_mae": 0.21957116572661076, + "effect_prediction_mae": 0.024783752131495196, + "selection_regret": 0.035909125804901125, + "selected_candidate_type_counts": { + "expert": 357, + "near_miss": 7, + "no_op": 10, + "random_negative": 93, + "wrong_direction": 14, + "wrong_gripper": 19 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8630006712911166, + "top1_action_selection": 0.702, + "selected_success_rate": 0.658, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9783076597528776, + "potential_edge_mae": 0.34548913748399146, + "effect_prediction_mae": 0.022947574316882033, + "selection_regret": 0.040340313732624054, + "selected_candidate_type_counts": { + "expert": 379, + "near_miss": 36, + "no_op": 4, + "random_negative": 4, + "wrong_direction": 41, + "wrong_gripper": 36 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8240583272996683, + "top1_action_selection": 0.512, + "selected_success_rate": 0.318, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9601585176739642, + "potential_edge_mae": 0.28740767123109495, + "effect_prediction_mae": 0.031682993197568544, + "selection_regret": 0.12661975744366646, + "selected_candidate_type_counts": { + "expert": 193, + "near_miss": 246, + "no_op": 4, + "random_negative": 1, + "wrong_direction": 27, + "wrong_gripper": 29 + } + } + } +} + ✅ Complete +Evaluating lr0.001_h512... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.001_h512/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8457743657706369, + "top1_action_selection": 0.6091428571428571, + "selected_success_rate": 0.3822857142857143, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9737058698114338, + "potential_edge_mae": 0.2749793188709849, + "effect_prediction_mae": 0.029349568262953665, + "selection_regret": 0.0673150017128459, + "selected_candidate_type_counts": { + "expert": 2161, + "near_miss": 519, + "no_op": 366, + "random_negative": 53, + "wrong_direction": 89, + "wrong_gripper": 312 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8306578424426743, + "top1_action_selection": 0.534, + "selected_success_rate": 0.402, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9654602072077931, + "potential_edge_mae": 0.31413109101986825, + "effect_prediction_mae": 0.02357596442498993, + "selection_regret": 0.11722570392489433, + "selected_candidate_type_counts": { + "expert": 333, + "near_miss": 124, + "no_op": 1, + "random_negative": 3, + "wrong_direction": 8, + "wrong_gripper": 31 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.814481976724799, + "top1_action_selection": 0.688, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9731568489181334, + "potential_edge_mae": 0.1779422263486051, + "effect_prediction_mae": 0.05020761414601304, + "selection_regret": 0.03372641992941499, + "selected_candidate_type_counts": { + "expert": 311, + "wrong_gripper": 189 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8753456800469287, + "top1_action_selection": 0.553, + "selected_success_rate": 0.335, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9782237180633907, + "potential_edge_mae": 0.29113034868420423, + "effect_prediction_mae": 0.023486444911958917, + "selection_regret": 0.06449837126582861, + "selected_candidate_type_counts": { + "expert": 516, + "near_miss": 166, + "no_op": 291, + "wrong_direction": 23, + "wrong_gripper": 4 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8492173950281566, + "top1_action_selection": 0.698, + "selected_success_rate": 0.612, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9816916418548487, + "potential_edge_mae": 0.219009720069085, + "effect_prediction_mae": 0.0268146973231316, + "selection_regret": 0.03608278898149729, + "selected_candidate_type_counts": { + "expert": 297, + "near_miss": 20, + "no_op": 68, + "random_negative": 42, + "wrong_direction": 11, + "wrong_gripper": 62 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8605579175057806, + "top1_action_selection": 0.688, + "selected_success_rate": 0.656, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.978374739876166, + "potential_edge_mae": 0.3403800619725687, + "effect_prediction_mae": 0.025413101645652355, + "selection_regret": 0.04361427557468414, + "selected_candidate_type_counts": { + "expert": 403, + "near_miss": 47, + "no_op": 2, + "random_negative": 8, + "wrong_direction": 30, + "wrong_gripper": 10 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8166327097610668, + "top1_action_selection": 0.55, + "selected_success_rate": 0.326, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9608102146963285, + "potential_edge_mae": 0.2873330083330292, + "effect_prediction_mae": 0.03246271047697145, + "selection_regret": 0.11155908104777336, + "selected_candidate_type_counts": { + "expert": 301, + "near_miss": 162, + "no_op": 4, + "wrong_direction": 17, + "wrong_gripper": 16 + } + } + } +} + ✅ Complete + +✅ Phase A4 evaluation complete! diff --git a/workspace/logs/eval_phase_a5_14623957.err b/workspace/logs/eval_phase_a5_14623957.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_phase_a5_14623957.out b/workspace/logs/eval_phase_a5_14623957.out new file mode 100644 index 0000000000000000000000000000000000000000..b2fa6d476636a1e28396e4be9fa3309e847fa57a --- /dev/null +++ b/workspace/logs/eval_phase_a5_14623957.out @@ -0,0 +1,637 @@ +=== Evaluating Phase A5 (Horizon Sweep) === + +Evaluating H=4... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a5_horizon_sweep/h4/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.848873390963444, + "top1_action_selection": 0.6065714285714285, + "selected_success_rate": 0.37942857142857145, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9729356993298082, + "potential_edge_mae": 0.2817135802084327, + "effect_prediction_mae": 0.027282091901553684, + "selection_regret": 0.07330002299270459, + "selected_candidate_type_counts": { + "expert": 2029, + "near_miss": 454, + "no_op": 408, + "random_negative": 115, + "wrong_direction": 136, + "wrong_gripper": 358 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.834614919389677, + "top1_action_selection": 0.546, + "selected_success_rate": 0.406, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9656286678043069, + "potential_edge_mae": 0.330484778162696, + "effect_prediction_mae": 0.021617836275652554, + "selection_regret": 0.11216536232829094, + "selected_candidate_type_counts": { + "expert": 382, + "near_miss": 77, + "no_op": 3, + "random_negative": 3, + "wrong_direction": 2, + "wrong_gripper": 33 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8157157624462302, + "top1_action_selection": 0.684, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9709639036788248, + "potential_edge_mae": 0.1802397970459396, + "effect_prediction_mae": 0.04605580753744575, + "selection_regret": 0.0350611395612359, + "selected_candidate_type_counts": { + "expert": 313, + "wrong_gripper": 187 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8742478840191067, + "top1_action_selection": 0.516, + "selected_success_rate": 0.329, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9761679921374685, + "potential_edge_mae": 0.30123000960816737, + "effect_prediction_mae": 0.022024564853054648, + "selection_regret": 0.07833491713553667, + "selected_candidate_type_counts": { + "expert": 378, + "near_miss": 165, + "no_op": 397, + "wrong_direction": 59, + "wrong_gripper": 1 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8497955163479862, + "top1_action_selection": 0.712, + "selected_success_rate": 0.608, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9807627407096544, + "potential_edge_mae": 0.23148925279860208, + "effect_prediction_mae": 0.02198729162654722, + "selection_regret": 0.044035196483135225, + "selected_candidate_type_counts": { + "expert": 346, + "near_miss": 10, + "no_op": 1, + "random_negative": 104, + "wrong_direction": 6, + "wrong_gripper": 33 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8656298948310585, + "top1_action_selection": 0.712, + "selected_success_rate": 0.652, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9773934709952478, + "potential_edge_mae": 0.3385302822398105, + "effect_prediction_mae": 0.024117636678057382, + "selection_regret": 0.05143456473946571, + "selected_candidate_type_counts": { + "expert": 299, + "near_miss": 56, + "no_op": 3, + "random_negative": 8, + "wrong_direction": 43, + "wrong_gripper": 91 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8294128542322652, + "top1_action_selection": 0.56, + "selected_success_rate": 0.322, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9634651278456848, + "potential_edge_mae": 0.28564565038845224, + "effect_prediction_mae": 0.03314694148706474, + "selection_regret": 0.11373406356573104, + "selected_candidate_type_counts": { + "expert": 311, + "near_miss": 146, + "no_op": 4, + "wrong_direction": 26, + "wrong_gripper": 13 + } + } + } +} +✅ H=4 complete + +Evaluating H=8... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a5_horizon_sweep/h8/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8485483712481009, + "top1_action_selection": 0.5965714285714285, + "selected_success_rate": 0.3802857142857143, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9725010947905504, + "potential_edge_mae": 0.28866785881384577, + "effect_prediction_mae": 0.028066217648649663, + "selection_regret": 0.07148465641428317, + "selected_candidate_type_counts": { + "expert": 2054, + "near_miss": 551, + "no_op": 403, + "random_negative": 83, + "wrong_direction": 113, + "wrong_gripper": 296 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8325586217621952, + "top1_action_selection": 0.536, + "selected_success_rate": 0.404, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9647240884995278, + "potential_edge_mae": 0.32863840987709747, + "effect_prediction_mae": 0.023600924187944353, + "selection_regret": 0.11502571770548821, + "selected_candidate_type_counts": { + "expert": 360, + "near_miss": 100, + "no_op": 1, + "random_negative": 1, + "wrong_direction": 2, + "wrong_gripper": 36 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8233352229150689, + "top1_action_selection": 0.684, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.971779476162129, + "potential_edge_mae": 0.20254382133708312, + "effect_prediction_mae": 0.04473089839226843, + "selection_regret": 0.034455125350505114, + "selected_candidate_type_counts": { + "expert": 309, + "wrong_gripper": 191 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8731836084806838, + "top1_action_selection": 0.526, + "selected_success_rate": 0.332, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9766583483581768, + "potential_edge_mae": 0.307460764325826, + "effect_prediction_mae": 0.023161722374274427, + "selection_regret": 0.07190046679973602, + "selected_candidate_type_counts": { + "expert": 408, + "near_miss": 176, + "no_op": 378, + "wrong_direction": 36, + "wrong_gripper": 2 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8521080016273045, + "top1_action_selection": 0.702, + "selected_success_rate": 0.606, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9803024257139668, + "potential_edge_mae": 0.2296969227850877, + "effect_prediction_mae": 0.022678795472643554, + "selection_regret": 0.047168828047811986, + "selected_candidate_type_counts": { + "expert": 322, + "near_miss": 40, + "no_op": 15, + "random_negative": 78, + "wrong_direction": 22, + "wrong_gripper": 23 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8645297232788841, + "top1_action_selection": 0.684, + "selected_success_rate": 0.656, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9764559542656174, + "potential_edge_mae": 0.3461657687938562, + "effect_prediction_mae": 0.024554847819109406, + "selection_regret": 0.04448705795407295, + "selected_candidate_type_counts": { + "expert": 374, + "near_miss": 67, + "no_op": 7, + "random_negative": 4, + "wrong_direction": 33, + "wrong_gripper": 15 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8228628197140885, + "top1_action_selection": 0.518, + "selected_success_rate": 0.322, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9609290221762656, + "potential_edge_mae": 0.2933905524675264, + "effect_prediction_mae": 0.034574612920032624, + "selection_regret": 0.11545493224263191, + "selected_candidate_type_counts": { + "expert": 281, + "near_miss": 168, + "no_op": 2, + "wrong_direction": 20, + "wrong_gripper": 29 + } + } + } +} +✅ H=8 complete + +Evaluating H=12... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a5_horizon_sweep/h12/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8513979627058773, + "top1_action_selection": 0.6137142857142858, + "selected_success_rate": 0.38171428571428573, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9739879618625659, + "potential_edge_mae": 0.311519485185442, + "effect_prediction_mae": 0.025371263522738295, + "selection_regret": 0.06960772782510945, + "selected_candidate_type_counts": { + "expert": 2172, + "near_miss": 545, + "no_op": 226, + "random_negative": 51, + "wrong_direction": 124, + "wrong_gripper": 382 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.8372760104370064, + "top1_action_selection": 0.534, + "selected_success_rate": 0.402, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.9666355424432421, + "potential_edge_mae": 0.32322976398370507, + "effect_prediction_mae": 0.02049270931245466, + "selection_regret": 0.11816497150063515, + "selected_candidate_type_counts": { + "expert": 332, + "near_miss": 125, + "no_op": 2, + "random_negative": 3, + "wrong_direction": 7, + "wrong_gripper": 31 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8275867818200007, + "top1_action_selection": 0.688, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9756686311964435, + "potential_edge_mae": 0.20839558574568923, + "effect_prediction_mae": 0.04319144305446601, + "selection_regret": 0.03323618159070611, + "selected_candidate_type_counts": { + "expert": 305, + "near_miss": 3, + "wrong_direction": 2, + "wrong_gripper": 190 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8782452023799547, + "top1_action_selection": 0.545, + "selected_success_rate": 0.328, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9766781829196756, + "potential_edge_mae": 0.33982576701873135, + "effect_prediction_mae": 0.019926206135357987, + "selection_regret": 0.07382571572810412, + "selected_candidate_type_counts": { + "expert": 586, + "near_miss": 173, + "no_op": 214, + "wrong_direction": 27 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8509731708883798, + "top1_action_selection": 0.708, + "selected_success_rate": 0.612, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9819373152379046, + "potential_edge_mae": 0.25320796231382414, + "effect_prediction_mae": 0.019695551005677287, + "selection_regret": 0.036286431349813936, + "selected_candidate_type_counts": { + "expert": 308, + "near_miss": 72, + "no_op": 2, + "random_negative": 36, + "wrong_direction": 48, + "wrong_gripper": 34 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.861117326769598, + "top1_action_selection": 0.724, + "selected_success_rate": 0.656, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9790737222751167, + "potential_edge_mae": 0.35681431011837317, + "effect_prediction_mae": 0.021291259668266945, + "selection_regret": 0.04376586377620697, + "selected_candidate_type_counts": { + "expert": 349, + "near_miss": 16, + "no_op": 1, + "random_negative": 5, + "wrong_direction": 10, + "wrong_gripper": 119 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8268197814410075, + "top1_action_selection": 0.552, + "selected_success_rate": 0.336, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9612441560458905, + "potential_edge_mae": 0.352334169154979, + "effect_prediction_mae": 0.0330754693475864, + "selection_regret": 0.10814921510219574, + "selected_candidate_type_counts": { + "expert": 292, + "near_miss": 156, + "no_op": 7, + "random_negative": 7, + "wrong_direction": 30, + "wrong_gripper": 8 + } + } + } +} +✅ H=12 complete + +Evaluating H=16... +{ + "checkpoint": "/scratch/knguy52/dovla/experiments/phase_a5_horizon_sweep/h16/best.pt", + "dataset": "/scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection", + "split": "all_groups", + "seed": 0, + "k": 16, + "training_k": null, + "evaluation_k": 16, + "objective": "lattice_field", + "observation_mode": "state", + "backbone_type": "native", + "backbone_model": null, + "val_fraction": 0.2, + "num_groups": 3500, + "num_records": 56000, + "num_pairs": 396899, + "pairwise_ranking_accuracy": 0.8454317093265541, + "top1_action_selection": 0.606, + "selected_success_rate": 0.374, + "oracle_success_rate": 0.4257142857142857, + "ndcg_at_k": 0.9723573627528053, + "potential_edge_mae": 0.3004329855448288, + "effect_prediction_mae": 0.02708220269034668, + "selection_regret": 0.07832444343449814, + "selected_candidate_type_counts": { + "expert": 2309, + "near_miss": 507, + "no_op": 164, + "random_negative": 56, + "wrong_direction": 106, + "wrong_gripper": 358 + }, + "per_task": { + "LiftPegUpright-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 57871, + "pairwise_ranking_accuracy": 0.826718045307667, + "top1_action_selection": 0.534, + "selected_success_rate": 0.394, + "oracle_success_rate": 0.492, + "ndcg_at_k": 0.964069332117036, + "potential_edge_mae": 0.3301231290942507, + "effect_prediction_mae": 0.023808806435093702, + "selection_regret": 0.12785429334640502, + "selected_candidate_type_counts": { + "expert": 364, + "near_miss": 93, + "random_negative": 2, + "wrong_direction": 5, + "wrong_gripper": 36 + } + }, + "PegInsertionSide-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59978, + "pairwise_ranking_accuracy": 0.8099303077795191, + "top1_action_selection": 0.684, + "selected_success_rate": 0.01, + "oracle_success_rate": 0.026, + "ndcg_at_k": 0.9732681459128515, + "potential_edge_mae": 0.20445765791515638, + "effect_prediction_mae": 0.047586079942799946, + "selection_regret": 0.034455125350505114, + "selected_candidate_type_counts": { + "expert": 309, + "wrong_gripper": 191 + } + }, + "PickCube-v1": { + "num_groups": 1000, + "num_records": 16000, + "num_pairs": 119330, + "pairwise_ranking_accuracy": 0.8753875806586776, + "top1_action_selection": 0.545, + "selected_success_rate": 0.322, + "oracle_success_rate": 0.374, + "ndcg_at_k": 0.9750336101290297, + "potential_edge_mae": 0.3430605992787838, + "effect_prediction_mae": 0.020885566346230018, + "selection_regret": 0.07967505978792906, + "selected_candidate_type_counts": { + "expert": 613, + "near_miss": 206, + "no_op": 149, + "wrong_direction": 26, + "wrong_gripper": 6 + } + }, + "PullCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 46703, + "pairwise_ranking_accuracy": 0.8414448750615592, + "top1_action_selection": 0.696, + "selected_success_rate": 0.606, + "oracle_success_rate": 0.628, + "ndcg_at_k": 0.9809098310104648, + "potential_edge_mae": 0.21708434699290485, + "effect_prediction_mae": 0.02073788793229813, + "selection_regret": 0.04757944610714913, + "selected_candidate_type_counts": { + "expert": 276, + "near_miss": 34, + "no_op": 9, + "random_negative": 39, + "wrong_direction": 36, + "wrong_gripper": 106 + } + }, + "PushCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 53628, + "pairwise_ranking_accuracy": 0.8619377936898635, + "top1_action_selection": 0.698, + "selected_success_rate": 0.654, + "oracle_success_rate": 0.678, + "ndcg_at_k": 0.9775019818027816, + "potential_edge_mae": 0.3419758065459811, + "effect_prediction_mae": 0.023185871818971894, + "selection_regret": 0.04735343900322914, + "selected_candidate_type_counts": { + "expert": 400, + "near_miss": 49, + "no_op": 1, + "random_negative": 14, + "wrong_direction": 24, + "wrong_gripper": 12 + } + }, + "StackCube-v1": { + "num_groups": 500, + "num_records": 8000, + "num_pairs": 59389, + "pairwise_ranking_accuracy": 0.8275606593813669, + "top1_action_selection": 0.54, + "selected_success_rate": 0.31, + "oracle_success_rate": 0.408, + "ndcg_at_k": 0.9606850281684548, + "potential_edge_mae": 0.31080914641623364, + "effect_prediction_mae": 0.03248564001080156, + "selection_regret": 0.13167868065834046, + "selected_candidate_type_counts": { + "expert": 347, + "near_miss": 125, + "no_op": 5, + "random_negative": 1, + "wrong_direction": 15, + "wrong_gripper": 7 + } + } + } +} +✅ H=16 complete + +✅ All Phase A5 evaluations complete! diff --git a/workspace/logs/eval_transformer_14708976_0.err b/workspace/logs/eval_transformer_14708976_0.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_transformer_14708976_0.out b/workspace/logs/eval_transformer_14708976_0.out new file mode 100644 index 0000000000000000000000000000000000000000..15dfd4ca340ffe0799f1873e770802b335595bab --- /dev/null +++ b/workspace/logs/eval_transformer_14708976_0.out @@ -0,0 +1,8 @@ +=== Evaluating Baseline Transformer (No Language) === +Seed: 0 + +Selected success rate: 0.3780 +Top-1 selection: 0.6429 +Oracle success: 0.4257 + +✅ Evaluation complete diff --git a/workspace/logs/eval_transformer_14708976_1.err b/workspace/logs/eval_transformer_14708976_1.err new file mode 100644 index 0000000000000000000000000000000000000000..bb1bb18bcca3f9eae45494ce002215f8a74a2caa --- /dev/null +++ b/workspace/logs/eval_transformer_14708976_1.err @@ -0,0 +1,43 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_transformer_checkpoint.py", line 155, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_transformer_checkpoint.py", line 143, in main + result = evaluate_transformer_checkpoint( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/eval_transformer_checkpoint.py", line 36, in evaluate_transformer_checkpoint + checkpoint = torch.load(checkpoint_path, map_location=resolved_device, weights_only=False) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/torch/serialization.py", line 1579, in load + return _load( + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/torch/serialization.py", line 2190, in _load + result = unpickler.load() + ^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/torch/serialization.py", line 2154, in persistent_load + typed_storage = load_tensor( + ^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/torch/serialization.py", line 2116, in load_tensor + wrap_storage = restore_location(storage, location) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/torch/serialization.py", line 1915, in restore_location + return default_restore_location(storage, map_location) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/torch/serialization.py", line 734, in default_restore_location + result = fn(storage, location) + ^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/torch/serialization.py", line 668, in _deserialize + return obj.to(device=device) + ^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/torch/storage.py", line 289, in to + return _to(self, device, non_blocking) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/torch/_utils.py", line 106, in _to + untyped_storage = torch.UntypedStorage(self.size(), device=device) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +torch.AcceleratorError: CUDA error: CUDA-capable device(s) is/are busy or unavailable +Search for `cudaErrorDevicesUnavailable' in https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__TYPES.html for more information. +CUDA kernel errors might be asynchronously reported at some other API call, so the stacktrace below might be incorrect. +For debugging consider passing CUDA_LAUNCH_BLOCKING=1 +Compile with `TORCH_USE_CUDA_DSA` to enable device-side assertions. + diff --git a/workspace/logs/eval_transformer_14708976_1.out b/workspace/logs/eval_transformer_14708976_1.out new file mode 100644 index 0000000000000000000000000000000000000000..21976405e186626a79874b9dc0caaf99d6b5fd4b --- /dev/null +++ b/workspace/logs/eval_transformer_14708976_1.out @@ -0,0 +1,3 @@ +=== Evaluating Baseline Transformer (No Language) === +Seed: 1 + diff --git a/workspace/logs/eval_transformer_14708976_2.err b/workspace/logs/eval_transformer_14708976_2.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/eval_transformer_14708976_2.out b/workspace/logs/eval_transformer_14708976_2.out new file mode 100644 index 0000000000000000000000000000000000000000..9d4055ebfb6a5ddcc16ce81740a407872736693e --- /dev/null +++ b/workspace/logs/eval_transformer_14708976_2.out @@ -0,0 +1,8 @@ +=== Evaluating Baseline Transformer (No Language) === +Seed: 2 + +Selected success rate: 0.3631 +Top-1 selection: 0.6277 +Oracle success: 0.4257 + +✅ Evaluation complete diff --git a/workspace/logs/gen_embeddings_14708990.err b/workspace/logs/gen_embeddings_14708990.err new file mode 100644 index 0000000000000000000000000000000000000000..e6d52750d5b81917720517cc651d44d933558370 --- /dev/null +++ b/workspace/logs/gen_embeddings_14708990.err @@ -0,0 +1,80 @@ +'[Errno 101] Network is unreachable' thrown while requesting HEAD https://huggingface.co/sentence-transformers/all-mpnet-base-v2/resolve/main/adapter_config.json +Retrying in 1s [Retry 1/5]. +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/generate_instruction_embeddings.py", line 102, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/generate_instruction_embeddings.py", line 74, in main + embedder = LanguageEmbedder( + ^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/utils/language_embeddings.py", line 27, in __init__ + self.model = SentenceTransformer(model_name) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/sentence_transformers/util/decorators.py", line 41, in wrapper + return func(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/sentence_transformers/sentence_transformer/model.py", line 188, in __init__ + super().__init__( + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/sentence_transformers/base/model.py", line 216, in __init__ + modules, self.module_kwargs = self._load_modules( + ^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/sentence_transformers/base/model.py", line 1002, in _load_modules + return self._load_config_modules(model_name_or_path, **load_kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/sentence_transformers/base/model.py", line 1208, in _load_config_modules + module = module_class.load( + ^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/sentence_transformers/base/modules/transformer.py", line 2030, in load + return cls(model_name_or_path=model_name_or_path, **init_kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/sentence_transformers/util/decorators.py", line 87, in wrapper + return func(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/sentence_transformers/base/modules/transformer.py", line 639, in __init__ + config, is_peft_model = self._load_config(model_name_or_path, backend, config_kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/sentence_transformers/base/modules/transformer.py", line 1647, in _load_config + adapter_config_file = find_adapter_config_file( + ^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/transformers/utils/peft_utils.py", line 84, in find_adapter_config_file + adapter_cached_filename = cached_file( + ^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/transformers/utils/hub.py", line 293, in cached_file + file = cached_files(path_or_repo_id=path_or_repo_id, filenames=[filename], **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/transformers/utils/hub.py", line 527, in cached_files + raise e + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/transformers/utils/hub.py", line 437, in cached_files + hf_hub_download( + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/huggingface_hub/utils/_validators.py", line 88, in _inner_fn + return fn(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/huggingface_hub/file_download.py", line 1016, in hf_hub_download + return _hf_hub_download_to_cache_dir( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/huggingface_hub/file_download.py", line 1149, in _hf_hub_download_to_cache_dir + _get_metadata_or_catch_error( + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/huggingface_hub/file_download.py", line 1691, in _get_metadata_or_catch_error + metadata = get_hf_file_metadata( + ^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/huggingface_hub/utils/_validators.py", line 88, in _inner_fn + return fn(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/huggingface_hub/file_download.py", line 1613, in get_hf_file_metadata + response = _httpx_follow_relative_redirects_with_backoff( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/huggingface_hub/utils/_http.py", line 696, in _httpx_follow_relative_redirects_with_backoff + response = http_backoff( + ^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/huggingface_hub/utils/_http.py", line 570, in http_backoff + return next( + ^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/huggingface_hub/utils/_http.py", line 478, in _http_backoff_base + response = client.request(method=method, url=url, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/httpx/_client.py", line 825, in request + return self.send(request, auth=auth, follow_redirects=follow_redirects) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/.venv/lib/python3.11/site-packages/httpx/_client.py", line 901, in send + raise RuntimeError("Cannot send a request, as the client has been closed.") +RuntimeError: Cannot send a request, as the client has been closed. diff --git a/workspace/logs/gen_embeddings_14708990.out b/workspace/logs/gen_embeddings_14708990.out new file mode 100644 index 0000000000000000000000000000000000000000..7f1452188a04d8edc376b06f95ad25b3031764dc --- /dev/null +++ b/workspace/logs/gen_embeddings_14708990.out @@ -0,0 +1,14 @@ +=== Generating Instruction Embeddings (Fast Parallel) === +Using 8 CPU cores for parallel encoding + +====================================================================== +Instruction Embedding Generation +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Output: /scratch/knguy52/dovla/experiments/instruction_embeddings.pkl +Model: all-mpnet-base-v2 + +Loading dataset... +Found 3500 groups + +Loading embedding model: all-mpnet-base-v2 diff --git a/workspace/logs/hybrid_direct_14714365_0.err b/workspace/logs/hybrid_direct_14714365_0.err new file mode 100644 index 0000000000000000000000000000000000000000..18fc19e37a8de945fd05adf161e748e8fa75b7ee --- /dev/null +++ b/workspace/logs/hybrid_direct_14714365_0.err @@ -0,0 +1,11 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 347, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 286, in main + train_metrics = train_epoch(model, train_loader, optimizer, device) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 149, in train_epoch + reward_loss = F.mse_loss(pred_rewards, target_rewards) + ^ +NameError: name 'F' is not defined diff --git a/workspace/logs/hybrid_direct_14714365_0.out b/workspace/logs/hybrid_direct_14714365_0.out new file mode 100644 index 0000000000000000000000000000000000000000..99091424cf89714dd8e8fbfb1336d81eb330a144 --- /dev/null +++ b/workspace/logs/hybrid_direct_14714365_0.out @@ -0,0 +1,32 @@ += = = = = = = = = = = = = = = +DoVLA-Hybrid: DIRECT Scoring (FIXED!) += = = = = = = = = = = = = = = + +KEY IMPROVEMENT: + OLD: Pairwise ranking → aggregate → 37% + NEW: Direct scoring → 45-48% + +Approach: + - Predict reward(action) directly + - Predict success(action) directly + - Select: argmax(success_prob * reward) + +Expected: 45-48% WITHOUT language +Then +language: 55-60% final + +Seed: 0 + +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + +Total: 3500, Train: 2800, Val: 700 + +Model parameters: 5,093,890 + +Starting training... + diff --git a/workspace/logs/hybrid_direct_14714365_1.err b/workspace/logs/hybrid_direct_14714365_1.err new file mode 100644 index 0000000000000000000000000000000000000000..18fc19e37a8de945fd05adf161e748e8fa75b7ee --- /dev/null +++ b/workspace/logs/hybrid_direct_14714365_1.err @@ -0,0 +1,11 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 347, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 286, in main + train_metrics = train_epoch(model, train_loader, optimizer, device) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 149, in train_epoch + reward_loss = F.mse_loss(pred_rewards, target_rewards) + ^ +NameError: name 'F' is not defined diff --git a/workspace/logs/hybrid_direct_14714365_1.out b/workspace/logs/hybrid_direct_14714365_1.out new file mode 100644 index 0000000000000000000000000000000000000000..962e2265b01f1b8da03f9867c690880949939416 --- /dev/null +++ b/workspace/logs/hybrid_direct_14714365_1.out @@ -0,0 +1,32 @@ += = = = = = = = = = = = = = = +DoVLA-Hybrid: DIRECT Scoring (FIXED!) += = = = = = = = = = = = = = = + +KEY IMPROVEMENT: + OLD: Pairwise ranking → aggregate → 37% + NEW: Direct scoring → 45-48% + +Approach: + - Predict reward(action) directly + - Predict success(action) directly + - Select: argmax(success_prob * reward) + +Expected: 45-48% WITHOUT language +Then +language: 55-60% final + +Seed: 1 + +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + +Total: 3500, Train: 2800, Val: 700 + +Model parameters: 5,093,890 + +Starting training... + diff --git a/workspace/logs/hybrid_direct_14714365_2.err b/workspace/logs/hybrid_direct_14714365_2.err new file mode 100644 index 0000000000000000000000000000000000000000..18fc19e37a8de945fd05adf161e748e8fa75b7ee --- /dev/null +++ b/workspace/logs/hybrid_direct_14714365_2.err @@ -0,0 +1,11 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 347, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 286, in main + train_metrics = train_epoch(model, train_loader, optimizer, device) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 149, in train_epoch + reward_loss = F.mse_loss(pred_rewards, target_rewards) + ^ +NameError: name 'F' is not defined diff --git a/workspace/logs/hybrid_direct_14714365_2.out b/workspace/logs/hybrid_direct_14714365_2.out new file mode 100644 index 0000000000000000000000000000000000000000..4631207185d978440d92ea2af01e19d4510dd929 --- /dev/null +++ b/workspace/logs/hybrid_direct_14714365_2.out @@ -0,0 +1,32 @@ += = = = = = = = = = = = = = = +DoVLA-Hybrid: DIRECT Scoring (FIXED!) += = = = = = = = = = = = = = = + +KEY IMPROVEMENT: + OLD: Pairwise ranking → aggregate → 37% + NEW: Direct scoring → 45-48% + +Approach: + - Predict reward(action) directly + - Predict success(action) directly + - Select: argmax(success_prob * reward) + +Expected: 45-48% WITHOUT language +Then +language: 55-60% final + +Seed: 2 + +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + +Total: 3500, Train: 2800, Val: 700 + +Model parameters: 5,093,890 + +Starting training... + diff --git a/workspace/logs/hybrid_direct_14716069_0.err b/workspace/logs/hybrid_direct_14716069_0.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/hybrid_direct_14716069_0.out b/workspace/logs/hybrid_direct_14716069_0.out new file mode 100644 index 0000000000000000000000000000000000000000..d4069d72df85cd1bfeae5a8f9fce1a9133db6c9c --- /dev/null +++ b/workspace/logs/hybrid_direct_14716069_0.out @@ -0,0 +1,87 @@ += = = = = = = = = = = = = = = +DoVLA-Hybrid: DIRECT Scoring (FIXED!) += = = = = = = = = = = = = = = + +KEY IMPROVEMENT: + OLD: Pairwise ranking → aggregate → 37% + NEW: Direct scoring → 45-48% + +Approach: + - Predict reward(action) directly + - Predict success(action) directly + - Select: argmax(success_prob * reward) + +Expected: 45-48% WITHOUT language +Then +language: 55-60% final + +Seed: 0 + +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + +Total: 3500, Train: 2800, Val: 700 + +Model parameters: 5,093,890 + +Starting training... + +Epoch 1/50: r_loss=2.6963, s_loss=0.5513, val_top1=0.1614 +Epoch 2/50: r_loss=0.6439, s_loss=0.4144, val_top1=0.4814 +Epoch 3/50: r_loss=0.3352, s_loss=0.3476, val_top1=0.4500 +Epoch 4/50: r_loss=0.2609, s_loss=0.2854, val_top1=0.4657 +Epoch 5/50: r_loss=0.2143, s_loss=0.2240, val_top1=0.4829 +Epoch 6/50: r_loss=0.1881, s_loss=0.2007, val_top1=0.4900 +Epoch 7/50: r_loss=0.1671, s_loss=0.1817, val_top1=0.4900 +Epoch 8/50: r_loss=0.1531, s_loss=0.1680, val_top1=0.5129 +Epoch 9/50: r_loss=0.1438, s_loss=0.1596, val_top1=0.4757 +Epoch 10/50: r_loss=0.1354, s_loss=0.1504, val_top1=0.4857 +Epoch 11/50: r_loss=0.1275, s_loss=0.1448, val_top1=0.5000 +Epoch 12/50: r_loss=0.1229, s_loss=0.1378, val_top1=0.4986 +Epoch 13/50: r_loss=0.1165, s_loss=0.1304, val_top1=0.5000 +Epoch 14/50: r_loss=0.1145, s_loss=0.1312, val_top1=0.4914 +Epoch 15/50: r_loss=0.1093, s_loss=0.1249, val_top1=0.5086 +Epoch 16/50: r_loss=0.1071, s_loss=0.1214, val_top1=0.5271 +Epoch 17/50: r_loss=0.1059, s_loss=0.1221, val_top1=0.4843 +Epoch 18/50: r_loss=0.1039, s_loss=0.1186, val_top1=0.5100 +Epoch 19/50: r_loss=0.1018, s_loss=0.1183, val_top1=0.5214 +Epoch 20/50: r_loss=0.0991, s_loss=0.1124, val_top1=0.5100 +Epoch 21/50: r_loss=0.0978, s_loss=0.1127, val_top1=0.5214 +Epoch 22/50: r_loss=0.0939, s_loss=0.1076, val_top1=0.5286 +Epoch 23/50: r_loss=0.0934, s_loss=0.1087, val_top1=0.5743 +Epoch 24/50: r_loss=0.0927, s_loss=0.1060, val_top1=0.5657 +Epoch 25/50: r_loss=0.0907, s_loss=0.1061, val_top1=0.5371 +Epoch 26/50: r_loss=0.0894, s_loss=0.1029, val_top1=0.5686 +Epoch 27/50: r_loss=0.0895, s_loss=0.1028, val_top1=0.5429 +Epoch 28/50: r_loss=0.0901, s_loss=0.1042, val_top1=0.5314 +Epoch 29/50: r_loss=0.0864, s_loss=0.1002, val_top1=0.5429 +Epoch 30/50: r_loss=0.0865, s_loss=0.1011, val_top1=0.5714 +Epoch 31/50: r_loss=0.0845, s_loss=0.0991, val_top1=0.5757 +Epoch 32/50: r_loss=0.0831, s_loss=0.0970, val_top1=0.5614 +Epoch 33/50: r_loss=0.0809, s_loss=0.0955, val_top1=0.5743 +Epoch 34/50: r_loss=0.0826, s_loss=0.0955, val_top1=0.5186 +Epoch 35/50: r_loss=0.0802, s_loss=0.0935, val_top1=0.5543 +Epoch 36/50: r_loss=0.0815, s_loss=0.0949, val_top1=0.5829 +Epoch 37/50: r_loss=0.0811, s_loss=0.0943, val_top1=0.5671 +Epoch 38/50: r_loss=0.0783, s_loss=0.0922, val_top1=0.5786 +Epoch 39/50: r_loss=0.0782, s_loss=0.0912, val_top1=0.5743 +Epoch 40/50: r_loss=0.0779, s_loss=0.0912, val_top1=0.5300 +Epoch 41/50: r_loss=0.0786, s_loss=0.0914, val_top1=0.5257 +Epoch 42/50: r_loss=0.0759, s_loss=0.0891, val_top1=0.5757 +Epoch 43/50: r_loss=0.0764, s_loss=0.0896, val_top1=0.5943 +Epoch 44/50: r_loss=0.0745, s_loss=0.0878, val_top1=0.5943 +Epoch 45/50: r_loss=0.0728, s_loss=0.0857, val_top1=0.5814 +Epoch 46/50: r_loss=0.0734, s_loss=0.0857, val_top1=0.5543 +Epoch 47/50: r_loss=0.0725, s_loss=0.0854, val_top1=0.5900 +Epoch 48/50: r_loss=0.0700, s_loss=0.0833, val_top1=0.5757 +Epoch 49/50: r_loss=0.0689, s_loss=0.0814, val_top1=0.5771 +Epoch 50/50: r_loss=0.0686, s_loss=0.0799, val_top1=0.5886 + +✅ Training complete! Best val top-1: 0.5943 + +✅ Hybrid training complete (seed 0) + diff --git a/workspace/logs/hybrid_direct_14716069_1.err b/workspace/logs/hybrid_direct_14716069_1.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/hybrid_direct_14716069_1.out b/workspace/logs/hybrid_direct_14716069_1.out new file mode 100644 index 0000000000000000000000000000000000000000..931d6b4384dfeb1fc0ae5784d4deb8dca2e08d51 --- /dev/null +++ b/workspace/logs/hybrid_direct_14716069_1.out @@ -0,0 +1,87 @@ += = = = = = = = = = = = = = = +DoVLA-Hybrid: DIRECT Scoring (FIXED!) += = = = = = = = = = = = = = = + +KEY IMPROVEMENT: + OLD: Pairwise ranking → aggregate → 37% + NEW: Direct scoring → 45-48% + +Approach: + - Predict reward(action) directly + - Predict success(action) directly + - Select: argmax(success_prob * reward) + +Expected: 45-48% WITHOUT language +Then +language: 55-60% final + +Seed: 1 + +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + +Total: 3500, Train: 2800, Val: 700 + +Model parameters: 5,093,890 + +Starting training... + +Epoch 1/50: r_loss=0.7127, s_loss=0.6495, val_top1=0.3329 +Epoch 2/50: r_loss=0.5239, s_loss=0.4127, val_top1=0.5229 +Epoch 3/50: r_loss=0.3590, s_loss=0.3366, val_top1=0.4371 +Epoch 4/50: r_loss=0.2748, s_loss=0.2769, val_top1=0.4757 +Epoch 5/50: r_loss=0.2280, s_loss=0.2327, val_top1=0.4829 +Epoch 6/50: r_loss=0.1965, s_loss=0.2085, val_top1=0.4900 +Epoch 7/50: r_loss=0.1742, s_loss=0.1921, val_top1=0.4957 +Epoch 8/50: r_loss=0.1614, s_loss=0.1773, val_top1=0.5371 +Epoch 9/50: r_loss=0.1499, s_loss=0.1680, val_top1=0.5357 +Epoch 10/50: r_loss=0.1402, s_loss=0.1569, val_top1=0.5300 +Epoch 11/50: r_loss=0.1326, s_loss=0.1500, val_top1=0.4971 +Epoch 12/50: r_loss=0.1268, s_loss=0.1438, val_top1=0.5057 +Epoch 13/50: r_loss=0.1221, s_loss=0.1382, val_top1=0.4986 +Epoch 14/50: r_loss=0.1168, s_loss=0.1334, val_top1=0.5214 +Epoch 15/50: r_loss=0.1115, s_loss=0.1288, val_top1=0.5157 +Epoch 16/50: r_loss=0.1084, s_loss=0.1256, val_top1=0.5229 +Epoch 17/50: r_loss=0.1082, s_loss=0.1255, val_top1=0.5300 +Epoch 18/50: r_loss=0.1057, s_loss=0.1219, val_top1=0.5214 +Epoch 19/50: r_loss=0.1044, s_loss=0.1209, val_top1=0.4986 +Epoch 20/50: r_loss=0.1033, s_loss=0.1184, val_top1=0.5486 +Epoch 21/50: r_loss=0.0999, s_loss=0.1151, val_top1=0.5471 +Epoch 22/50: r_loss=0.1005, s_loss=0.1152, val_top1=0.5886 +Epoch 23/50: r_loss=0.0968, s_loss=0.1124, val_top1=0.5957 +Epoch 24/50: r_loss=0.0959, s_loss=0.1112, val_top1=0.6043 +Epoch 25/50: r_loss=0.0952, s_loss=0.1120, val_top1=0.5971 +Epoch 26/50: r_loss=0.0932, s_loss=0.1087, val_top1=0.5843 +Epoch 27/50: r_loss=0.0923, s_loss=0.1083, val_top1=0.6129 +Epoch 28/50: r_loss=0.0915, s_loss=0.1065, val_top1=0.5943 +Epoch 29/50: r_loss=0.0896, s_loss=0.1065, val_top1=0.5886 +Epoch 30/50: r_loss=0.0891, s_loss=0.1046, val_top1=0.5871 +Epoch 31/50: r_loss=0.0879, s_loss=0.1034, val_top1=0.5829 +Epoch 32/50: r_loss=0.0883, s_loss=0.1040, val_top1=0.5943 +Epoch 33/50: r_loss=0.0859, s_loss=0.1007, val_top1=0.5843 +Epoch 34/50: r_loss=0.0876, s_loss=0.1018, val_top1=0.5743 +Epoch 35/50: r_loss=0.0852, s_loss=0.1008, val_top1=0.5957 +Epoch 36/50: r_loss=0.0839, s_loss=0.0993, val_top1=0.5757 +Epoch 37/50: r_loss=0.0839, s_loss=0.0990, val_top1=0.5857 +Epoch 38/50: r_loss=0.0823, s_loss=0.0977, val_top1=0.5757 +Epoch 39/50: r_loss=0.0821, s_loss=0.0977, val_top1=0.5843 +Epoch 40/50: r_loss=0.0801, s_loss=0.0950, val_top1=0.5900 +Epoch 41/50: r_loss=0.0804, s_loss=0.0960, val_top1=0.5857 +Epoch 42/50: r_loss=0.0804, s_loss=0.0959, val_top1=0.5914 +Epoch 43/50: r_loss=0.0784, s_loss=0.0922, val_top1=0.5900 +Epoch 44/50: r_loss=0.0778, s_loss=0.0927, val_top1=0.5957 +Epoch 45/50: r_loss=0.0786, s_loss=0.0937, val_top1=0.5800 +Epoch 46/50: r_loss=0.0779, s_loss=0.0922, val_top1=0.5871 +Epoch 47/50: r_loss=0.0766, s_loss=0.0896, val_top1=0.5829 +Epoch 48/50: r_loss=0.0744, s_loss=0.0886, val_top1=0.5943 +Epoch 49/50: r_loss=0.0739, s_loss=0.0868, val_top1=0.5957 +Epoch 50/50: r_loss=0.0740, s_loss=0.0878, val_top1=0.5929 + +✅ Training complete! Best val top-1: 0.6129 + +✅ Hybrid training complete (seed 1) + diff --git a/workspace/logs/hybrid_direct_14716069_2.err b/workspace/logs/hybrid_direct_14716069_2.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/hybrid_direct_14716069_2.out b/workspace/logs/hybrid_direct_14716069_2.out new file mode 100644 index 0000000000000000000000000000000000000000..366fcd2d145ab7dc408271d1a4403a8051365be6 --- /dev/null +++ b/workspace/logs/hybrid_direct_14716069_2.out @@ -0,0 +1,87 @@ += = = = = = = = = = = = = = = +DoVLA-Hybrid: DIRECT Scoring (FIXED!) += = = = = = = = = = = = = = = + +KEY IMPROVEMENT: + OLD: Pairwise ranking → aggregate → 37% + NEW: Direct scoring → 45-48% + +Approach: + - Predict reward(action) directly + - Predict success(action) directly + - Select: argmax(success_prob * reward) + +Expected: 45-48% WITHOUT language +Then +language: 55-60% final + +Seed: 2 + +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + +Total: 3500, Train: 2800, Val: 700 + +Model parameters: 5,093,890 + +Starting training... + +Epoch 1/50: r_loss=1.1210, s_loss=0.9893, val_top1=0.1643 +Epoch 2/50: r_loss=0.5601, s_loss=0.4771, val_top1=0.5214 +Epoch 3/50: r_loss=0.3470, s_loss=0.3331, val_top1=0.4771 +Epoch 4/50: r_loss=0.2670, s_loss=0.2752, val_top1=0.4629 +Epoch 5/50: r_loss=0.2242, s_loss=0.2291, val_top1=0.4929 +Epoch 6/50: r_loss=0.1937, s_loss=0.2047, val_top1=0.5071 +Epoch 7/50: r_loss=0.1724, s_loss=0.1894, val_top1=0.5214 +Epoch 8/50: r_loss=0.1587, s_loss=0.1729, val_top1=0.5386 +Epoch 9/50: r_loss=0.1487, s_loss=0.1638, val_top1=0.5257 +Epoch 10/50: r_loss=0.1410, s_loss=0.1551, val_top1=0.4914 +Epoch 11/50: r_loss=0.1340, s_loss=0.1478, val_top1=0.4971 +Epoch 12/50: r_loss=0.1284, s_loss=0.1429, val_top1=0.4986 +Epoch 13/50: r_loss=0.1226, s_loss=0.1372, val_top1=0.5329 +Epoch 14/50: r_loss=0.1180, s_loss=0.1319, val_top1=0.5500 +Epoch 15/50: r_loss=0.1131, s_loss=0.1282, val_top1=0.5257 +Epoch 16/50: r_loss=0.1103, s_loss=0.1252, val_top1=0.5414 +Epoch 17/50: r_loss=0.1071, s_loss=0.1232, val_top1=0.5514 +Epoch 18/50: r_loss=0.1081, s_loss=0.1220, val_top1=0.5986 +Epoch 19/50: r_loss=0.1042, s_loss=0.1191, val_top1=0.5657 +Epoch 20/50: r_loss=0.0996, s_loss=0.1140, val_top1=0.5743 +Epoch 21/50: r_loss=0.0997, s_loss=0.1132, val_top1=0.5700 +Epoch 22/50: r_loss=0.0996, s_loss=0.1138, val_top1=0.5800 +Epoch 23/50: r_loss=0.0962, s_loss=0.1107, val_top1=0.5729 +Epoch 24/50: r_loss=0.0951, s_loss=0.1097, val_top1=0.5929 +Epoch 25/50: r_loss=0.0920, s_loss=0.1061, val_top1=0.5871 +Epoch 26/50: r_loss=0.0910, s_loss=0.1057, val_top1=0.5843 +Epoch 27/50: r_loss=0.0912, s_loss=0.1058, val_top1=0.5786 +Epoch 28/50: r_loss=0.0906, s_loss=0.1056, val_top1=0.5757 +Epoch 29/50: r_loss=0.0894, s_loss=0.1027, val_top1=0.5857 +Epoch 30/50: r_loss=0.0883, s_loss=0.1031, val_top1=0.5829 +Epoch 31/50: r_loss=0.0858, s_loss=0.1002, val_top1=0.5743 +Epoch 32/50: r_loss=0.0863, s_loss=0.1009, val_top1=0.5957 +Epoch 33/50: r_loss=0.0866, s_loss=0.1000, val_top1=0.5800 +Epoch 34/50: r_loss=0.0850, s_loss=0.0994, val_top1=0.5643 +Epoch 35/50: r_loss=0.0839, s_loss=0.0977, val_top1=0.5943 +Epoch 36/50: r_loss=0.0823, s_loss=0.0975, val_top1=0.5857 +Epoch 37/50: r_loss=0.0826, s_loss=0.0965, val_top1=0.5886 +Epoch 38/50: r_loss=0.0809, s_loss=0.0947, val_top1=0.5629 +Epoch 39/50: r_loss=0.0795, s_loss=0.0929, val_top1=0.5757 +Epoch 40/50: r_loss=0.0792, s_loss=0.0933, val_top1=0.5714 +Epoch 41/50: r_loss=0.0789, s_loss=0.0916, val_top1=0.5700 +Epoch 42/50: r_loss=0.0782, s_loss=0.0911, val_top1=0.5729 +Epoch 43/50: r_loss=0.0785, s_loss=0.0912, val_top1=0.5686 +Epoch 44/50: r_loss=0.0777, s_loss=0.0917, val_top1=0.5686 +Epoch 45/50: r_loss=0.0754, s_loss=0.0893, val_top1=0.5443 +Epoch 46/50: r_loss=0.0761, s_loss=0.0896, val_top1=0.5643 +Epoch 47/50: r_loss=0.0750, s_loss=0.0876, val_top1=0.5543 +Epoch 48/50: r_loss=0.0743, s_loss=0.0880, val_top1=0.5300 +Epoch 49/50: r_loss=0.0743, s_loss=0.0872, val_top1=0.5214 +Epoch 50/50: r_loss=0.0722, s_loss=0.0851, val_top1=0.5657 + +✅ Training complete! Best val top-1: 0.5986 + +✅ Hybrid training complete (seed 2) + diff --git a/workspace/logs/monitor_eval_14759050.err b/workspace/logs/monitor_eval_14759050.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/monitor_eval_14759050.out b/workspace/logs/monitor_eval_14759050.out new file mode 100644 index 0000000000000000000000000000000000000000..ccf7d29313967458927f8ac41b2e91c258d75d45 --- /dev/null +++ b/workspace/logs/monitor_eval_14759050.out @@ -0,0 +1,20 @@ +=== Autonomous Evaluation Monitor Started === +Watching job: 14758888 +Start time: Fri 26 Jun 2026 12:14:27 AM EDT + +[00:14:27] Job status: FAILED + +❌ Evaluation failed. Checking logs... +JobID State ExitCode Reason +------------ ---------- -------- ---------------------- +14758888_0 FAILED 1:0 None +14758888_0.+ FAILED 1:0 +14758888_0.+ COMPLETED 0:0 +14758888_1 FAILED 1:0 None +14758888_1.+ FAILED 1:0 +14758888_1.+ COMPLETED 0:0 +14758888_2 FAILED 1:0 None +14758888_2.+ FAILED 1:0 +14758888_2.+ COMPLETED 0:0 + +Check logs in: outputs/hpc/logs/eval_h16_rollout_14758888_*.{out,err} diff --git a/workspace/logs/monitor_eval_final_14775812.err b/workspace/logs/monitor_eval_final_14775812.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/monitor_eval_final_14775812.out b/workspace/logs/monitor_eval_final_14775812.out new file mode 100644 index 0000000000000000000000000000000000000000..97e15b420fd9971ea621009ba18060569a776218 --- /dev/null +++ b/workspace/logs/monitor_eval_final_14775812.out @@ -0,0 +1,10 @@ +=== Final Evaluation Monitor === +Job: 14775756 +Start: Fri 26 Jun 2026 11:15:38 AM EDT + +[11:15:38] Eval status: RUNNING +[11:25:38] Eval status: COMPLETED + +✅ Evaluation completed! Parsing results... + +❌ No results found! diff --git a/workspace/logs/monitor_eval_final_14778351.err b/workspace/logs/monitor_eval_final_14778351.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/monitor_eval_final_14778351.out b/workspace/logs/monitor_eval_final_14778351.out new file mode 100644 index 0000000000000000000000000000000000000000..1a2d01f13540f26167db1ce34bf9436ce5b987a5 --- /dev/null +++ b/workspace/logs/monitor_eval_final_14778351.out @@ -0,0 +1,10 @@ +=== Final Evaluation Monitor === +Job: 14778350 +Start: Fri 26 Jun 2026 11:50:12 AM EDT + +[11:50:12] Eval status: RUNNING +[12:00:12] Eval status: COMPLETED + +✅ Evaluation completed! Parsing results... + +❌ No results found! diff --git a/workspace/logs/monitor_eval_final_14779663.err b/workspace/logs/monitor_eval_final_14779663.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/monitor_eval_final_14779663.out b/workspace/logs/monitor_eval_final_14779663.out new file mode 100644 index 0000000000000000000000000000000000000000..7d3f063276b0af948c781acaa9cbb7db8aa07b5c --- /dev/null +++ b/workspace/logs/monitor_eval_final_14779663.out @@ -0,0 +1,11 @@ +=== Final Evaluation Monitor === +Job: 14779587 +Start: Fri 26 Jun 2026 12:13:40 PM EDT + +[12:13:40] Eval status: RUNNING +[12:23:40] Eval status: RUNNING +[12:33:40] Eval status: COMPLETED + +✅ Evaluation completed! Parsing results... + +❌ No results found! diff --git a/workspace/logs/monitor_h16_correct_14763341.err b/workspace/logs/monitor_h16_correct_14763341.err new file mode 100644 index 0000000000000000000000000000000000000000..d42b817769d5c06cad99011bf68b069a5cd68d8e --- /dev/null +++ b/workspace/logs/monitor_h16_correct_14763341.err @@ -0,0 +1 @@ +[2026-06-26T23:41:48.028] error: *** JOB 14763341 ON rc31801 CANCELLED AT 2026-06-26T23:41:48 DUE TO TIME LIMIT *** diff --git a/workspace/logs/monitor_h16_correct_14763341.out b/workspace/logs/monitor_h16_correct_14763341.out new file mode 100644 index 0000000000000000000000000000000000000000..84caa4fc31f199acc4032b42882199310832f536 --- /dev/null +++ b/workspace/logs/monitor_h16_correct_14763341.out @@ -0,0 +1,77 @@ +=== Monitor for DoVLAModel h=16 Training === +Training job: 14763330 +Start: Fri 26 Jun 2026 07:41:23 AM EDT + +[07:41:23] Training status: RUNNING +[07:51:23] Training status: RUNNING +[08:01:23] Training status: RUNNING +[08:11:23] Training status: RUNNING +[08:21:23] Training status: RUNNING +[08:31:23] Training status: RUNNING +[08:41:23] Training status: RUNNING +[08:51:23] Training status: RUNNING +[09:01:23] Training status: RUNNING +[09:11:23] Training status: RUNNING +[09:21:23] Training status: RUNNING +[09:31:23] Training status: RUNNING +[09:41:23] Training status: RUNNING +[09:51:23] Training status: RUNNING +[10:01:23] Training status: RUNNING +[10:11:23] Training status: RUNNING +[10:21:23] Training status: RUNNING +[10:31:23] Training status: RUNNING +[10:41:23] Training status: RUNNING +[10:51:23] Training status: RUNNING +[11:01:24] Training status: RUNNING +[11:11:24] Training status: RUNNING +[11:21:24] Training status: RUNNING +[11:31:24] Training status: RUNNING +[11:41:24] Training status: RUNNING +[11:51:24] Training status: RUNNING +[12:01:24] Training status: RUNNING +[12:11:24] Training status: RUNNING +[12:21:24] Training status: RUNNING +[12:31:24] Training status: RUNNING +[12:41:24] Training status: RUNNING +[12:51:24] Training status: RUNNING +[13:01:24] Training status: RUNNING +[13:11:24] Training status: RUNNING +[13:21:24] Training status: RUNNING +[13:31:24] Training status: RUNNING +[13:41:24] Training status: RUNNING +[13:51:24] Training status: RUNNING +[14:01:24] Training status: RUNNING +[14:11:24] Training status: RUNNING +[14:21:24] Training status: RUNNING +[14:31:24] Training status: RUNNING +[14:41:24] Training status: RUNNING +[14:51:24] Training status: RUNNING +[15:01:24] Training status: RUNNING +[15:11:25] Training status: RUNNING +[15:21:25] Training status: RUNNING +[15:31:25] Training status: RUNNING +[15:41:25] Training status: RUNNING +[15:51:25] Training status: RUNNING +[16:01:25] Training status: RUNNING +[16:11:25] Training status: RUNNING +[16:21:25] Training status: RUNNING +[16:31:25] Training status: RUNNING +[16:41:25] Training status: RUNNING +[16:51:25] Training status: RUNNING +[17:01:25] Training status: RUNNING +[17:11:25] Training status: RUNNING +[17:21:25] Training status: RUNNING +[17:31:25] Training status: RUNNING +[17:41:28] Training status: RUNNING +[17:51:28] Training status: RUNNING +[18:01:28] Training status: RUNNING +[18:11:30] Training status: RUNNING +[18:21:30] Training status: RUNNING +[18:31:30] Training status: RUNNING +[18:41:30] Training status: RUNNING +[18:51:30] Training status: RUNNING +[19:01:30] Training status: RUNNING +[19:11:30] Training status: RUNNING +[19:21:30] Training status: RUNNING +[19:31:30] Training status: RUNNING +[19:41:30] Training status: RUNNING diff --git a/workspace/logs/paper_iterate_14759092.err b/workspace/logs/paper_iterate_14759092.err new file mode 100644 index 0000000000000000000000000000000000000000..e8eed1306c6c9f32fd2add13e03f129f5eb171bd --- /dev/null +++ b/workspace/logs/paper_iterate_14759092.err @@ -0,0 +1 @@ +[2026-06-26T11:35:13.006] error: *** JOB 14759092 ON rc32129 CANCELLED AT 2026-06-26T11:35:13 DUE to SIGNAL Terminated *** diff --git a/workspace/logs/paper_iterate_14759092.out b/workspace/logs/paper_iterate_14759092.out new file mode 100644 index 0000000000000000000000000000000000000000..69a1f90ac98a2f8c049ea245fd5a010ddf123219 --- /dev/null +++ b/workspace/logs/paper_iterate_14759092.out @@ -0,0 +1,79 @@ +=== Autonomous Paper Iteration Started === +Goal: Achieve A* quality (score ≥8/10) +Time: Fri 26 Jun 2026 12:20:40 AM EDT + +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) +================================================== +ITERATION 1 +================================================== + +⏳ Waiting for initial draft... (sleeping 30 min) diff --git a/workspace/logs/phase_a1_10k_gen_14661777.err b/workspace/logs/phase_a1_10k_gen_14661777.err new file mode 100644 index 0000000000000000000000000000000000000000..68d8efbb2b2d64e29486c88697eb26c6f5ffaa58 --- /dev/null +++ b/workspace/logs/phase_a1_10k_gen_14661777.err @@ -0,0 +1,15 @@ +usage: generate_maniskill_lattice.py [-h] --demo DEMO --out OUT + [--num-groups NUM_GROUPS] + [--group-offset GROUP_OFFSET] [--k K] + [--horizon HORIZON] [--seed SEED] + [--shard-size SHARD_SIZE] + [--env-id ENV_ID] [--obs-mode OBS_MODE] + [--image-quality IMAGE_QUALITY] + [--control-mode CONTROL_MODE] + [--sim-backend SIM_BACKEND] + [--render-backend RENDER_BACKEND] + [--parallel-branches | --no-parallel-branches] + [--state-storage {archive,files,none}] + [--state-batch-size STATE_BATCH_SIZE] + [--candidate-mode {structured,random}] +generate_maniskill_lattice.py: error: the following arguments are required: --demo diff --git a/workspace/logs/phase_a1_10k_gen_14661777.out b/workspace/logs/phase_a1_10k_gen_14661777.out new file mode 100644 index 0000000000000000000000000000000000000000..791919ddb8fcd70fee5f05a423e0b5ea176eeb0c --- /dev/null +++ b/workspace/logs/phase_a1_10k_gen_14661777.out @@ -0,0 +1,22 @@ += = = = = = = = = = = = = = = = = = +Phase A1: Enhanced 10K Generation for 50%+ Target += = = = = = = = = = = = = = = = = = + +Strategy: + - 10,000 groups (vs 3,500 current) + - 160,000 records total + - K=16 interventions per group + - Optimized for diverse counterfactuals + +Expected outcome: 42-50% policy success + +Task distribution (total: 10000 groups): + PushCube-v1: 1800 groups + LiftPegUpright-v1: 1600 groups + PullCube-v1: 1600 groups + StackCube-v1: 1600 groups + PickCube-v1: 1800 groups + PegInsertionSide-v1: 1600 groups + +Generating PushCube-v1: 1800 groups... + Start: Wed 24 Jun 2026 02:30:40 AM EDT diff --git a/workspace/logs/phase_a1_enhanced_single_14662616_0.err b/workspace/logs/phase_a1_enhanced_single_14662616_0.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/phase_a1_enhanced_single_14662616_0.out b/workspace/logs/phase_a1_enhanced_single_14662616_0.out new file mode 100644 index 0000000000000000000000000000000000000000..a3d194c4dd334a2fc6f68fe68638cc59aa081b01 --- /dev/null +++ b/workspace/logs/phase_a1_enhanced_single_14662616_0.out @@ -0,0 +1,218 @@ += = = = = = = = = = = = = = = = = = +Phase A1-Revised: Enhanced Training (Existing Data) += = = = = = = = = = = = = = = = = = + +Strategy: Better training, not more data +Seed: 0 +Dataset: 3,500 groups (existing) +Model: h=256 (best from Phase A4) +Training: 200 epochs with cosine schedule + +Target: 45%+ policy success + +epoch=1 train_loss=1.3539 val_loss=1.2258 val_rank_acc=0.800 val_progress_mae=0.238 +epoch=2 train_loss=1.2227 val_loss=1.2158 val_rank_acc=0.811 val_progress_mae=0.238 +epoch=3 train_loss=1.1928 val_loss=1.1548 val_rank_acc=0.819 val_progress_mae=0.232 +epoch=4 train_loss=1.1832 val_loss=1.1508 val_rank_acc=0.811 val_progress_mae=0.236 +epoch=5 train_loss=1.1648 val_loss=1.1622 val_rank_acc=0.816 val_progress_mae=0.232 +epoch=6 train_loss=1.1664 val_loss=1.1693 val_rank_acc=0.817 val_progress_mae=0.239 +epoch=7 train_loss=1.1322 val_loss=1.1476 val_rank_acc=0.822 val_progress_mae=0.229 +epoch=8 train_loss=1.1315 val_loss=1.1461 val_rank_acc=0.820 val_progress_mae=0.232 +epoch=9 train_loss=1.1199 val_loss=1.1231 val_rank_acc=0.827 val_progress_mae=0.228 +epoch=10 train_loss=1.1124 val_loss=1.1388 val_rank_acc=0.822 val_progress_mae=0.233 +epoch=11 train_loss=1.1200 val_loss=1.1387 val_rank_acc=0.825 val_progress_mae=0.232 +epoch=12 train_loss=1.1168 val_loss=1.1337 val_rank_acc=0.829 val_progress_mae=0.230 +epoch=13 train_loss=1.1054 val_loss=1.1354 val_rank_acc=0.827 val_progress_mae=0.229 +epoch=14 train_loss=1.1020 val_loss=1.1337 val_rank_acc=0.825 val_progress_mae=0.231 +epoch=15 train_loss=1.0941 val_loss=1.1059 val_rank_acc=0.835 val_progress_mae=0.228 +epoch=16 train_loss=1.0894 val_loss=1.1324 val_rank_acc=0.832 val_progress_mae=0.232 +epoch=17 train_loss=1.0816 val_loss=1.1211 val_rank_acc=0.835 val_progress_mae=0.232 +epoch=18 train_loss=1.0852 val_loss=1.1146 val_rank_acc=0.829 val_progress_mae=0.231 +epoch=19 train_loss=1.0792 val_loss=1.1286 val_rank_acc=0.828 val_progress_mae=0.223 +epoch=20 train_loss=1.0786 val_loss=1.1572 val_rank_acc=0.831 val_progress_mae=0.233 +epoch=21 train_loss=1.0618 val_loss=1.1453 val_rank_acc=0.831 val_progress_mae=0.227 +epoch=22 train_loss=1.0520 val_loss=1.1241 val_rank_acc=0.833 val_progress_mae=0.231 +epoch=23 train_loss=1.0690 val_loss=1.1116 val_rank_acc=0.836 val_progress_mae=0.232 +epoch=24 train_loss=1.0568 val_loss=1.1035 val_rank_acc=0.830 val_progress_mae=0.227 +epoch=25 train_loss=1.0636 val_loss=1.1151 val_rank_acc=0.827 val_progress_mae=0.229 +epoch=26 train_loss=1.0527 val_loss=1.1115 val_rank_acc=0.836 val_progress_mae=0.231 +epoch=27 train_loss=1.0435 val_loss=1.1283 val_rank_acc=0.831 val_progress_mae=0.223 +epoch=28 train_loss=1.0401 val_loss=1.1271 val_rank_acc=0.834 val_progress_mae=0.225 +epoch=29 train_loss=1.0374 val_loss=1.1389 val_rank_acc=0.830 val_progress_mae=0.231 +epoch=30 train_loss=1.0208 val_loss=1.1153 val_rank_acc=0.830 val_progress_mae=0.225 +epoch=31 train_loss=1.0235 val_loss=1.1131 val_rank_acc=0.837 val_progress_mae=0.234 +epoch=32 train_loss=1.0163 val_loss=1.1078 val_rank_acc=0.829 val_progress_mae=0.236 +epoch=33 train_loss=1.0281 val_loss=1.1486 val_rank_acc=0.818 val_progress_mae=0.232 +epoch=34 train_loss=1.0127 val_loss=1.1160 val_rank_acc=0.829 val_progress_mae=0.228 +epoch=35 train_loss=1.0252 val_loss=1.1283 val_rank_acc=0.834 val_progress_mae=0.226 +epoch=36 train_loss=1.0153 val_loss=1.1144 val_rank_acc=0.830 val_progress_mae=0.229 +epoch=37 train_loss=1.0068 val_loss=1.1153 val_rank_acc=0.832 val_progress_mae=0.227 +epoch=38 train_loss=1.0102 val_loss=1.1199 val_rank_acc=0.828 val_progress_mae=0.229 +epoch=39 train_loss=0.9965 val_loss=1.0944 val_rank_acc=0.834 val_progress_mae=0.228 +epoch=40 train_loss=1.0010 val_loss=1.1265 val_rank_acc=0.830 val_progress_mae=0.224 +epoch=41 train_loss=1.0075 val_loss=1.1132 val_rank_acc=0.834 val_progress_mae=0.223 +epoch=42 train_loss=0.9954 val_loss=1.1212 val_rank_acc=0.821 val_progress_mae=0.225 +epoch=43 train_loss=0.9940 val_loss=1.1169 val_rank_acc=0.823 val_progress_mae=0.224 +epoch=44 train_loss=0.9914 val_loss=1.1472 val_rank_acc=0.827 val_progress_mae=0.230 +epoch=45 train_loss=0.9874 val_loss=1.1245 val_rank_acc=0.827 val_progress_mae=0.233 +epoch=46 train_loss=0.9808 val_loss=1.1442 val_rank_acc=0.826 val_progress_mae=0.227 +epoch=47 train_loss=0.9889 val_loss=1.1139 val_rank_acc=0.828 val_progress_mae=0.225 +epoch=48 train_loss=0.9823 val_loss=1.1271 val_rank_acc=0.831 val_progress_mae=0.228 +epoch=49 train_loss=0.9726 val_loss=1.1128 val_rank_acc=0.833 val_progress_mae=0.231 +epoch=50 train_loss=0.9693 val_loss=1.1504 val_rank_acc=0.829 val_progress_mae=0.226 +epoch=51 train_loss=0.9686 val_loss=1.1333 val_rank_acc=0.826 val_progress_mae=0.228 +epoch=52 train_loss=0.9571 val_loss=1.1410 val_rank_acc=0.831 val_progress_mae=0.225 +epoch=53 train_loss=0.9569 val_loss=1.1368 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=54 train_loss=0.9669 val_loss=1.1457 val_rank_acc=0.833 val_progress_mae=0.230 +epoch=55 train_loss=0.9700 val_loss=1.1422 val_rank_acc=0.827 val_progress_mae=0.230 +epoch=56 train_loss=0.9627 val_loss=1.1617 val_rank_acc=0.823 val_progress_mae=0.228 +epoch=57 train_loss=0.9522 val_loss=1.1393 val_rank_acc=0.829 val_progress_mae=0.228 +epoch=58 train_loss=0.9614 val_loss=1.1363 val_rank_acc=0.830 val_progress_mae=0.228 +epoch=59 train_loss=0.9528 val_loss=1.1413 val_rank_acc=0.829 val_progress_mae=0.225 +epoch=60 train_loss=0.9439 val_loss=1.1411 val_rank_acc=0.819 val_progress_mae=0.225 +epoch=61 train_loss=0.9401 val_loss=1.1432 val_rank_acc=0.827 val_progress_mae=0.231 +epoch=62 train_loss=0.9485 val_loss=1.1512 val_rank_acc=0.827 val_progress_mae=0.229 +epoch=63 train_loss=0.9322 val_loss=1.1634 val_rank_acc=0.823 val_progress_mae=0.225 +epoch=64 train_loss=0.9392 val_loss=1.1554 val_rank_acc=0.827 val_progress_mae=0.228 +epoch=65 train_loss=0.9329 val_loss=1.1486 val_rank_acc=0.827 val_progress_mae=0.228 +epoch=66 train_loss=0.9346 val_loss=1.1494 val_rank_acc=0.828 val_progress_mae=0.231 +epoch=67 train_loss=0.9252 val_loss=1.1657 val_rank_acc=0.828 val_progress_mae=0.224 +epoch=68 train_loss=0.9198 val_loss=1.1692 val_rank_acc=0.825 val_progress_mae=0.228 +epoch=69 train_loss=0.9204 val_loss=1.1297 val_rank_acc=0.824 val_progress_mae=0.228 +epoch=70 train_loss=0.9135 val_loss=1.1658 val_rank_acc=0.823 val_progress_mae=0.226 +epoch=71 train_loss=0.9305 val_loss=1.1779 val_rank_acc=0.821 val_progress_mae=0.231 +epoch=72 train_loss=0.9297 val_loss=1.1442 val_rank_acc=0.829 val_progress_mae=0.228 +epoch=73 train_loss=0.9323 val_loss=1.1584 val_rank_acc=0.822 val_progress_mae=0.227 +epoch=74 train_loss=0.9202 val_loss=1.1757 val_rank_acc=0.829 val_progress_mae=0.231 +epoch=75 train_loss=0.9172 val_loss=1.1645 val_rank_acc=0.824 val_progress_mae=0.230 +epoch=76 train_loss=0.9051 val_loss=1.1519 val_rank_acc=0.830 val_progress_mae=0.229 +epoch=77 train_loss=0.9124 val_loss=1.1457 val_rank_acc=0.829 val_progress_mae=0.228 +epoch=78 train_loss=0.9090 val_loss=1.1795 val_rank_acc=0.819 val_progress_mae=0.225 +epoch=79 train_loss=0.9082 val_loss=1.1566 val_rank_acc=0.827 val_progress_mae=0.230 +epoch=80 train_loss=0.8981 val_loss=1.1751 val_rank_acc=0.824 val_progress_mae=0.228 +epoch=81 train_loss=0.8998 val_loss=1.1651 val_rank_acc=0.827 val_progress_mae=0.226 +epoch=82 train_loss=0.8997 val_loss=1.1476 val_rank_acc=0.828 val_progress_mae=0.229 +epoch=83 train_loss=0.9109 val_loss=1.2000 val_rank_acc=0.828 val_progress_mae=0.230 +epoch=84 train_loss=0.9051 val_loss=1.1931 val_rank_acc=0.816 val_progress_mae=0.235 +epoch=85 train_loss=0.8964 val_loss=1.2036 val_rank_acc=0.816 val_progress_mae=0.231 +epoch=86 train_loss=0.8883 val_loss=1.1860 val_rank_acc=0.817 val_progress_mae=0.228 +epoch=87 train_loss=0.9111 val_loss=1.1820 val_rank_acc=0.819 val_progress_mae=0.229 +epoch=88 train_loss=0.8918 val_loss=1.2035 val_rank_acc=0.819 val_progress_mae=0.227 +epoch=89 train_loss=0.8952 val_loss=1.1769 val_rank_acc=0.820 val_progress_mae=0.223 +epoch=90 train_loss=0.8925 val_loss=1.1736 val_rank_acc=0.819 val_progress_mae=0.234 +epoch=91 train_loss=0.8803 val_loss=1.1843 val_rank_acc=0.817 val_progress_mae=0.227 +epoch=92 train_loss=0.8770 val_loss=1.1828 val_rank_acc=0.822 val_progress_mae=0.232 +epoch=93 train_loss=0.8776 val_loss=1.1747 val_rank_acc=0.819 val_progress_mae=0.228 +epoch=94 train_loss=0.8963 val_loss=1.1875 val_rank_acc=0.821 val_progress_mae=0.229 +epoch=95 train_loss=0.8993 val_loss=1.1744 val_rank_acc=0.816 val_progress_mae=0.233 +epoch=96 train_loss=0.8814 val_loss=1.1741 val_rank_acc=0.817 val_progress_mae=0.233 +epoch=97 train_loss=0.8877 val_loss=1.1656 val_rank_acc=0.817 val_progress_mae=0.229 +epoch=98 train_loss=0.8824 val_loss=1.1898 val_rank_acc=0.818 val_progress_mae=0.229 +epoch=99 train_loss=0.8833 val_loss=1.1640 val_rank_acc=0.817 val_progress_mae=0.230 +epoch=100 train_loss=0.8814 val_loss=1.1635 val_rank_acc=0.811 val_progress_mae=0.230 +epoch=101 train_loss=0.8754 val_loss=1.1655 val_rank_acc=0.815 val_progress_mae=0.229 +epoch=102 train_loss=0.8750 val_loss=1.1807 val_rank_acc=0.815 val_progress_mae=0.227 +epoch=103 train_loss=0.8840 val_loss=1.1977 val_rank_acc=0.821 val_progress_mae=0.234 +epoch=104 train_loss=0.8642 val_loss=1.1830 val_rank_acc=0.819 val_progress_mae=0.229 +epoch=105 train_loss=0.8556 val_loss=1.1914 val_rank_acc=0.807 val_progress_mae=0.233 +epoch=106 train_loss=0.8613 val_loss=1.1948 val_rank_acc=0.815 val_progress_mae=0.227 +epoch=107 train_loss=0.8654 val_loss=1.1867 val_rank_acc=0.820 val_progress_mae=0.229 +epoch=108 train_loss=0.8601 val_loss=1.1711 val_rank_acc=0.812 val_progress_mae=0.231 +epoch=109 train_loss=0.8596 val_loss=1.1818 val_rank_acc=0.811 val_progress_mae=0.229 +epoch=110 train_loss=0.8630 val_loss=1.1836 val_rank_acc=0.818 val_progress_mae=0.231 +epoch=111 train_loss=0.8593 val_loss=1.2012 val_rank_acc=0.810 val_progress_mae=0.233 +epoch=112 train_loss=0.8554 val_loss=1.1905 val_rank_acc=0.811 val_progress_mae=0.229 +epoch=113 train_loss=0.8512 val_loss=1.2067 val_rank_acc=0.815 val_progress_mae=0.229 +epoch=114 train_loss=0.8627 val_loss=1.1900 val_rank_acc=0.815 val_progress_mae=0.229 +epoch=115 train_loss=0.8575 val_loss=1.2025 val_rank_acc=0.810 val_progress_mae=0.228 +epoch=116 train_loss=0.8595 val_loss=1.1856 val_rank_acc=0.821 val_progress_mae=0.228 +epoch=117 train_loss=0.8469 val_loss=1.1997 val_rank_acc=0.812 val_progress_mae=0.232 +epoch=118 train_loss=0.8553 val_loss=1.1890 val_rank_acc=0.818 val_progress_mae=0.227 +epoch=119 train_loss=0.8579 val_loss=1.1763 val_rank_acc=0.813 val_progress_mae=0.226 +epoch=120 train_loss=0.8608 val_loss=1.1866 val_rank_acc=0.809 val_progress_mae=0.227 +epoch=121 train_loss=0.8571 val_loss=1.1758 val_rank_acc=0.820 val_progress_mae=0.228 +epoch=122 train_loss=0.8467 val_loss=1.1895 val_rank_acc=0.819 val_progress_mae=0.233 +epoch=123 train_loss=0.8417 val_loss=1.2082 val_rank_acc=0.804 val_progress_mae=0.228 +epoch=124 train_loss=0.8563 val_loss=1.1811 val_rank_acc=0.813 val_progress_mae=0.230 +epoch=125 train_loss=0.8466 val_loss=1.1950 val_rank_acc=0.805 val_progress_mae=0.232 +epoch=126 train_loss=0.8360 val_loss=1.1940 val_rank_acc=0.814 val_progress_mae=0.225 +epoch=127 train_loss=0.8685 val_loss=1.1919 val_rank_acc=0.812 val_progress_mae=0.230 +epoch=128 train_loss=0.8495 val_loss=1.1705 val_rank_acc=0.814 val_progress_mae=0.227 +epoch=129 train_loss=0.8567 val_loss=1.1904 val_rank_acc=0.814 val_progress_mae=0.232 +epoch=130 train_loss=0.8332 val_loss=1.1851 val_rank_acc=0.799 val_progress_mae=0.226 +epoch=131 train_loss=0.8514 val_loss=1.2130 val_rank_acc=0.813 val_progress_mae=0.233 +epoch=132 train_loss=0.8236 val_loss=1.1699 val_rank_acc=0.811 val_progress_mae=0.227 +epoch=133 train_loss=0.8431 val_loss=1.1841 val_rank_acc=0.815 val_progress_mae=0.232 +epoch=134 train_loss=0.8420 val_loss=1.1918 val_rank_acc=0.812 val_progress_mae=0.226 +epoch=135 train_loss=0.8525 val_loss=1.1786 val_rank_acc=0.814 val_progress_mae=0.226 +epoch=136 train_loss=0.8361 val_loss=1.1738 val_rank_acc=0.815 val_progress_mae=0.225 +epoch=137 train_loss=0.8359 val_loss=1.2145 val_rank_acc=0.806 val_progress_mae=0.227 +epoch=138 train_loss=0.8380 val_loss=1.1707 val_rank_acc=0.812 val_progress_mae=0.225 +epoch=139 train_loss=0.8386 val_loss=1.1979 val_rank_acc=0.807 val_progress_mae=0.230 +epoch=140 train_loss=0.8402 val_loss=1.1887 val_rank_acc=0.807 val_progress_mae=0.231 +epoch=141 train_loss=0.8338 val_loss=1.1986 val_rank_acc=0.811 val_progress_mae=0.230 +epoch=142 train_loss=0.8503 val_loss=1.1711 val_rank_acc=0.809 val_progress_mae=0.229 +epoch=143 train_loss=0.8452 val_loss=1.2159 val_rank_acc=0.810 val_progress_mae=0.230 +epoch=144 train_loss=0.8273 val_loss=1.2011 val_rank_acc=0.817 val_progress_mae=0.229 +epoch=145 train_loss=0.8367 val_loss=1.2049 val_rank_acc=0.810 val_progress_mae=0.231 +epoch=146 train_loss=0.8410 val_loss=1.1920 val_rank_acc=0.807 val_progress_mae=0.226 +epoch=147 train_loss=0.8415 val_loss=1.1952 val_rank_acc=0.806 val_progress_mae=0.230 +epoch=148 train_loss=0.8317 val_loss=1.1879 val_rank_acc=0.809 val_progress_mae=0.231 +epoch=149 train_loss=0.8303 val_loss=1.1808 val_rank_acc=0.810 val_progress_mae=0.231 +epoch=150 train_loss=0.8366 val_loss=1.1946 val_rank_acc=0.810 val_progress_mae=0.228 +epoch=151 train_loss=0.8389 val_loss=1.1953 val_rank_acc=0.803 val_progress_mae=0.231 +epoch=152 train_loss=0.8295 val_loss=1.1991 val_rank_acc=0.809 val_progress_mae=0.229 +epoch=153 train_loss=0.8403 val_loss=1.2003 val_rank_acc=0.814 val_progress_mae=0.230 +epoch=154 train_loss=0.8267 val_loss=1.1645 val_rank_acc=0.816 val_progress_mae=0.229 +epoch=155 train_loss=0.8491 val_loss=1.1810 val_rank_acc=0.808 val_progress_mae=0.229 +epoch=156 train_loss=0.8248 val_loss=1.1979 val_rank_acc=0.809 val_progress_mae=0.228 +epoch=157 train_loss=0.8361 val_loss=1.1961 val_rank_acc=0.812 val_progress_mae=0.230 +epoch=158 train_loss=0.8410 val_loss=1.1856 val_rank_acc=0.808 val_progress_mae=0.229 +epoch=159 train_loss=0.8261 val_loss=1.1741 val_rank_acc=0.812 val_progress_mae=0.226 +epoch=160 train_loss=0.8213 val_loss=1.1958 val_rank_acc=0.813 val_progress_mae=0.228 +epoch=161 train_loss=0.8333 val_loss=1.2065 val_rank_acc=0.815 val_progress_mae=0.231 +epoch=162 train_loss=0.8335 val_loss=1.1804 val_rank_acc=0.808 val_progress_mae=0.228 +epoch=163 train_loss=0.8140 val_loss=1.2241 val_rank_acc=0.816 val_progress_mae=0.231 +epoch=164 train_loss=0.8192 val_loss=1.2000 val_rank_acc=0.801 val_progress_mae=0.234 +epoch=165 train_loss=0.8329 val_loss=1.1963 val_rank_acc=0.806 val_progress_mae=0.228 +epoch=166 train_loss=0.8320 val_loss=1.1830 val_rank_acc=0.815 val_progress_mae=0.235 +epoch=167 train_loss=0.8173 val_loss=1.2117 val_rank_acc=0.811 val_progress_mae=0.232 +epoch=168 train_loss=0.8181 val_loss=1.1727 val_rank_acc=0.818 val_progress_mae=0.232 +epoch=169 train_loss=0.8249 val_loss=1.1846 val_rank_acc=0.806 val_progress_mae=0.227 +epoch=170 train_loss=0.8359 val_loss=1.1894 val_rank_acc=0.811 val_progress_mae=0.225 +epoch=171 train_loss=0.8247 val_loss=1.1952 val_rank_acc=0.807 val_progress_mae=0.228 +epoch=172 train_loss=0.8269 val_loss=1.2105 val_rank_acc=0.809 val_progress_mae=0.225 +epoch=173 train_loss=0.8081 val_loss=1.2007 val_rank_acc=0.813 val_progress_mae=0.231 +epoch=174 train_loss=0.8198 val_loss=1.1986 val_rank_acc=0.809 val_progress_mae=0.231 +epoch=175 train_loss=0.8190 val_loss=1.1831 val_rank_acc=0.805 val_progress_mae=0.228 +epoch=176 train_loss=0.8178 val_loss=1.2043 val_rank_acc=0.807 val_progress_mae=0.227 +epoch=177 train_loss=0.8115 val_loss=1.1993 val_rank_acc=0.804 val_progress_mae=0.233 +epoch=178 train_loss=0.8171 val_loss=1.1935 val_rank_acc=0.817 val_progress_mae=0.229 +epoch=179 train_loss=0.8281 val_loss=1.1936 val_rank_acc=0.805 val_progress_mae=0.226 +epoch=180 train_loss=0.8176 val_loss=1.1679 val_rank_acc=0.811 val_progress_mae=0.234 +epoch=181 train_loss=0.8099 val_loss=1.1979 val_rank_acc=0.816 val_progress_mae=0.230 +epoch=182 train_loss=0.8251 val_loss=1.2337 val_rank_acc=0.798 val_progress_mae=0.226 +epoch=183 train_loss=0.8150 val_loss=1.1912 val_rank_acc=0.805 val_progress_mae=0.227 +epoch=184 train_loss=0.8198 val_loss=1.2084 val_rank_acc=0.808 val_progress_mae=0.230 +epoch=185 train_loss=0.8247 val_loss=1.2140 val_rank_acc=0.809 val_progress_mae=0.231 +epoch=186 train_loss=0.8126 val_loss=1.1800 val_rank_acc=0.812 val_progress_mae=0.230 +epoch=187 train_loss=0.8133 val_loss=1.1925 val_rank_acc=0.810 val_progress_mae=0.227 +epoch=188 train_loss=0.8177 val_loss=1.1991 val_rank_acc=0.808 val_progress_mae=0.231 +epoch=189 train_loss=0.8149 val_loss=1.1972 val_rank_acc=0.804 val_progress_mae=0.230 +epoch=190 train_loss=0.8132 val_loss=1.1957 val_rank_acc=0.807 val_progress_mae=0.227 +epoch=191 train_loss=0.8281 val_loss=1.2042 val_rank_acc=0.803 val_progress_mae=0.228 +epoch=192 train_loss=0.8044 val_loss=1.1770 val_rank_acc=0.809 val_progress_mae=0.225 +epoch=193 train_loss=0.8059 val_loss=1.2211 val_rank_acc=0.807 val_progress_mae=0.227 +epoch=194 train_loss=0.8255 val_loss=1.1831 val_rank_acc=0.804 val_progress_mae=0.226 +epoch=195 train_loss=0.8177 val_loss=1.1765 val_rank_acc=0.811 val_progress_mae=0.227 +epoch=196 train_loss=0.8078 val_loss=1.1986 val_rank_acc=0.800 val_progress_mae=0.229 +epoch=197 train_loss=0.8156 val_loss=1.2031 val_rank_acc=0.809 val_progress_mae=0.229 +epoch=198 train_loss=0.8034 val_loss=1.2084 val_rank_acc=0.805 val_progress_mae=0.226 +epoch=199 train_loss=0.8116 val_loss=1.2200 val_rank_acc=0.805 val_progress_mae=0.230 +epoch=200 train_loss=0.8226 val_loss=1.1966 val_rank_acc=0.808 val_progress_mae=0.227 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a1_revised_enhanced/seed_0 +best val rank_acc=0.8367 + +✅ Phase A1-Revised enhanced training complete (seed 0) + +Next: Evaluate and check if 45%+ achieved diff --git a/workspace/logs/phase_a1_enhanced_single_14662616_1.err b/workspace/logs/phase_a1_enhanced_single_14662616_1.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/phase_a1_enhanced_single_14662616_1.out b/workspace/logs/phase_a1_enhanced_single_14662616_1.out new file mode 100644 index 0000000000000000000000000000000000000000..9d79065ea43794d7ac59c7a6d9b26a96aa997c67 --- /dev/null +++ b/workspace/logs/phase_a1_enhanced_single_14662616_1.out @@ -0,0 +1,218 @@ += = = = = = = = = = = = = = = = = = +Phase A1-Revised: Enhanced Training (Existing Data) += = = = = = = = = = = = = = = = = = + +Strategy: Better training, not more data +Seed: 1 +Dataset: 3,500 groups (existing) +Model: h=256 (best from Phase A4) +Training: 200 epochs with cosine schedule + +Target: 45%+ policy success + +epoch=1 train_loss=1.3479 val_loss=1.2650 val_rank_acc=0.798 val_progress_mae=0.240 +epoch=2 train_loss=1.2174 val_loss=1.2125 val_rank_acc=0.810 val_progress_mae=0.248 +epoch=3 train_loss=1.1828 val_loss=1.2217 val_rank_acc=0.815 val_progress_mae=0.254 +epoch=4 train_loss=1.1764 val_loss=1.1751 val_rank_acc=0.796 val_progress_mae=0.240 +epoch=5 train_loss=1.1612 val_loss=1.1522 val_rank_acc=0.817 val_progress_mae=0.233 +epoch=6 train_loss=1.1513 val_loss=1.1732 val_rank_acc=0.809 val_progress_mae=0.241 +epoch=7 train_loss=1.1498 val_loss=1.1605 val_rank_acc=0.814 val_progress_mae=0.239 +epoch=8 train_loss=1.1359 val_loss=1.1638 val_rank_acc=0.822 val_progress_mae=0.243 +epoch=9 train_loss=1.1260 val_loss=1.1849 val_rank_acc=0.828 val_progress_mae=0.238 +epoch=10 train_loss=1.1119 val_loss=1.1651 val_rank_acc=0.825 val_progress_mae=0.247 +epoch=11 train_loss=1.1133 val_loss=1.1540 val_rank_acc=0.825 val_progress_mae=0.250 +epoch=12 train_loss=1.1041 val_loss=1.1556 val_rank_acc=0.824 val_progress_mae=0.254 +epoch=13 train_loss=1.1010 val_loss=1.1722 val_rank_acc=0.825 val_progress_mae=0.246 +epoch=14 train_loss=1.0866 val_loss=1.1558 val_rank_acc=0.825 val_progress_mae=0.239 +epoch=15 train_loss=1.0962 val_loss=1.1096 val_rank_acc=0.831 val_progress_mae=0.234 +epoch=16 train_loss=1.0616 val_loss=1.1310 val_rank_acc=0.827 val_progress_mae=0.243 +epoch=17 train_loss=1.0841 val_loss=1.1455 val_rank_acc=0.832 val_progress_mae=0.248 +epoch=18 train_loss=1.0753 val_loss=1.0994 val_rank_acc=0.836 val_progress_mae=0.242 +epoch=19 train_loss=1.0760 val_loss=1.1251 val_rank_acc=0.829 val_progress_mae=0.225 +epoch=20 train_loss=1.0667 val_loss=1.1561 val_rank_acc=0.833 val_progress_mae=0.245 +epoch=21 train_loss=1.0679 val_loss=1.1229 val_rank_acc=0.829 val_progress_mae=0.242 +epoch=22 train_loss=1.0515 val_loss=1.1221 val_rank_acc=0.833 val_progress_mae=0.241 +epoch=23 train_loss=1.0535 val_loss=1.1496 val_rank_acc=0.831 val_progress_mae=0.238 +epoch=24 train_loss=1.0511 val_loss=1.1334 val_rank_acc=0.827 val_progress_mae=0.242 +epoch=25 train_loss=1.0539 val_loss=1.1405 val_rank_acc=0.819 val_progress_mae=0.243 +epoch=26 train_loss=1.0240 val_loss=1.1238 val_rank_acc=0.830 val_progress_mae=0.245 +epoch=27 train_loss=1.0333 val_loss=1.1283 val_rank_acc=0.830 val_progress_mae=0.242 +epoch=28 train_loss=1.0281 val_loss=1.1326 val_rank_acc=0.828 val_progress_mae=0.234 +epoch=29 train_loss=1.0382 val_loss=1.1376 val_rank_acc=0.832 val_progress_mae=0.244 +epoch=30 train_loss=1.0310 val_loss=1.1416 val_rank_acc=0.829 val_progress_mae=0.238 +epoch=31 train_loss=1.0294 val_loss=1.1078 val_rank_acc=0.828 val_progress_mae=0.242 +epoch=32 train_loss=1.0226 val_loss=1.1143 val_rank_acc=0.832 val_progress_mae=0.236 +epoch=33 train_loss=1.0163 val_loss=1.1477 val_rank_acc=0.831 val_progress_mae=0.241 +epoch=34 train_loss=1.0113 val_loss=1.1539 val_rank_acc=0.822 val_progress_mae=0.236 +epoch=35 train_loss=1.0172 val_loss=1.1387 val_rank_acc=0.828 val_progress_mae=0.246 +epoch=36 train_loss=1.0153 val_loss=1.1222 val_rank_acc=0.829 val_progress_mae=0.236 +epoch=37 train_loss=1.0101 val_loss=1.1105 val_rank_acc=0.832 val_progress_mae=0.239 +epoch=38 train_loss=1.0056 val_loss=1.1419 val_rank_acc=0.827 val_progress_mae=0.238 +epoch=39 train_loss=1.0030 val_loss=1.1408 val_rank_acc=0.824 val_progress_mae=0.244 +epoch=40 train_loss=1.0021 val_loss=1.1413 val_rank_acc=0.829 val_progress_mae=0.241 +epoch=41 train_loss=0.9941 val_loss=1.1461 val_rank_acc=0.826 val_progress_mae=0.245 +epoch=42 train_loss=1.0004 val_loss=1.1586 val_rank_acc=0.827 val_progress_mae=0.239 +epoch=43 train_loss=0.9984 val_loss=1.1223 val_rank_acc=0.828 val_progress_mae=0.240 +epoch=44 train_loss=0.9806 val_loss=1.1450 val_rank_acc=0.828 val_progress_mae=0.238 +epoch=45 train_loss=0.9844 val_loss=1.1364 val_rank_acc=0.828 val_progress_mae=0.235 +epoch=46 train_loss=0.9832 val_loss=1.1315 val_rank_acc=0.833 val_progress_mae=0.244 +epoch=47 train_loss=0.9946 val_loss=1.1162 val_rank_acc=0.831 val_progress_mae=0.237 +epoch=48 train_loss=0.9797 val_loss=1.1418 val_rank_acc=0.831 val_progress_mae=0.237 +epoch=49 train_loss=0.9764 val_loss=1.1308 val_rank_acc=0.831 val_progress_mae=0.243 +epoch=50 train_loss=0.9634 val_loss=1.1207 val_rank_acc=0.833 val_progress_mae=0.241 +epoch=51 train_loss=0.9709 val_loss=1.1370 val_rank_acc=0.831 val_progress_mae=0.249 +epoch=52 train_loss=0.9579 val_loss=1.1342 val_rank_acc=0.830 val_progress_mae=0.238 +epoch=53 train_loss=0.9682 val_loss=1.1543 val_rank_acc=0.824 val_progress_mae=0.237 +epoch=54 train_loss=0.9595 val_loss=1.1481 val_rank_acc=0.826 val_progress_mae=0.241 +epoch=55 train_loss=0.9663 val_loss=1.1320 val_rank_acc=0.827 val_progress_mae=0.241 +epoch=56 train_loss=0.9441 val_loss=1.1411 val_rank_acc=0.824 val_progress_mae=0.239 +epoch=57 train_loss=0.9452 val_loss=1.1469 val_rank_acc=0.827 val_progress_mae=0.235 +epoch=58 train_loss=0.9527 val_loss=1.1354 val_rank_acc=0.826 val_progress_mae=0.234 +epoch=59 train_loss=0.9546 val_loss=1.1684 val_rank_acc=0.828 val_progress_mae=0.247 +epoch=60 train_loss=0.9338 val_loss=1.1313 val_rank_acc=0.828 val_progress_mae=0.236 +epoch=61 train_loss=0.9417 val_loss=1.1534 val_rank_acc=0.820 val_progress_mae=0.240 +epoch=62 train_loss=0.9397 val_loss=1.1311 val_rank_acc=0.828 val_progress_mae=0.239 +epoch=63 train_loss=0.9438 val_loss=1.1692 val_rank_acc=0.816 val_progress_mae=0.239 +epoch=64 train_loss=0.9400 val_loss=1.1337 val_rank_acc=0.826 val_progress_mae=0.239 +epoch=65 train_loss=0.9451 val_loss=1.1596 val_rank_acc=0.823 val_progress_mae=0.234 +epoch=66 train_loss=0.9272 val_loss=1.1501 val_rank_acc=0.823 val_progress_mae=0.240 +epoch=67 train_loss=0.9352 val_loss=1.1300 val_rank_acc=0.826 val_progress_mae=0.242 +epoch=68 train_loss=0.9082 val_loss=1.1520 val_rank_acc=0.819 val_progress_mae=0.238 +epoch=69 train_loss=0.9206 val_loss=1.1102 val_rank_acc=0.823 val_progress_mae=0.241 +epoch=70 train_loss=0.9113 val_loss=1.1774 val_rank_acc=0.821 val_progress_mae=0.243 +epoch=71 train_loss=0.9293 val_loss=1.1556 val_rank_acc=0.813 val_progress_mae=0.243 +epoch=72 train_loss=0.9129 val_loss=1.1328 val_rank_acc=0.826 val_progress_mae=0.244 +epoch=73 train_loss=0.9269 val_loss=1.1579 val_rank_acc=0.820 val_progress_mae=0.242 +epoch=74 train_loss=0.9183 val_loss=1.1387 val_rank_acc=0.820 val_progress_mae=0.238 +epoch=75 train_loss=0.9133 val_loss=1.1409 val_rank_acc=0.820 val_progress_mae=0.242 +epoch=76 train_loss=0.9038 val_loss=1.1441 val_rank_acc=0.825 val_progress_mae=0.243 +epoch=77 train_loss=0.9085 val_loss=1.1387 val_rank_acc=0.819 val_progress_mae=0.243 +epoch=78 train_loss=0.9152 val_loss=1.1283 val_rank_acc=0.828 val_progress_mae=0.239 +epoch=79 train_loss=0.9134 val_loss=1.1408 val_rank_acc=0.822 val_progress_mae=0.242 +epoch=80 train_loss=0.9103 val_loss=1.1806 val_rank_acc=0.817 val_progress_mae=0.240 +epoch=81 train_loss=0.9048 val_loss=1.1693 val_rank_acc=0.824 val_progress_mae=0.243 +epoch=82 train_loss=0.8942 val_loss=1.1428 val_rank_acc=0.821 val_progress_mae=0.241 +epoch=83 train_loss=0.9055 val_loss=1.1716 val_rank_acc=0.820 val_progress_mae=0.239 +epoch=84 train_loss=0.9083 val_loss=1.1722 val_rank_acc=0.821 val_progress_mae=0.239 +epoch=85 train_loss=0.9022 val_loss=1.1491 val_rank_acc=0.822 val_progress_mae=0.238 +epoch=86 train_loss=0.8928 val_loss=1.1466 val_rank_acc=0.821 val_progress_mae=0.247 +epoch=87 train_loss=0.8900 val_loss=1.1507 val_rank_acc=0.819 val_progress_mae=0.238 +epoch=88 train_loss=0.8935 val_loss=1.1642 val_rank_acc=0.815 val_progress_mae=0.239 +epoch=89 train_loss=0.8988 val_loss=1.1493 val_rank_acc=0.819 val_progress_mae=0.239 +epoch=90 train_loss=0.9007 val_loss=1.1518 val_rank_acc=0.816 val_progress_mae=0.240 +epoch=91 train_loss=0.8933 val_loss=1.1574 val_rank_acc=0.821 val_progress_mae=0.240 +epoch=92 train_loss=0.8928 val_loss=1.1868 val_rank_acc=0.818 val_progress_mae=0.240 +epoch=93 train_loss=0.8838 val_loss=1.1538 val_rank_acc=0.820 val_progress_mae=0.239 +epoch=94 train_loss=0.8952 val_loss=1.1513 val_rank_acc=0.818 val_progress_mae=0.238 +epoch=95 train_loss=0.8797 val_loss=1.1595 val_rank_acc=0.819 val_progress_mae=0.241 +epoch=96 train_loss=0.8844 val_loss=1.1876 val_rank_acc=0.813 val_progress_mae=0.239 +epoch=97 train_loss=0.8786 val_loss=1.1572 val_rank_acc=0.822 val_progress_mae=0.238 +epoch=98 train_loss=0.8808 val_loss=1.1565 val_rank_acc=0.819 val_progress_mae=0.241 +epoch=99 train_loss=0.8782 val_loss=1.1678 val_rank_acc=0.822 val_progress_mae=0.242 +epoch=100 train_loss=0.8797 val_loss=1.1622 val_rank_acc=0.813 val_progress_mae=0.239 +epoch=101 train_loss=0.8616 val_loss=1.1736 val_rank_acc=0.813 val_progress_mae=0.245 +epoch=102 train_loss=0.8687 val_loss=1.1734 val_rank_acc=0.819 val_progress_mae=0.234 +epoch=103 train_loss=0.8732 val_loss=1.1689 val_rank_acc=0.813 val_progress_mae=0.238 +epoch=104 train_loss=0.8759 val_loss=1.1982 val_rank_acc=0.814 val_progress_mae=0.237 +epoch=105 train_loss=0.8778 val_loss=1.1410 val_rank_acc=0.814 val_progress_mae=0.243 +epoch=106 train_loss=0.8704 val_loss=1.1780 val_rank_acc=0.818 val_progress_mae=0.240 +epoch=107 train_loss=0.8759 val_loss=1.1541 val_rank_acc=0.817 val_progress_mae=0.242 +epoch=108 train_loss=0.8647 val_loss=1.1715 val_rank_acc=0.817 val_progress_mae=0.239 +epoch=109 train_loss=0.8565 val_loss=1.1722 val_rank_acc=0.820 val_progress_mae=0.243 +epoch=110 train_loss=0.8563 val_loss=1.1648 val_rank_acc=0.823 val_progress_mae=0.237 +epoch=111 train_loss=0.8561 val_loss=1.1831 val_rank_acc=0.813 val_progress_mae=0.239 +epoch=112 train_loss=0.8665 val_loss=1.1671 val_rank_acc=0.816 val_progress_mae=0.241 +epoch=113 train_loss=0.8605 val_loss=1.1710 val_rank_acc=0.820 val_progress_mae=0.240 +epoch=114 train_loss=0.8697 val_loss=1.1762 val_rank_acc=0.813 val_progress_mae=0.235 +epoch=115 train_loss=0.8607 val_loss=1.1764 val_rank_acc=0.815 val_progress_mae=0.244 +epoch=116 train_loss=0.8540 val_loss=1.1917 val_rank_acc=0.813 val_progress_mae=0.244 +epoch=117 train_loss=0.8663 val_loss=1.1876 val_rank_acc=0.815 val_progress_mae=0.240 +epoch=118 train_loss=0.8700 val_loss=1.1637 val_rank_acc=0.817 val_progress_mae=0.239 +epoch=119 train_loss=0.8541 val_loss=1.1769 val_rank_acc=0.817 val_progress_mae=0.239 +epoch=120 train_loss=0.8661 val_loss=1.1587 val_rank_acc=0.814 val_progress_mae=0.236 +epoch=121 train_loss=0.8525 val_loss=1.1622 val_rank_acc=0.812 val_progress_mae=0.242 +epoch=122 train_loss=0.8631 val_loss=1.1479 val_rank_acc=0.814 val_progress_mae=0.240 +epoch=123 train_loss=0.8560 val_loss=1.1923 val_rank_acc=0.817 val_progress_mae=0.240 +epoch=124 train_loss=0.8600 val_loss=1.1774 val_rank_acc=0.813 val_progress_mae=0.244 +epoch=125 train_loss=0.8556 val_loss=1.1651 val_rank_acc=0.811 val_progress_mae=0.236 +epoch=126 train_loss=0.8512 val_loss=1.1695 val_rank_acc=0.821 val_progress_mae=0.242 +epoch=127 train_loss=0.8529 val_loss=1.1842 val_rank_acc=0.814 val_progress_mae=0.239 +epoch=128 train_loss=0.8448 val_loss=1.1588 val_rank_acc=0.818 val_progress_mae=0.244 +epoch=129 train_loss=0.8555 val_loss=1.1493 val_rank_acc=0.821 val_progress_mae=0.239 +epoch=130 train_loss=0.8495 val_loss=1.1741 val_rank_acc=0.817 val_progress_mae=0.243 +epoch=131 train_loss=0.8562 val_loss=1.1827 val_rank_acc=0.823 val_progress_mae=0.247 +epoch=132 train_loss=0.8577 val_loss=1.1545 val_rank_acc=0.824 val_progress_mae=0.239 +epoch=133 train_loss=0.8434 val_loss=1.1650 val_rank_acc=0.817 val_progress_mae=0.240 +epoch=134 train_loss=0.8431 val_loss=1.1658 val_rank_acc=0.819 val_progress_mae=0.243 +epoch=135 train_loss=0.8471 val_loss=1.1694 val_rank_acc=0.816 val_progress_mae=0.245 +epoch=136 train_loss=0.8524 val_loss=1.1690 val_rank_acc=0.817 val_progress_mae=0.241 +epoch=137 train_loss=0.8404 val_loss=1.1702 val_rank_acc=0.815 val_progress_mae=0.238 +epoch=138 train_loss=0.8388 val_loss=1.2010 val_rank_acc=0.810 val_progress_mae=0.241 +epoch=139 train_loss=0.8432 val_loss=1.1734 val_rank_acc=0.810 val_progress_mae=0.244 +epoch=140 train_loss=0.8415 val_loss=1.1768 val_rank_acc=0.814 val_progress_mae=0.241 +epoch=141 train_loss=0.8374 val_loss=1.1865 val_rank_acc=0.807 val_progress_mae=0.239 +epoch=142 train_loss=0.8302 val_loss=1.1857 val_rank_acc=0.814 val_progress_mae=0.237 +epoch=143 train_loss=0.8359 val_loss=1.1873 val_rank_acc=0.807 val_progress_mae=0.239 +epoch=144 train_loss=0.8411 val_loss=1.1798 val_rank_acc=0.810 val_progress_mae=0.242 +epoch=145 train_loss=0.8338 val_loss=1.1617 val_rank_acc=0.817 val_progress_mae=0.239 +epoch=146 train_loss=0.8486 val_loss=1.1805 val_rank_acc=0.823 val_progress_mae=0.240 +epoch=147 train_loss=0.8352 val_loss=1.1903 val_rank_acc=0.813 val_progress_mae=0.240 +epoch=148 train_loss=0.8301 val_loss=1.1632 val_rank_acc=0.817 val_progress_mae=0.243 +epoch=149 train_loss=0.8267 val_loss=1.1650 val_rank_acc=0.806 val_progress_mae=0.237 +epoch=150 train_loss=0.8321 val_loss=1.1765 val_rank_acc=0.809 val_progress_mae=0.237 +epoch=151 train_loss=0.8382 val_loss=1.1754 val_rank_acc=0.808 val_progress_mae=0.243 +epoch=152 train_loss=0.8370 val_loss=1.1481 val_rank_acc=0.819 val_progress_mae=0.238 +epoch=153 train_loss=0.8233 val_loss=1.1558 val_rank_acc=0.814 val_progress_mae=0.242 +epoch=154 train_loss=0.8379 val_loss=1.1701 val_rank_acc=0.811 val_progress_mae=0.244 +epoch=155 train_loss=0.8341 val_loss=1.1628 val_rank_acc=0.813 val_progress_mae=0.241 +epoch=156 train_loss=0.8335 val_loss=1.1941 val_rank_acc=0.807 val_progress_mae=0.242 +epoch=157 train_loss=0.8235 val_loss=1.1698 val_rank_acc=0.818 val_progress_mae=0.244 +epoch=158 train_loss=0.8453 val_loss=1.1837 val_rank_acc=0.806 val_progress_mae=0.240 +epoch=159 train_loss=0.8295 val_loss=1.1815 val_rank_acc=0.813 val_progress_mae=0.243 +epoch=160 train_loss=0.8250 val_loss=1.1540 val_rank_acc=0.818 val_progress_mae=0.234 +epoch=161 train_loss=0.8226 val_loss=1.1833 val_rank_acc=0.811 val_progress_mae=0.244 +epoch=162 train_loss=0.8394 val_loss=1.1598 val_rank_acc=0.808 val_progress_mae=0.241 +epoch=163 train_loss=0.8150 val_loss=1.1811 val_rank_acc=0.809 val_progress_mae=0.240 +epoch=164 train_loss=0.8273 val_loss=1.1751 val_rank_acc=0.818 val_progress_mae=0.238 +epoch=165 train_loss=0.8254 val_loss=1.1685 val_rank_acc=0.814 val_progress_mae=0.236 +epoch=166 train_loss=0.8339 val_loss=1.2104 val_rank_acc=0.799 val_progress_mae=0.237 +epoch=167 train_loss=0.8291 val_loss=1.1832 val_rank_acc=0.811 val_progress_mae=0.242 +epoch=168 train_loss=0.8325 val_loss=1.1666 val_rank_acc=0.812 val_progress_mae=0.245 +epoch=169 train_loss=0.8225 val_loss=1.1697 val_rank_acc=0.811 val_progress_mae=0.238 +epoch=170 train_loss=0.8270 val_loss=1.1774 val_rank_acc=0.809 val_progress_mae=0.240 +epoch=171 train_loss=0.8284 val_loss=1.1895 val_rank_acc=0.813 val_progress_mae=0.240 +epoch=172 train_loss=0.8213 val_loss=1.1638 val_rank_acc=0.816 val_progress_mae=0.242 +epoch=173 train_loss=0.8245 val_loss=1.1997 val_rank_acc=0.810 val_progress_mae=0.242 +epoch=174 train_loss=0.8252 val_loss=1.1889 val_rank_acc=0.810 val_progress_mae=0.241 +epoch=175 train_loss=0.8304 val_loss=1.2007 val_rank_acc=0.806 val_progress_mae=0.242 +epoch=176 train_loss=0.8289 val_loss=1.1753 val_rank_acc=0.815 val_progress_mae=0.239 +epoch=177 train_loss=0.8251 val_loss=1.1750 val_rank_acc=0.815 val_progress_mae=0.242 +epoch=178 train_loss=0.8217 val_loss=1.1591 val_rank_acc=0.809 val_progress_mae=0.240 +epoch=179 train_loss=0.8252 val_loss=1.1884 val_rank_acc=0.809 val_progress_mae=0.240 +epoch=180 train_loss=0.8329 val_loss=1.1673 val_rank_acc=0.813 val_progress_mae=0.237 +epoch=181 train_loss=0.8182 val_loss=1.1889 val_rank_acc=0.808 val_progress_mae=0.241 +epoch=182 train_loss=0.8188 val_loss=1.1842 val_rank_acc=0.817 val_progress_mae=0.239 +epoch=183 train_loss=0.8169 val_loss=1.1531 val_rank_acc=0.821 val_progress_mae=0.241 +epoch=184 train_loss=0.8248 val_loss=1.1805 val_rank_acc=0.814 val_progress_mae=0.240 +epoch=185 train_loss=0.8278 val_loss=1.1891 val_rank_acc=0.810 val_progress_mae=0.241 +epoch=186 train_loss=0.8166 val_loss=1.1792 val_rank_acc=0.810 val_progress_mae=0.240 +epoch=187 train_loss=0.8155 val_loss=1.2020 val_rank_acc=0.804 val_progress_mae=0.240 +epoch=188 train_loss=0.8290 val_loss=1.1770 val_rank_acc=0.810 val_progress_mae=0.239 +epoch=189 train_loss=0.8229 val_loss=1.1929 val_rank_acc=0.807 val_progress_mae=0.241 +epoch=190 train_loss=0.8159 val_loss=1.1529 val_rank_acc=0.815 val_progress_mae=0.240 +epoch=191 train_loss=0.8211 val_loss=1.2098 val_rank_acc=0.814 val_progress_mae=0.245 +epoch=192 train_loss=0.8178 val_loss=1.1807 val_rank_acc=0.815 val_progress_mae=0.239 +epoch=193 train_loss=0.8177 val_loss=1.1982 val_rank_acc=0.803 val_progress_mae=0.240 +epoch=194 train_loss=0.8248 val_loss=1.1717 val_rank_acc=0.807 val_progress_mae=0.235 +epoch=195 train_loss=0.8236 val_loss=1.1742 val_rank_acc=0.811 val_progress_mae=0.241 +epoch=196 train_loss=0.8100 val_loss=1.2058 val_rank_acc=0.814 val_progress_mae=0.237 +epoch=197 train_loss=0.8102 val_loss=1.1853 val_rank_acc=0.808 val_progress_mae=0.241 +epoch=198 train_loss=0.8034 val_loss=1.2086 val_rank_acc=0.813 val_progress_mae=0.238 +epoch=199 train_loss=0.8213 val_loss=1.1672 val_rank_acc=0.812 val_progress_mae=0.239 +epoch=200 train_loss=0.8167 val_loss=1.1909 val_rank_acc=0.806 val_progress_mae=0.241 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a1_revised_enhanced/seed_1 +best val rank_acc=0.8358 + +✅ Phase A1-Revised enhanced training complete (seed 1) + +Next: Evaluate and check if 45%+ achieved diff --git a/workspace/logs/phase_a1_enhanced_single_14662616_2.err b/workspace/logs/phase_a1_enhanced_single_14662616_2.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/phase_a1_enhanced_single_14662616_2.out b/workspace/logs/phase_a1_enhanced_single_14662616_2.out new file mode 100644 index 0000000000000000000000000000000000000000..670a394296b3ce6411a9afb3f3362d92a54f1f9d --- /dev/null +++ b/workspace/logs/phase_a1_enhanced_single_14662616_2.out @@ -0,0 +1,218 @@ += = = = = = = = = = = = = = = = = = +Phase A1-Revised: Enhanced Training (Existing Data) += = = = = = = = = = = = = = = = = = + +Strategy: Better training, not more data +Seed: 2 +Dataset: 3,500 groups (existing) +Model: h=256 (best from Phase A4) +Training: 200 epochs with cosine schedule + +Target: 45%+ policy success + +epoch=1 train_loss=1.3510 val_loss=1.2182 val_rank_acc=0.797 val_progress_mae=0.225 +epoch=2 train_loss=1.2229 val_loss=1.2107 val_rank_acc=0.811 val_progress_mae=0.234 +epoch=3 train_loss=1.2020 val_loss=1.1725 val_rank_acc=0.814 val_progress_mae=0.220 +epoch=4 train_loss=1.1800 val_loss=1.1949 val_rank_acc=0.827 val_progress_mae=0.252 +epoch=5 train_loss=1.1607 val_loss=1.1817 val_rank_acc=0.814 val_progress_mae=0.230 +epoch=6 train_loss=1.1469 val_loss=1.1605 val_rank_acc=0.817 val_progress_mae=0.230 +epoch=7 train_loss=1.1418 val_loss=1.1264 val_rank_acc=0.816 val_progress_mae=0.244 +epoch=8 train_loss=1.1365 val_loss=1.1414 val_rank_acc=0.821 val_progress_mae=0.231 +epoch=9 train_loss=1.1245 val_loss=1.1459 val_rank_acc=0.828 val_progress_mae=0.232 +epoch=10 train_loss=1.1323 val_loss=1.1347 val_rank_acc=0.822 val_progress_mae=0.238 +epoch=11 train_loss=1.1132 val_loss=1.1317 val_rank_acc=0.830 val_progress_mae=0.234 +epoch=12 train_loss=1.1122 val_loss=1.1331 val_rank_acc=0.824 val_progress_mae=0.234 +epoch=13 train_loss=1.0928 val_loss=1.1034 val_rank_acc=0.829 val_progress_mae=0.229 +epoch=14 train_loss=1.1042 val_loss=1.1468 val_rank_acc=0.826 val_progress_mae=0.233 +epoch=15 train_loss=1.0927 val_loss=1.1131 val_rank_acc=0.830 val_progress_mae=0.232 +epoch=16 train_loss=1.0944 val_loss=1.0950 val_rank_acc=0.833 val_progress_mae=0.246 +epoch=17 train_loss=1.0830 val_loss=1.1288 val_rank_acc=0.835 val_progress_mae=0.249 +epoch=18 train_loss=1.0821 val_loss=1.1090 val_rank_acc=0.829 val_progress_mae=0.237 +epoch=19 train_loss=1.0677 val_loss=1.0971 val_rank_acc=0.840 val_progress_mae=0.238 +epoch=20 train_loss=1.0651 val_loss=1.1372 val_rank_acc=0.828 val_progress_mae=0.244 +epoch=21 train_loss=1.0811 val_loss=1.1622 val_rank_acc=0.835 val_progress_mae=0.233 +epoch=22 train_loss=1.0726 val_loss=1.1219 val_rank_acc=0.828 val_progress_mae=0.228 +epoch=23 train_loss=1.0607 val_loss=1.1121 val_rank_acc=0.832 val_progress_mae=0.227 +epoch=24 train_loss=1.0516 val_loss=1.1354 val_rank_acc=0.829 val_progress_mae=0.238 +epoch=25 train_loss=1.0468 val_loss=1.1032 val_rank_acc=0.828 val_progress_mae=0.229 +epoch=26 train_loss=1.0522 val_loss=1.1092 val_rank_acc=0.833 val_progress_mae=0.232 +epoch=27 train_loss=1.0408 val_loss=1.1051 val_rank_acc=0.830 val_progress_mae=0.234 +epoch=28 train_loss=1.0376 val_loss=1.1171 val_rank_acc=0.834 val_progress_mae=0.232 +epoch=29 train_loss=1.0355 val_loss=1.1127 val_rank_acc=0.831 val_progress_mae=0.237 +epoch=30 train_loss=1.0434 val_loss=1.1215 val_rank_acc=0.833 val_progress_mae=0.226 +epoch=31 train_loss=1.0390 val_loss=1.1115 val_rank_acc=0.833 val_progress_mae=0.233 +epoch=32 train_loss=1.0290 val_loss=1.1113 val_rank_acc=0.831 val_progress_mae=0.236 +epoch=33 train_loss=1.0367 val_loss=1.1051 val_rank_acc=0.832 val_progress_mae=0.233 +epoch=34 train_loss=1.0208 val_loss=1.1338 val_rank_acc=0.828 val_progress_mae=0.227 +epoch=35 train_loss=1.0227 val_loss=1.1450 val_rank_acc=0.828 val_progress_mae=0.234 +epoch=36 train_loss=1.0160 val_loss=1.0818 val_rank_acc=0.836 val_progress_mae=0.236 +epoch=37 train_loss=1.0218 val_loss=1.1289 val_rank_acc=0.834 val_progress_mae=0.241 +epoch=38 train_loss=1.0068 val_loss=1.1123 val_rank_acc=0.829 val_progress_mae=0.240 +epoch=39 train_loss=1.0108 val_loss=1.1242 val_rank_acc=0.830 val_progress_mae=0.237 +epoch=40 train_loss=1.0067 val_loss=1.1231 val_rank_acc=0.832 val_progress_mae=0.237 +epoch=41 train_loss=0.9967 val_loss=1.0950 val_rank_acc=0.833 val_progress_mae=0.237 +epoch=42 train_loss=1.0080 val_loss=1.1313 val_rank_acc=0.828 val_progress_mae=0.233 +epoch=43 train_loss=1.0051 val_loss=1.1307 val_rank_acc=0.822 val_progress_mae=0.237 +epoch=44 train_loss=1.0002 val_loss=1.0934 val_rank_acc=0.838 val_progress_mae=0.232 +epoch=45 train_loss=0.9926 val_loss=1.1363 val_rank_acc=0.827 val_progress_mae=0.235 +epoch=46 train_loss=0.9738 val_loss=1.1330 val_rank_acc=0.825 val_progress_mae=0.234 +epoch=47 train_loss=0.9870 val_loss=1.1061 val_rank_acc=0.828 val_progress_mae=0.234 +epoch=48 train_loss=0.9865 val_loss=1.1234 val_rank_acc=0.824 val_progress_mae=0.242 +epoch=49 train_loss=0.9823 val_loss=1.1465 val_rank_acc=0.824 val_progress_mae=0.237 +epoch=50 train_loss=0.9821 val_loss=1.1158 val_rank_acc=0.827 val_progress_mae=0.232 +epoch=51 train_loss=0.9773 val_loss=1.1260 val_rank_acc=0.825 val_progress_mae=0.235 +epoch=52 train_loss=0.9804 val_loss=1.1248 val_rank_acc=0.815 val_progress_mae=0.231 +epoch=53 train_loss=0.9668 val_loss=1.1052 val_rank_acc=0.821 val_progress_mae=0.233 +epoch=54 train_loss=0.9733 val_loss=1.1307 val_rank_acc=0.828 val_progress_mae=0.236 +epoch=55 train_loss=0.9590 val_loss=1.1168 val_rank_acc=0.825 val_progress_mae=0.239 +epoch=56 train_loss=0.9474 val_loss=1.1417 val_rank_acc=0.829 val_progress_mae=0.235 +epoch=57 train_loss=0.9574 val_loss=1.1328 val_rank_acc=0.827 val_progress_mae=0.239 +epoch=58 train_loss=0.9632 val_loss=1.1565 val_rank_acc=0.818 val_progress_mae=0.238 +epoch=59 train_loss=0.9537 val_loss=1.1301 val_rank_acc=0.823 val_progress_mae=0.240 +epoch=60 train_loss=0.9529 val_loss=1.1492 val_rank_acc=0.817 val_progress_mae=0.238 +epoch=61 train_loss=0.9546 val_loss=1.1249 val_rank_acc=0.833 val_progress_mae=0.236 +epoch=62 train_loss=0.9486 val_loss=1.1618 val_rank_acc=0.819 val_progress_mae=0.230 +epoch=63 train_loss=0.9600 val_loss=1.1249 val_rank_acc=0.825 val_progress_mae=0.237 +epoch=64 train_loss=0.9444 val_loss=1.1441 val_rank_acc=0.815 val_progress_mae=0.235 +epoch=65 train_loss=0.9421 val_loss=1.1300 val_rank_acc=0.837 val_progress_mae=0.240 +epoch=66 train_loss=0.9219 val_loss=1.1324 val_rank_acc=0.820 val_progress_mae=0.236 +epoch=67 train_loss=0.9360 val_loss=1.1291 val_rank_acc=0.822 val_progress_mae=0.233 +epoch=68 train_loss=0.9283 val_loss=1.1132 val_rank_acc=0.832 val_progress_mae=0.234 +epoch=69 train_loss=0.9262 val_loss=1.1319 val_rank_acc=0.829 val_progress_mae=0.235 +epoch=70 train_loss=0.9268 val_loss=1.1433 val_rank_acc=0.820 val_progress_mae=0.235 +epoch=71 train_loss=0.9263 val_loss=1.1303 val_rank_acc=0.826 val_progress_mae=0.232 +epoch=72 train_loss=0.9298 val_loss=1.1470 val_rank_acc=0.822 val_progress_mae=0.237 +epoch=73 train_loss=0.9304 val_loss=1.1224 val_rank_acc=0.825 val_progress_mae=0.239 +epoch=74 train_loss=0.9197 val_loss=1.1547 val_rank_acc=0.825 val_progress_mae=0.232 +epoch=75 train_loss=0.9192 val_loss=1.1197 val_rank_acc=0.818 val_progress_mae=0.235 +epoch=76 train_loss=0.9148 val_loss=1.1163 val_rank_acc=0.824 val_progress_mae=0.238 +epoch=77 train_loss=0.9063 val_loss=1.1466 val_rank_acc=0.827 val_progress_mae=0.242 +epoch=78 train_loss=0.9184 val_loss=1.1429 val_rank_acc=0.822 val_progress_mae=0.230 +epoch=79 train_loss=0.9144 val_loss=1.1544 val_rank_acc=0.820 val_progress_mae=0.235 +epoch=80 train_loss=0.9056 val_loss=1.1541 val_rank_acc=0.821 val_progress_mae=0.233 +epoch=81 train_loss=0.9021 val_loss=1.1524 val_rank_acc=0.818 val_progress_mae=0.231 +epoch=82 train_loss=0.9056 val_loss=1.1502 val_rank_acc=0.822 val_progress_mae=0.229 +epoch=83 train_loss=0.9029 val_loss=1.1473 val_rank_acc=0.818 val_progress_mae=0.232 +epoch=84 train_loss=0.8939 val_loss=1.1668 val_rank_acc=0.819 val_progress_mae=0.229 +epoch=85 train_loss=0.9070 val_loss=1.1562 val_rank_acc=0.818 val_progress_mae=0.230 +epoch=86 train_loss=0.8957 val_loss=1.1594 val_rank_acc=0.816 val_progress_mae=0.236 +epoch=87 train_loss=0.9003 val_loss=1.1506 val_rank_acc=0.819 val_progress_mae=0.234 +epoch=88 train_loss=0.8992 val_loss=1.1378 val_rank_acc=0.814 val_progress_mae=0.233 +epoch=89 train_loss=0.9003 val_loss=1.1267 val_rank_acc=0.820 val_progress_mae=0.233 +epoch=90 train_loss=0.8926 val_loss=1.1596 val_rank_acc=0.820 val_progress_mae=0.235 +epoch=91 train_loss=0.8892 val_loss=1.1463 val_rank_acc=0.818 val_progress_mae=0.236 +epoch=92 train_loss=0.8810 val_loss=1.1640 val_rank_acc=0.814 val_progress_mae=0.232 +epoch=93 train_loss=0.8703 val_loss=1.1536 val_rank_acc=0.821 val_progress_mae=0.232 +epoch=94 train_loss=0.8852 val_loss=1.1344 val_rank_acc=0.817 val_progress_mae=0.235 +epoch=95 train_loss=0.8914 val_loss=1.1413 val_rank_acc=0.820 val_progress_mae=0.234 +epoch=96 train_loss=0.8754 val_loss=1.1910 val_rank_acc=0.818 val_progress_mae=0.235 +epoch=97 train_loss=0.8914 val_loss=1.1506 val_rank_acc=0.823 val_progress_mae=0.226 +epoch=98 train_loss=0.8793 val_loss=1.1699 val_rank_acc=0.812 val_progress_mae=0.233 +epoch=99 train_loss=0.8703 val_loss=1.1502 val_rank_acc=0.820 val_progress_mae=0.235 +epoch=100 train_loss=0.8745 val_loss=1.1592 val_rank_acc=0.820 val_progress_mae=0.236 +epoch=101 train_loss=0.8765 val_loss=1.1375 val_rank_acc=0.816 val_progress_mae=0.238 +epoch=102 train_loss=0.8871 val_loss=1.1579 val_rank_acc=0.818 val_progress_mae=0.234 +epoch=103 train_loss=0.8677 val_loss=1.1419 val_rank_acc=0.815 val_progress_mae=0.238 +epoch=104 train_loss=0.8738 val_loss=1.1759 val_rank_acc=0.815 val_progress_mae=0.233 +epoch=105 train_loss=0.8683 val_loss=1.1736 val_rank_acc=0.819 val_progress_mae=0.234 +epoch=106 train_loss=0.8781 val_loss=1.1689 val_rank_acc=0.818 val_progress_mae=0.233 +epoch=107 train_loss=0.8797 val_loss=1.1666 val_rank_acc=0.820 val_progress_mae=0.235 +epoch=108 train_loss=0.8670 val_loss=1.1619 val_rank_acc=0.815 val_progress_mae=0.235 +epoch=109 train_loss=0.8751 val_loss=1.1666 val_rank_acc=0.814 val_progress_mae=0.233 +epoch=110 train_loss=0.8783 val_loss=1.1843 val_rank_acc=0.811 val_progress_mae=0.232 +epoch=111 train_loss=0.8612 val_loss=1.1666 val_rank_acc=0.813 val_progress_mae=0.234 +epoch=112 train_loss=0.8670 val_loss=1.1699 val_rank_acc=0.820 val_progress_mae=0.233 +epoch=113 train_loss=0.8618 val_loss=1.1839 val_rank_acc=0.820 val_progress_mae=0.229 +epoch=114 train_loss=0.8732 val_loss=1.1614 val_rank_acc=0.820 val_progress_mae=0.235 +epoch=115 train_loss=0.8655 val_loss=1.1588 val_rank_acc=0.815 val_progress_mae=0.234 +epoch=116 train_loss=0.8532 val_loss=1.1658 val_rank_acc=0.806 val_progress_mae=0.230 +epoch=117 train_loss=0.8450 val_loss=1.1622 val_rank_acc=0.813 val_progress_mae=0.235 +epoch=118 train_loss=0.8580 val_loss=1.1745 val_rank_acc=0.814 val_progress_mae=0.234 +epoch=119 train_loss=0.8599 val_loss=1.1767 val_rank_acc=0.809 val_progress_mae=0.233 +epoch=120 train_loss=0.8518 val_loss=1.1755 val_rank_acc=0.815 val_progress_mae=0.235 +epoch=121 train_loss=0.8589 val_loss=1.1701 val_rank_acc=0.809 val_progress_mae=0.232 +epoch=122 train_loss=0.8522 val_loss=1.1641 val_rank_acc=0.820 val_progress_mae=0.236 +epoch=123 train_loss=0.8626 val_loss=1.1645 val_rank_acc=0.812 val_progress_mae=0.232 +epoch=124 train_loss=0.8545 val_loss=1.1678 val_rank_acc=0.811 val_progress_mae=0.233 +epoch=125 train_loss=0.8540 val_loss=1.1622 val_rank_acc=0.814 val_progress_mae=0.234 +epoch=126 train_loss=0.8463 val_loss=1.1707 val_rank_acc=0.811 val_progress_mae=0.233 +epoch=127 train_loss=0.8464 val_loss=1.1633 val_rank_acc=0.815 val_progress_mae=0.237 +epoch=128 train_loss=0.8576 val_loss=1.1691 val_rank_acc=0.817 val_progress_mae=0.234 +epoch=129 train_loss=0.8536 val_loss=1.1826 val_rank_acc=0.813 val_progress_mae=0.234 +epoch=130 train_loss=0.8500 val_loss=1.1485 val_rank_acc=0.820 val_progress_mae=0.233 +epoch=131 train_loss=0.8628 val_loss=1.1610 val_rank_acc=0.812 val_progress_mae=0.237 +epoch=132 train_loss=0.8550 val_loss=1.1534 val_rank_acc=0.813 val_progress_mae=0.232 +epoch=133 train_loss=0.8486 val_loss=1.1626 val_rank_acc=0.814 val_progress_mae=0.235 +epoch=134 train_loss=0.8520 val_loss=1.1609 val_rank_acc=0.811 val_progress_mae=0.233 +epoch=135 train_loss=0.8358 val_loss=1.1592 val_rank_acc=0.811 val_progress_mae=0.234 +epoch=136 train_loss=0.8348 val_loss=1.1901 val_rank_acc=0.810 val_progress_mae=0.231 +epoch=137 train_loss=0.8552 val_loss=1.1780 val_rank_acc=0.810 val_progress_mae=0.233 +epoch=138 train_loss=0.8472 val_loss=1.1471 val_rank_acc=0.816 val_progress_mae=0.238 +epoch=139 train_loss=0.8437 val_loss=1.1670 val_rank_acc=0.816 val_progress_mae=0.236 +epoch=140 train_loss=0.8420 val_loss=1.1701 val_rank_acc=0.814 val_progress_mae=0.235 +epoch=141 train_loss=0.8548 val_loss=1.1707 val_rank_acc=0.812 val_progress_mae=0.230 +epoch=142 train_loss=0.8424 val_loss=1.1647 val_rank_acc=0.811 val_progress_mae=0.233 +epoch=143 train_loss=0.8288 val_loss=1.1867 val_rank_acc=0.807 val_progress_mae=0.232 +epoch=144 train_loss=0.8420 val_loss=1.1509 val_rank_acc=0.814 val_progress_mae=0.234 +epoch=145 train_loss=0.8396 val_loss=1.1563 val_rank_acc=0.811 val_progress_mae=0.235 +epoch=146 train_loss=0.8407 val_loss=1.1652 val_rank_acc=0.814 val_progress_mae=0.233 +epoch=147 train_loss=0.8452 val_loss=1.1691 val_rank_acc=0.814 val_progress_mae=0.231 +epoch=148 train_loss=0.8402 val_loss=1.1795 val_rank_acc=0.812 val_progress_mae=0.234 +epoch=149 train_loss=0.8346 val_loss=1.1477 val_rank_acc=0.812 val_progress_mae=0.234 +epoch=150 train_loss=0.8481 val_loss=1.1720 val_rank_acc=0.814 val_progress_mae=0.233 +epoch=151 train_loss=0.8440 val_loss=1.1692 val_rank_acc=0.812 val_progress_mae=0.236 +epoch=152 train_loss=0.8499 val_loss=1.1592 val_rank_acc=0.814 val_progress_mae=0.235 +epoch=153 train_loss=0.8227 val_loss=1.1648 val_rank_acc=0.810 val_progress_mae=0.236 +epoch=154 train_loss=0.8397 val_loss=1.1704 val_rank_acc=0.811 val_progress_mae=0.235 +epoch=155 train_loss=0.8363 val_loss=1.1839 val_rank_acc=0.815 val_progress_mae=0.239 +epoch=156 train_loss=0.8381 val_loss=1.1852 val_rank_acc=0.810 val_progress_mae=0.233 +epoch=157 train_loss=0.8390 val_loss=1.1783 val_rank_acc=0.806 val_progress_mae=0.233 +epoch=158 train_loss=0.8294 val_loss=1.1719 val_rank_acc=0.815 val_progress_mae=0.234 +epoch=159 train_loss=0.8253 val_loss=1.1910 val_rank_acc=0.805 val_progress_mae=0.232 +epoch=160 train_loss=0.8304 val_loss=1.1481 val_rank_acc=0.810 val_progress_mae=0.230 +epoch=161 train_loss=0.8271 val_loss=1.2008 val_rank_acc=0.809 val_progress_mae=0.238 +epoch=162 train_loss=0.8336 val_loss=1.1605 val_rank_acc=0.806 val_progress_mae=0.234 +epoch=163 train_loss=0.8412 val_loss=1.1706 val_rank_acc=0.812 val_progress_mae=0.232 +epoch=164 train_loss=0.8342 val_loss=1.1843 val_rank_acc=0.815 val_progress_mae=0.235 +epoch=165 train_loss=0.8223 val_loss=1.1925 val_rank_acc=0.811 val_progress_mae=0.235 +epoch=166 train_loss=0.8375 val_loss=1.1727 val_rank_acc=0.811 val_progress_mae=0.234 +epoch=167 train_loss=0.8334 val_loss=1.1908 val_rank_acc=0.807 val_progress_mae=0.234 +epoch=168 train_loss=0.8233 val_loss=1.1779 val_rank_acc=0.806 val_progress_mae=0.232 +epoch=169 train_loss=0.8220 val_loss=1.1706 val_rank_acc=0.808 val_progress_mae=0.231 +epoch=170 train_loss=0.8239 val_loss=1.1900 val_rank_acc=0.811 val_progress_mae=0.231 +epoch=171 train_loss=0.8088 val_loss=1.1820 val_rank_acc=0.812 val_progress_mae=0.236 +epoch=172 train_loss=0.8338 val_loss=1.1846 val_rank_acc=0.809 val_progress_mae=0.238 +epoch=173 train_loss=0.8314 val_loss=1.1878 val_rank_acc=0.813 val_progress_mae=0.232 +epoch=174 train_loss=0.8327 val_loss=1.1895 val_rank_acc=0.801 val_progress_mae=0.232 +epoch=175 train_loss=0.8351 val_loss=1.1715 val_rank_acc=0.811 val_progress_mae=0.237 +epoch=176 train_loss=0.8267 val_loss=1.1637 val_rank_acc=0.811 val_progress_mae=0.233 +epoch=177 train_loss=0.8236 val_loss=1.1677 val_rank_acc=0.808 val_progress_mae=0.237 +epoch=178 train_loss=0.8253 val_loss=1.1661 val_rank_acc=0.810 val_progress_mae=0.234 +epoch=179 train_loss=0.8144 val_loss=1.1802 val_rank_acc=0.810 val_progress_mae=0.237 +epoch=180 train_loss=0.8151 val_loss=1.1781 val_rank_acc=0.807 val_progress_mae=0.231 +epoch=181 train_loss=0.8280 val_loss=1.1662 val_rank_acc=0.802 val_progress_mae=0.235 +epoch=182 train_loss=0.8073 val_loss=1.1745 val_rank_acc=0.808 val_progress_mae=0.232 +epoch=183 train_loss=0.8276 val_loss=1.1604 val_rank_acc=0.816 val_progress_mae=0.236 +epoch=184 train_loss=0.8294 val_loss=1.1930 val_rank_acc=0.811 val_progress_mae=0.232 +epoch=185 train_loss=0.8180 val_loss=1.1662 val_rank_acc=0.811 val_progress_mae=0.233 +epoch=186 train_loss=0.8247 val_loss=1.1555 val_rank_acc=0.808 val_progress_mae=0.237 +epoch=187 train_loss=0.8180 val_loss=1.1623 val_rank_acc=0.815 val_progress_mae=0.232 +epoch=188 train_loss=0.8320 val_loss=1.1546 val_rank_acc=0.813 val_progress_mae=0.233 +epoch=189 train_loss=0.8263 val_loss=1.1727 val_rank_acc=0.809 val_progress_mae=0.236 +epoch=190 train_loss=0.8132 val_loss=1.1834 val_rank_acc=0.807 val_progress_mae=0.235 +epoch=191 train_loss=0.8219 val_loss=1.1657 val_rank_acc=0.819 val_progress_mae=0.234 +epoch=192 train_loss=0.8189 val_loss=1.1726 val_rank_acc=0.803 val_progress_mae=0.233 +epoch=193 train_loss=0.8255 val_loss=1.1606 val_rank_acc=0.814 val_progress_mae=0.236 +epoch=194 train_loss=0.8089 val_loss=1.1841 val_rank_acc=0.809 val_progress_mae=0.237 +epoch=195 train_loss=0.8136 val_loss=1.1684 val_rank_acc=0.803 val_progress_mae=0.234 +epoch=196 train_loss=0.8262 val_loss=1.1830 val_rank_acc=0.808 val_progress_mae=0.237 +epoch=197 train_loss=0.8135 val_loss=1.1802 val_rank_acc=0.818 val_progress_mae=0.236 +epoch=198 train_loss=0.8114 val_loss=1.1744 val_rank_acc=0.808 val_progress_mae=0.234 +epoch=199 train_loss=0.8219 val_loss=1.1610 val_rank_acc=0.808 val_progress_mae=0.233 +epoch=200 train_loss=0.8148 val_loss=1.1778 val_rank_acc=0.808 val_progress_mae=0.233 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a1_revised_enhanced/seed_2 +best val rank_acc=0.8400 + +✅ Phase A1-Revised enhanced training complete (seed 2) + +Next: Evaluate and check if 45%+ achieved diff --git a/workspace/logs/phase_a2_large_train_14622955.err b/workspace/logs/phase_a2_large_train_14622955.err new file mode 100644 index 0000000000000000000000000000000000000000..e76ba996d397f818c3dc0609a827d9537ed338bd --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14622955.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --dropout 0.1 --warmup-steps 1000 diff --git a/workspace/logs/phase_a2_large_train_14622955.out b/workspace/logs/phase_a2_large_train_14622955.out new file mode 100644 index 0000000000000000000000000000000000000000..f373e1bbaeccaa373fee2fe275d29ac5ba5908c2 --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14622955.out @@ -0,0 +1,6 @@ +=== Phase A2: Training Large Capacity Model === +Seed: 2 +Hidden dim: 512 (vs current 256) +Dataset: 10K groups +Target: 40%+ policy success + diff --git a/workspace/logs/phase_a2_large_train_14623347.err b/workspace/logs/phase_a2_large_train_14623347.err new file mode 100644 index 0000000000000000000000000000000000000000..e76ba996d397f818c3dc0609a827d9537ed338bd --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14623347.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --dropout 0.1 --warmup-steps 1000 diff --git a/workspace/logs/phase_a2_large_train_14623347.out b/workspace/logs/phase_a2_large_train_14623347.out new file mode 100644 index 0000000000000000000000000000000000000000..3d1f55aadba1982cc85e91503530e41c2ed4ab2b --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14623347.out @@ -0,0 +1,6 @@ +=== Phase A2: Training Large Capacity Model === +Seed: 0 +Hidden dim: 512 (vs current 256) +Dataset: 10K groups +Target: 40%+ policy success + diff --git a/workspace/logs/phase_a2_large_train_14623348.err b/workspace/logs/phase_a2_large_train_14623348.err new file mode 100644 index 0000000000000000000000000000000000000000..e76ba996d397f818c3dc0609a827d9537ed338bd --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14623348.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --dropout 0.1 --warmup-steps 1000 diff --git a/workspace/logs/phase_a2_large_train_14623348.out b/workspace/logs/phase_a2_large_train_14623348.out new file mode 100644 index 0000000000000000000000000000000000000000..7e1ab15f7d58bb4f43028e99ef1f52f68b95cf26 --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14623348.out @@ -0,0 +1,6 @@ +=== Phase A2: Training Large Capacity Model === +Seed: 1 +Hidden dim: 512 (vs current 256) +Dataset: 10K groups +Target: 40%+ policy success + diff --git a/workspace/logs/phase_a2_large_train_14623492.err b/workspace/logs/phase_a2_large_train_14623492.err new file mode 100644 index 0000000000000000000000000000000000000000..4d22bb292fc77a935ec1337906cb0e1c16c90b0e --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14623492.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unknown loss weight 'field_utility_regression'; choose one of: bc, bc_best_action, causal_contrastive, contrast, effect, field_anchor, field_effect, field_potential, field_preference, forward_effect_prediction, lang_pair, language_minimal_pair, progress, rank, regret, regret_prediction, same_state_pairwise_ranking, success diff --git a/workspace/logs/phase_a2_large_train_14623492.out b/workspace/logs/phase_a2_large_train_14623492.out new file mode 100644 index 0000000000000000000000000000000000000000..f373e1bbaeccaa373fee2fe275d29ac5ba5908c2 --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14623492.out @@ -0,0 +1,6 @@ +=== Phase A2: Training Large Capacity Model === +Seed: 2 +Hidden dim: 512 (vs current 256) +Dataset: 10K groups +Target: 40%+ policy success + diff --git a/workspace/logs/phase_a2_large_train_14623702.err b/workspace/logs/phase_a2_large_train_14623702.err new file mode 100644 index 0000000000000000000000000000000000000000..4d22bb292fc77a935ec1337906cb0e1c16c90b0e --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14623702.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unknown loss weight 'field_utility_regression'; choose one of: bc, bc_best_action, causal_contrastive, contrast, effect, field_anchor, field_effect, field_potential, field_preference, forward_effect_prediction, lang_pair, language_minimal_pair, progress, rank, regret, regret_prediction, same_state_pairwise_ranking, success diff --git a/workspace/logs/phase_a2_large_train_14623702.out b/workspace/logs/phase_a2_large_train_14623702.out new file mode 100644 index 0000000000000000000000000000000000000000..3d1f55aadba1982cc85e91503530e41c2ed4ab2b --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14623702.out @@ -0,0 +1,6 @@ +=== Phase A2: Training Large Capacity Model === +Seed: 0 +Hidden dim: 512 (vs current 256) +Dataset: 10K groups +Target: 40%+ policy success + diff --git a/workspace/logs/phase_a2_large_train_14623703.err b/workspace/logs/phase_a2_large_train_14623703.err new file mode 100644 index 0000000000000000000000000000000000000000..4d22bb292fc77a935ec1337906cb0e1c16c90b0e --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14623703.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unknown loss weight 'field_utility_regression'; choose one of: bc, bc_best_action, causal_contrastive, contrast, effect, field_anchor, field_effect, field_potential, field_preference, forward_effect_prediction, lang_pair, language_minimal_pair, progress, rank, regret, regret_prediction, same_state_pairwise_ranking, success diff --git a/workspace/logs/phase_a2_large_train_14623703.out b/workspace/logs/phase_a2_large_train_14623703.out new file mode 100644 index 0000000000000000000000000000000000000000..7e1ab15f7d58bb4f43028e99ef1f52f68b95cf26 --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14623703.out @@ -0,0 +1,6 @@ +=== Phase A2: Training Large Capacity Model === +Seed: 1 +Hidden dim: 512 (vs current 256) +Dataset: 10K groups +Target: 40%+ policy success + diff --git a/workspace/logs/phase_a2_large_train_14624319.err b/workspace/logs/phase_a2_large_train_14624319.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/phase_a2_large_train_14624319.out b/workspace/logs/phase_a2_large_train_14624319.out new file mode 100644 index 0000000000000000000000000000000000000000..1ff8c09e27efc62da2a7c3aa171059d517388beb --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14624319.out @@ -0,0 +1,112 @@ +=== Phase A2: Training Large Capacity Model === +Seed: 2 +Hidden dim: 512 (vs current 256) +Dataset: 10K groups +Target: 40%+ policy success + +epoch=1 train_loss=1.1232 val_loss=1.0123 val_rank_acc=0.798 val_progress_mae=0.220 +epoch=2 train_loss=1.0261 val_loss=1.0135 val_rank_acc=0.809 val_progress_mae=0.231 +epoch=3 train_loss=1.0118 val_loss=0.9810 val_rank_acc=0.805 val_progress_mae=0.230 +epoch=4 train_loss=0.9894 val_loss=1.0017 val_rank_acc=0.820 val_progress_mae=0.244 +epoch=5 train_loss=0.9720 val_loss=0.9852 val_rank_acc=0.814 val_progress_mae=0.230 +epoch=6 train_loss=0.9594 val_loss=0.9630 val_rank_acc=0.819 val_progress_mae=0.224 +epoch=7 train_loss=0.9515 val_loss=0.9360 val_rank_acc=0.821 val_progress_mae=0.240 +epoch=8 train_loss=0.9466 val_loss=0.9538 val_rank_acc=0.824 val_progress_mae=0.230 +epoch=9 train_loss=0.9382 val_loss=0.9650 val_rank_acc=0.823 val_progress_mae=0.232 +epoch=10 train_loss=0.9459 val_loss=0.9488 val_rank_acc=0.826 val_progress_mae=0.232 +epoch=11 train_loss=0.9271 val_loss=0.9470 val_rank_acc=0.828 val_progress_mae=0.230 +epoch=12 train_loss=0.9271 val_loss=0.9402 val_rank_acc=0.826 val_progress_mae=0.237 +epoch=13 train_loss=0.9089 val_loss=0.9168 val_rank_acc=0.831 val_progress_mae=0.226 +epoch=14 train_loss=0.9188 val_loss=0.9507 val_rank_acc=0.824 val_progress_mae=0.230 +epoch=15 train_loss=0.9061 val_loss=0.9169 val_rank_acc=0.827 val_progress_mae=0.230 +epoch=16 train_loss=0.9091 val_loss=0.9034 val_rank_acc=0.833 val_progress_mae=0.237 +epoch=17 train_loss=0.8968 val_loss=0.9302 val_rank_acc=0.836 val_progress_mae=0.238 +epoch=18 train_loss=0.8984 val_loss=0.9193 val_rank_acc=0.827 val_progress_mae=0.231 +epoch=19 train_loss=0.8850 val_loss=0.9104 val_rank_acc=0.838 val_progress_mae=0.230 +epoch=20 train_loss=0.8803 val_loss=0.9420 val_rank_acc=0.832 val_progress_mae=0.237 +epoch=21 train_loss=0.8974 val_loss=0.9788 val_rank_acc=0.834 val_progress_mae=0.229 +epoch=22 train_loss=0.8902 val_loss=0.9271 val_rank_acc=0.828 val_progress_mae=0.227 +epoch=23 train_loss=0.8783 val_loss=0.9151 val_rank_acc=0.835 val_progress_mae=0.222 +epoch=24 train_loss=0.8708 val_loss=0.9479 val_rank_acc=0.831 val_progress_mae=0.237 +epoch=25 train_loss=0.8622 val_loss=0.9072 val_rank_acc=0.833 val_progress_mae=0.228 +epoch=26 train_loss=0.8684 val_loss=0.9165 val_rank_acc=0.837 val_progress_mae=0.230 +epoch=27 train_loss=0.8575 val_loss=0.9122 val_rank_acc=0.829 val_progress_mae=0.229 +epoch=28 train_loss=0.8534 val_loss=0.9192 val_rank_acc=0.835 val_progress_mae=0.231 +epoch=29 train_loss=0.8532 val_loss=0.9199 val_rank_acc=0.832 val_progress_mae=0.233 +epoch=30 train_loss=0.8611 val_loss=0.9356 val_rank_acc=0.833 val_progress_mae=0.228 +epoch=31 train_loss=0.8543 val_loss=0.9154 val_rank_acc=0.836 val_progress_mae=0.231 +epoch=32 train_loss=0.8450 val_loss=0.9266 val_rank_acc=0.833 val_progress_mae=0.234 +epoch=33 train_loss=0.8535 val_loss=0.9092 val_rank_acc=0.831 val_progress_mae=0.232 +epoch=34 train_loss=0.8404 val_loss=0.9364 val_rank_acc=0.830 val_progress_mae=0.230 +epoch=35 train_loss=0.8376 val_loss=0.9561 val_rank_acc=0.830 val_progress_mae=0.231 +epoch=36 train_loss=0.8324 val_loss=0.8999 val_rank_acc=0.835 val_progress_mae=0.229 +epoch=37 train_loss=0.8344 val_loss=0.9310 val_rank_acc=0.830 val_progress_mae=0.239 +epoch=38 train_loss=0.8246 val_loss=0.9226 val_rank_acc=0.831 val_progress_mae=0.236 +epoch=39 train_loss=0.8275 val_loss=0.9295 val_rank_acc=0.832 val_progress_mae=0.235 +epoch=40 train_loss=0.8238 val_loss=0.9345 val_rank_acc=0.834 val_progress_mae=0.229 +epoch=41 train_loss=0.8136 val_loss=0.9100 val_rank_acc=0.833 val_progress_mae=0.239 +epoch=42 train_loss=0.8260 val_loss=0.9344 val_rank_acc=0.830 val_progress_mae=0.231 +epoch=43 train_loss=0.8208 val_loss=0.9277 val_rank_acc=0.827 val_progress_mae=0.232 +epoch=44 train_loss=0.8174 val_loss=0.9041 val_rank_acc=0.835 val_progress_mae=0.233 +epoch=45 train_loss=0.8083 val_loss=0.9503 val_rank_acc=0.833 val_progress_mae=0.233 +epoch=46 train_loss=0.7948 val_loss=0.9372 val_rank_acc=0.833 val_progress_mae=0.235 +epoch=47 train_loss=0.8056 val_loss=0.9223 val_rank_acc=0.832 val_progress_mae=0.233 +epoch=48 train_loss=0.8040 val_loss=0.9384 val_rank_acc=0.831 val_progress_mae=0.243 +epoch=49 train_loss=0.8006 val_loss=0.9492 val_rank_acc=0.830 val_progress_mae=0.238 +epoch=50 train_loss=0.7994 val_loss=0.9201 val_rank_acc=0.830 val_progress_mae=0.232 +epoch=51 train_loss=0.7909 val_loss=0.9361 val_rank_acc=0.826 val_progress_mae=0.232 +epoch=52 train_loss=0.7987 val_loss=0.9335 val_rank_acc=0.821 val_progress_mae=0.230 +epoch=53 train_loss=0.7809 val_loss=0.9118 val_rank_acc=0.837 val_progress_mae=0.235 +epoch=54 train_loss=0.7915 val_loss=0.9445 val_rank_acc=0.830 val_progress_mae=0.233 +epoch=55 train_loss=0.7776 val_loss=0.9254 val_rank_acc=0.824 val_progress_mae=0.236 +epoch=56 train_loss=0.7685 val_loss=0.9497 val_rank_acc=0.828 val_progress_mae=0.235 +epoch=57 train_loss=0.7716 val_loss=0.9412 val_rank_acc=0.830 val_progress_mae=0.239 +epoch=58 train_loss=0.7786 val_loss=0.9749 val_rank_acc=0.821 val_progress_mae=0.238 +epoch=59 train_loss=0.7739 val_loss=0.9487 val_rank_acc=0.826 val_progress_mae=0.237 +epoch=60 train_loss=0.7712 val_loss=0.9709 val_rank_acc=0.812 val_progress_mae=0.235 +epoch=61 train_loss=0.7705 val_loss=0.9499 val_rank_acc=0.828 val_progress_mae=0.232 +epoch=62 train_loss=0.7649 val_loss=0.9733 val_rank_acc=0.825 val_progress_mae=0.230 +epoch=63 train_loss=0.7763 val_loss=0.9391 val_rank_acc=0.827 val_progress_mae=0.236 +epoch=64 train_loss=0.7611 val_loss=0.9508 val_rank_acc=0.819 val_progress_mae=0.239 +epoch=65 train_loss=0.7580 val_loss=0.9494 val_rank_acc=0.832 val_progress_mae=0.239 +epoch=66 train_loss=0.7406 val_loss=0.9395 val_rank_acc=0.817 val_progress_mae=0.235 +epoch=67 train_loss=0.7540 val_loss=0.9454 val_rank_acc=0.826 val_progress_mae=0.228 +epoch=68 train_loss=0.7473 val_loss=0.9256 val_rank_acc=0.832 val_progress_mae=0.235 +epoch=69 train_loss=0.7462 val_loss=0.9592 val_rank_acc=0.825 val_progress_mae=0.235 +epoch=70 train_loss=0.7453 val_loss=0.9626 val_rank_acc=0.816 val_progress_mae=0.234 +epoch=71 train_loss=0.7454 val_loss=0.9465 val_rank_acc=0.825 val_progress_mae=0.234 +epoch=72 train_loss=0.7479 val_loss=0.9537 val_rank_acc=0.825 val_progress_mae=0.236 +epoch=73 train_loss=0.7452 val_loss=0.9340 val_rank_acc=0.822 val_progress_mae=0.237 +epoch=74 train_loss=0.7385 val_loss=0.9704 val_rank_acc=0.823 val_progress_mae=0.230 +epoch=75 train_loss=0.7375 val_loss=0.9369 val_rank_acc=0.821 val_progress_mae=0.235 +epoch=76 train_loss=0.7349 val_loss=0.9461 val_rank_acc=0.823 val_progress_mae=0.238 +epoch=77 train_loss=0.7236 val_loss=0.9512 val_rank_acc=0.825 val_progress_mae=0.238 +epoch=78 train_loss=0.7331 val_loss=0.9543 val_rank_acc=0.819 val_progress_mae=0.232 +epoch=79 train_loss=0.7360 val_loss=0.9651 val_rank_acc=0.821 val_progress_mae=0.232 +epoch=80 train_loss=0.7211 val_loss=0.9685 val_rank_acc=0.820 val_progress_mae=0.232 +epoch=81 train_loss=0.7168 val_loss=0.9614 val_rank_acc=0.817 val_progress_mae=0.231 +epoch=82 train_loss=0.7232 val_loss=0.9595 val_rank_acc=0.821 val_progress_mae=0.231 +epoch=83 train_loss=0.7181 val_loss=0.9564 val_rank_acc=0.821 val_progress_mae=0.232 +epoch=84 train_loss=0.7084 val_loss=0.9760 val_rank_acc=0.823 val_progress_mae=0.226 +epoch=85 train_loss=0.7213 val_loss=0.9845 val_rank_acc=0.820 val_progress_mae=0.234 +epoch=86 train_loss=0.7171 val_loss=0.9739 val_rank_acc=0.821 val_progress_mae=0.237 +epoch=87 train_loss=0.7167 val_loss=0.9488 val_rank_acc=0.819 val_progress_mae=0.234 +epoch=88 train_loss=0.7192 val_loss=0.9499 val_rank_acc=0.823 val_progress_mae=0.228 +epoch=89 train_loss=0.7160 val_loss=0.9339 val_rank_acc=0.817 val_progress_mae=0.235 +epoch=90 train_loss=0.7119 val_loss=0.9732 val_rank_acc=0.820 val_progress_mae=0.235 +epoch=91 train_loss=0.7082 val_loss=0.9578 val_rank_acc=0.826 val_progress_mae=0.239 +epoch=92 train_loss=0.6979 val_loss=0.9895 val_rank_acc=0.812 val_progress_mae=0.234 +epoch=93 train_loss=0.6901 val_loss=0.9648 val_rank_acc=0.823 val_progress_mae=0.230 +epoch=94 train_loss=0.7020 val_loss=0.9444 val_rank_acc=0.816 val_progress_mae=0.233 +epoch=95 train_loss=0.7048 val_loss=0.9436 val_rank_acc=0.824 val_progress_mae=0.231 +epoch=96 train_loss=0.6909 val_loss=0.9927 val_rank_acc=0.821 val_progress_mae=0.234 +epoch=97 train_loss=0.7054 val_loss=0.9712 val_rank_acc=0.818 val_progress_mae=0.230 +epoch=98 train_loss=0.6964 val_loss=0.9684 val_rank_acc=0.817 val_progress_mae=0.237 +epoch=99 train_loss=0.6862 val_loss=0.9565 val_rank_acc=0.823 val_progress_mae=0.236 +epoch=100 train_loss=0.6947 val_loss=0.9667 val_rank_acc=0.822 val_progress_mae=0.235 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a2_large_model/seed_2 +best val rank_acc=0.8385 + +✅ Phase A2 complete: Large model trained (seed 2) + +Next: Run phase_a3_eval_large_model.sbatch diff --git a/workspace/logs/phase_a2_large_train_14624320.err b/workspace/logs/phase_a2_large_train_14624320.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/phase_a2_large_train_14624320.out b/workspace/logs/phase_a2_large_train_14624320.out new file mode 100644 index 0000000000000000000000000000000000000000..bc703a1cf624afd5623f4e92821c51597420e9ef --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14624320.out @@ -0,0 +1,112 @@ +=== Phase A2: Training Large Capacity Model === +Seed: 0 +Hidden dim: 512 (vs current 256) +Dataset: 10K groups +Target: 40%+ policy success + +epoch=1 train_loss=1.1239 val_loss=1.0284 val_rank_acc=0.802 val_progress_mae=0.234 +epoch=2 train_loss=1.0310 val_loss=1.0300 val_rank_acc=0.805 val_progress_mae=0.240 +epoch=3 train_loss=1.0055 val_loss=0.9767 val_rank_acc=0.818 val_progress_mae=0.238 +epoch=4 train_loss=0.9951 val_loss=0.9675 val_rank_acc=0.807 val_progress_mae=0.241 +epoch=5 train_loss=0.9769 val_loss=0.9748 val_rank_acc=0.817 val_progress_mae=0.241 +epoch=6 train_loss=0.9784 val_loss=0.9835 val_rank_acc=0.817 val_progress_mae=0.238 +epoch=7 train_loss=0.9460 val_loss=0.9588 val_rank_acc=0.825 val_progress_mae=0.228 +epoch=8 train_loss=0.9430 val_loss=0.9575 val_rank_acc=0.820 val_progress_mae=0.232 +epoch=9 train_loss=0.9335 val_loss=0.9430 val_rank_acc=0.827 val_progress_mae=0.234 +epoch=10 train_loss=0.9259 val_loss=0.9570 val_rank_acc=0.821 val_progress_mae=0.241 +epoch=11 train_loss=0.9361 val_loss=0.9456 val_rank_acc=0.822 val_progress_mae=0.232 +epoch=12 train_loss=0.9302 val_loss=0.9409 val_rank_acc=0.830 val_progress_mae=0.230 +epoch=13 train_loss=0.9233 val_loss=0.9437 val_rank_acc=0.825 val_progress_mae=0.229 +epoch=14 train_loss=0.9179 val_loss=0.9502 val_rank_acc=0.830 val_progress_mae=0.226 +epoch=15 train_loss=0.9094 val_loss=0.9173 val_rank_acc=0.835 val_progress_mae=0.227 +epoch=16 train_loss=0.9074 val_loss=0.9372 val_rank_acc=0.830 val_progress_mae=0.222 +epoch=17 train_loss=0.8975 val_loss=0.9273 val_rank_acc=0.834 val_progress_mae=0.225 +epoch=18 train_loss=0.9026 val_loss=0.9253 val_rank_acc=0.828 val_progress_mae=0.228 +epoch=19 train_loss=0.8989 val_loss=0.9315 val_rank_acc=0.828 val_progress_mae=0.220 +epoch=20 train_loss=0.8959 val_loss=0.9614 val_rank_acc=0.830 val_progress_mae=0.232 +epoch=21 train_loss=0.8785 val_loss=0.9472 val_rank_acc=0.827 val_progress_mae=0.228 +epoch=22 train_loss=0.8702 val_loss=0.9268 val_rank_acc=0.831 val_progress_mae=0.229 +epoch=23 train_loss=0.8870 val_loss=0.9123 val_rank_acc=0.840 val_progress_mae=0.225 +epoch=24 train_loss=0.8729 val_loss=0.9130 val_rank_acc=0.827 val_progress_mae=0.223 +epoch=25 train_loss=0.8809 val_loss=0.9151 val_rank_acc=0.834 val_progress_mae=0.224 +epoch=26 train_loss=0.8711 val_loss=0.9195 val_rank_acc=0.835 val_progress_mae=0.222 +epoch=27 train_loss=0.8650 val_loss=0.9277 val_rank_acc=0.836 val_progress_mae=0.219 +epoch=28 train_loss=0.8587 val_loss=0.9402 val_rank_acc=0.832 val_progress_mae=0.223 +epoch=29 train_loss=0.8566 val_loss=0.9421 val_rank_acc=0.826 val_progress_mae=0.226 +epoch=30 train_loss=0.8378 val_loss=0.9277 val_rank_acc=0.833 val_progress_mae=0.226 +epoch=31 train_loss=0.8458 val_loss=0.9135 val_rank_acc=0.830 val_progress_mae=0.232 +epoch=32 train_loss=0.8392 val_loss=0.9099 val_rank_acc=0.830 val_progress_mae=0.229 +epoch=33 train_loss=0.8453 val_loss=0.9431 val_rank_acc=0.822 val_progress_mae=0.225 +epoch=34 train_loss=0.8310 val_loss=0.9217 val_rank_acc=0.831 val_progress_mae=0.226 +epoch=35 train_loss=0.8444 val_loss=0.9383 val_rank_acc=0.833 val_progress_mae=0.221 +epoch=36 train_loss=0.8346 val_loss=0.9222 val_rank_acc=0.834 val_progress_mae=0.227 +epoch=37 train_loss=0.8244 val_loss=0.9193 val_rank_acc=0.838 val_progress_mae=0.227 +epoch=38 train_loss=0.8259 val_loss=0.9445 val_rank_acc=0.828 val_progress_mae=0.226 +epoch=39 train_loss=0.8183 val_loss=0.9040 val_rank_acc=0.834 val_progress_mae=0.230 +epoch=40 train_loss=0.8196 val_loss=0.9358 val_rank_acc=0.825 val_progress_mae=0.227 +epoch=41 train_loss=0.8283 val_loss=0.9284 val_rank_acc=0.836 val_progress_mae=0.226 +epoch=42 train_loss=0.8156 val_loss=0.9185 val_rank_acc=0.829 val_progress_mae=0.224 +epoch=43 train_loss=0.8127 val_loss=0.9264 val_rank_acc=0.825 val_progress_mae=0.226 +epoch=44 train_loss=0.8086 val_loss=0.9505 val_rank_acc=0.831 val_progress_mae=0.226 +epoch=45 train_loss=0.8069 val_loss=0.9323 val_rank_acc=0.838 val_progress_mae=0.231 +epoch=46 train_loss=0.8006 val_loss=0.9439 val_rank_acc=0.826 val_progress_mae=0.227 +epoch=47 train_loss=0.8102 val_loss=0.9258 val_rank_acc=0.831 val_progress_mae=0.223 +epoch=48 train_loss=0.8006 val_loss=0.9335 val_rank_acc=0.833 val_progress_mae=0.227 +epoch=49 train_loss=0.7949 val_loss=0.9280 val_rank_acc=0.834 val_progress_mae=0.231 +epoch=50 train_loss=0.7876 val_loss=0.9668 val_rank_acc=0.824 val_progress_mae=0.230 +epoch=51 train_loss=0.7902 val_loss=0.9475 val_rank_acc=0.824 val_progress_mae=0.227 +epoch=52 train_loss=0.7765 val_loss=0.9384 val_rank_acc=0.836 val_progress_mae=0.225 +epoch=53 train_loss=0.7758 val_loss=0.9388 val_rank_acc=0.831 val_progress_mae=0.227 +epoch=54 train_loss=0.7877 val_loss=0.9425 val_rank_acc=0.835 val_progress_mae=0.229 +epoch=55 train_loss=0.7887 val_loss=0.9578 val_rank_acc=0.827 val_progress_mae=0.230 +epoch=56 train_loss=0.7839 val_loss=0.9568 val_rank_acc=0.826 val_progress_mae=0.226 +epoch=57 train_loss=0.7730 val_loss=0.9540 val_rank_acc=0.830 val_progress_mae=0.227 +epoch=58 train_loss=0.7856 val_loss=0.9301 val_rank_acc=0.832 val_progress_mae=0.228 +epoch=59 train_loss=0.7671 val_loss=0.9570 val_rank_acc=0.826 val_progress_mae=0.222 +epoch=60 train_loss=0.7630 val_loss=0.9447 val_rank_acc=0.821 val_progress_mae=0.222 +epoch=61 train_loss=0.7584 val_loss=0.9473 val_rank_acc=0.828 val_progress_mae=0.227 +epoch=62 train_loss=0.7660 val_loss=0.9451 val_rank_acc=0.829 val_progress_mae=0.226 +epoch=63 train_loss=0.7478 val_loss=0.9684 val_rank_acc=0.826 val_progress_mae=0.223 +epoch=64 train_loss=0.7575 val_loss=0.9814 val_rank_acc=0.824 val_progress_mae=0.230 +epoch=65 train_loss=0.7549 val_loss=0.9607 val_rank_acc=0.822 val_progress_mae=0.226 +epoch=66 train_loss=0.7579 val_loss=0.9559 val_rank_acc=0.827 val_progress_mae=0.228 +epoch=67 train_loss=0.7461 val_loss=0.9762 val_rank_acc=0.828 val_progress_mae=0.224 +epoch=68 train_loss=0.7391 val_loss=0.9641 val_rank_acc=0.822 val_progress_mae=0.227 +epoch=69 train_loss=0.7405 val_loss=0.9459 val_rank_acc=0.821 val_progress_mae=0.229 +epoch=70 train_loss=0.7330 val_loss=0.9625 val_rank_acc=0.826 val_progress_mae=0.228 +epoch=71 train_loss=0.7507 val_loss=0.9804 val_rank_acc=0.822 val_progress_mae=0.229 +epoch=72 train_loss=0.7506 val_loss=0.9550 val_rank_acc=0.828 val_progress_mae=0.226 +epoch=73 train_loss=0.7484 val_loss=0.9688 val_rank_acc=0.820 val_progress_mae=0.232 +epoch=74 train_loss=0.7419 val_loss=0.9948 val_rank_acc=0.828 val_progress_mae=0.225 +epoch=75 train_loss=0.7348 val_loss=0.9583 val_rank_acc=0.823 val_progress_mae=0.228 +epoch=76 train_loss=0.7205 val_loss=0.9667 val_rank_acc=0.824 val_progress_mae=0.223 +epoch=77 train_loss=0.7324 val_loss=0.9531 val_rank_acc=0.825 val_progress_mae=0.225 +epoch=78 train_loss=0.7258 val_loss=0.9735 val_rank_acc=0.821 val_progress_mae=0.225 +epoch=79 train_loss=0.7287 val_loss=0.9565 val_rank_acc=0.826 val_progress_mae=0.228 +epoch=80 train_loss=0.7178 val_loss=0.9855 val_rank_acc=0.828 val_progress_mae=0.226 +epoch=81 train_loss=0.7205 val_loss=0.9676 val_rank_acc=0.828 val_progress_mae=0.227 +epoch=82 train_loss=0.7171 val_loss=0.9573 val_rank_acc=0.827 val_progress_mae=0.230 +epoch=83 train_loss=0.7241 val_loss=0.9949 val_rank_acc=0.827 val_progress_mae=0.230 +epoch=84 train_loss=0.7245 val_loss=0.9914 val_rank_acc=0.816 val_progress_mae=0.232 +epoch=85 train_loss=0.7143 val_loss=1.0077 val_rank_acc=0.819 val_progress_mae=0.228 +epoch=86 train_loss=0.7015 val_loss=0.9901 val_rank_acc=0.818 val_progress_mae=0.227 +epoch=87 train_loss=0.7250 val_loss=0.9858 val_rank_acc=0.818 val_progress_mae=0.228 +epoch=88 train_loss=0.7066 val_loss=1.0102 val_rank_acc=0.817 val_progress_mae=0.225 +epoch=89 train_loss=0.7132 val_loss=0.9787 val_rank_acc=0.818 val_progress_mae=0.221 +epoch=90 train_loss=0.7089 val_loss=0.9784 val_rank_acc=0.819 val_progress_mae=0.230 +epoch=91 train_loss=0.7016 val_loss=0.9859 val_rank_acc=0.816 val_progress_mae=0.225 +epoch=92 train_loss=0.6978 val_loss=0.9902 val_rank_acc=0.819 val_progress_mae=0.230 +epoch=93 train_loss=0.7001 val_loss=0.9790 val_rank_acc=0.816 val_progress_mae=0.225 +epoch=94 train_loss=0.7157 val_loss=1.0007 val_rank_acc=0.817 val_progress_mae=0.226 +epoch=95 train_loss=0.7124 val_loss=0.9774 val_rank_acc=0.821 val_progress_mae=0.230 +epoch=96 train_loss=0.7004 val_loss=0.9631 val_rank_acc=0.820 val_progress_mae=0.232 +epoch=97 train_loss=0.7048 val_loss=0.9688 val_rank_acc=0.817 val_progress_mae=0.229 +epoch=98 train_loss=0.6952 val_loss=0.9991 val_rank_acc=0.812 val_progress_mae=0.232 +epoch=99 train_loss=0.6991 val_loss=0.9500 val_rank_acc=0.821 val_progress_mae=0.227 +epoch=100 train_loss=0.7019 val_loss=0.9524 val_rank_acc=0.820 val_progress_mae=0.226 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a2_large_model/seed_0 +best val rank_acc=0.8401 + +✅ Phase A2 complete: Large model trained (seed 0) + +Next: Run phase_a3_eval_large_model.sbatch diff --git a/workspace/logs/phase_a2_large_train_14624321.err b/workspace/logs/phase_a2_large_train_14624321.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/phase_a2_large_train_14624321.out b/workspace/logs/phase_a2_large_train_14624321.out new file mode 100644 index 0000000000000000000000000000000000000000..075c8b18e34f0ddb9c551e610edc1d7387a97dfc --- /dev/null +++ b/workspace/logs/phase_a2_large_train_14624321.out @@ -0,0 +1,112 @@ +=== Phase A2: Training Large Capacity Model === +Seed: 1 +Hidden dim: 512 (vs current 256) +Dataset: 10K groups +Target: 40%+ policy success + +epoch=1 train_loss=1.1223 val_loss=1.0566 val_rank_acc=0.801 val_progress_mae=0.242 +epoch=2 train_loss=1.0177 val_loss=1.0096 val_rank_acc=0.810 val_progress_mae=0.254 +epoch=3 train_loss=0.9926 val_loss=1.0298 val_rank_acc=0.811 val_progress_mae=0.256 +epoch=4 train_loss=0.9852 val_loss=0.9775 val_rank_acc=0.797 val_progress_mae=0.243 +epoch=5 train_loss=0.9729 val_loss=0.9660 val_rank_acc=0.812 val_progress_mae=0.234 +epoch=6 train_loss=0.9633 val_loss=0.9754 val_rank_acc=0.805 val_progress_mae=0.244 +epoch=7 train_loss=0.9625 val_loss=0.9755 val_rank_acc=0.805 val_progress_mae=0.243 +epoch=8 train_loss=0.9471 val_loss=0.9657 val_rank_acc=0.820 val_progress_mae=0.247 +epoch=9 train_loss=0.9401 val_loss=0.9866 val_rank_acc=0.824 val_progress_mae=0.245 +epoch=10 train_loss=0.9259 val_loss=0.9748 val_rank_acc=0.821 val_progress_mae=0.247 +epoch=11 train_loss=0.9304 val_loss=0.9587 val_rank_acc=0.826 val_progress_mae=0.249 +epoch=12 train_loss=0.9198 val_loss=0.9606 val_rank_acc=0.822 val_progress_mae=0.250 +epoch=13 train_loss=0.9146 val_loss=0.9795 val_rank_acc=0.821 val_progress_mae=0.244 +epoch=14 train_loss=0.9005 val_loss=0.9648 val_rank_acc=0.823 val_progress_mae=0.237 +epoch=15 train_loss=0.9120 val_loss=0.9245 val_rank_acc=0.827 val_progress_mae=0.235 +epoch=16 train_loss=0.8770 val_loss=0.9437 val_rank_acc=0.818 val_progress_mae=0.243 +epoch=17 train_loss=0.9023 val_loss=0.9525 val_rank_acc=0.825 val_progress_mae=0.246 +epoch=18 train_loss=0.8906 val_loss=0.9118 val_rank_acc=0.830 val_progress_mae=0.243 +epoch=19 train_loss=0.8905 val_loss=0.9343 val_rank_acc=0.829 val_progress_mae=0.228 +epoch=20 train_loss=0.8876 val_loss=0.9573 val_rank_acc=0.831 val_progress_mae=0.244 +epoch=21 train_loss=0.8816 val_loss=0.9155 val_rank_acc=0.826 val_progress_mae=0.243 +epoch=22 train_loss=0.8678 val_loss=0.9311 val_rank_acc=0.832 val_progress_mae=0.244 +epoch=23 train_loss=0.8716 val_loss=0.9517 val_rank_acc=0.829 val_progress_mae=0.239 +epoch=24 train_loss=0.8690 val_loss=0.9350 val_rank_acc=0.824 val_progress_mae=0.238 +epoch=25 train_loss=0.8688 val_loss=0.9505 val_rank_acc=0.825 val_progress_mae=0.237 +epoch=26 train_loss=0.8377 val_loss=0.9363 val_rank_acc=0.825 val_progress_mae=0.245 +epoch=27 train_loss=0.8543 val_loss=0.9297 val_rank_acc=0.821 val_progress_mae=0.241 +epoch=28 train_loss=0.8425 val_loss=0.9425 val_rank_acc=0.827 val_progress_mae=0.235 +epoch=29 train_loss=0.8535 val_loss=0.9390 val_rank_acc=0.827 val_progress_mae=0.244 +epoch=30 train_loss=0.8505 val_loss=0.9393 val_rank_acc=0.826 val_progress_mae=0.236 +epoch=31 train_loss=0.8459 val_loss=0.9191 val_rank_acc=0.826 val_progress_mae=0.243 +epoch=32 train_loss=0.8372 val_loss=0.9188 val_rank_acc=0.830 val_progress_mae=0.237 +epoch=33 train_loss=0.8330 val_loss=0.9546 val_rank_acc=0.828 val_progress_mae=0.241 +epoch=34 train_loss=0.8301 val_loss=0.9676 val_rank_acc=0.820 val_progress_mae=0.234 +epoch=35 train_loss=0.8344 val_loss=0.9377 val_rank_acc=0.829 val_progress_mae=0.242 +epoch=36 train_loss=0.8324 val_loss=0.9381 val_rank_acc=0.821 val_progress_mae=0.232 +epoch=37 train_loss=0.8287 val_loss=0.9191 val_rank_acc=0.827 val_progress_mae=0.236 +epoch=38 train_loss=0.8278 val_loss=0.9581 val_rank_acc=0.825 val_progress_mae=0.235 +epoch=39 train_loss=0.8224 val_loss=0.9541 val_rank_acc=0.831 val_progress_mae=0.246 +epoch=40 train_loss=0.8168 val_loss=0.9524 val_rank_acc=0.826 val_progress_mae=0.238 +epoch=41 train_loss=0.8108 val_loss=0.9430 val_rank_acc=0.830 val_progress_mae=0.242 +epoch=42 train_loss=0.8154 val_loss=0.9502 val_rank_acc=0.829 val_progress_mae=0.243 +epoch=43 train_loss=0.8152 val_loss=0.9332 val_rank_acc=0.828 val_progress_mae=0.238 +epoch=44 train_loss=0.7994 val_loss=0.9517 val_rank_acc=0.831 val_progress_mae=0.239 +epoch=45 train_loss=0.8039 val_loss=0.9504 val_rank_acc=0.824 val_progress_mae=0.240 +epoch=46 train_loss=0.7965 val_loss=0.9485 val_rank_acc=0.829 val_progress_mae=0.243 +epoch=47 train_loss=0.8065 val_loss=0.9243 val_rank_acc=0.832 val_progress_mae=0.241 +epoch=48 train_loss=0.7958 val_loss=0.9498 val_rank_acc=0.831 val_progress_mae=0.239 +epoch=49 train_loss=0.7927 val_loss=0.9403 val_rank_acc=0.830 val_progress_mae=0.244 +epoch=50 train_loss=0.7840 val_loss=0.9203 val_rank_acc=0.833 val_progress_mae=0.240 +epoch=51 train_loss=0.7900 val_loss=0.9467 val_rank_acc=0.831 val_progress_mae=0.247 +epoch=52 train_loss=0.7742 val_loss=0.9343 val_rank_acc=0.833 val_progress_mae=0.240 +epoch=53 train_loss=0.7869 val_loss=0.9558 val_rank_acc=0.829 val_progress_mae=0.240 +epoch=54 train_loss=0.7769 val_loss=0.9581 val_rank_acc=0.825 val_progress_mae=0.243 +epoch=55 train_loss=0.7784 val_loss=0.9480 val_rank_acc=0.822 val_progress_mae=0.239 +epoch=56 train_loss=0.7654 val_loss=0.9531 val_rank_acc=0.821 val_progress_mae=0.236 +epoch=57 train_loss=0.7666 val_loss=0.9513 val_rank_acc=0.823 val_progress_mae=0.244 +epoch=58 train_loss=0.7730 val_loss=0.9420 val_rank_acc=0.829 val_progress_mae=0.237 +epoch=59 train_loss=0.7720 val_loss=0.9783 val_rank_acc=0.827 val_progress_mae=0.248 +epoch=60 train_loss=0.7476 val_loss=0.9494 val_rank_acc=0.819 val_progress_mae=0.235 +epoch=61 train_loss=0.7642 val_loss=0.9446 val_rank_acc=0.823 val_progress_mae=0.240 +epoch=62 train_loss=0.7590 val_loss=0.9436 val_rank_acc=0.827 val_progress_mae=0.241 +epoch=63 train_loss=0.7595 val_loss=0.9773 val_rank_acc=0.820 val_progress_mae=0.242 +epoch=64 train_loss=0.7588 val_loss=0.9531 val_rank_acc=0.824 val_progress_mae=0.243 +epoch=65 train_loss=0.7662 val_loss=0.9599 val_rank_acc=0.825 val_progress_mae=0.237 +epoch=66 train_loss=0.7445 val_loss=0.9619 val_rank_acc=0.820 val_progress_mae=0.241 +epoch=67 train_loss=0.7544 val_loss=0.9466 val_rank_acc=0.823 val_progress_mae=0.242 +epoch=68 train_loss=0.7239 val_loss=0.9582 val_rank_acc=0.814 val_progress_mae=0.241 +epoch=69 train_loss=0.7349 val_loss=0.9217 val_rank_acc=0.826 val_progress_mae=0.240 +epoch=70 train_loss=0.7306 val_loss=0.9872 val_rank_acc=0.818 val_progress_mae=0.244 +epoch=71 train_loss=0.7439 val_loss=0.9640 val_rank_acc=0.820 val_progress_mae=0.240 +epoch=72 train_loss=0.7268 val_loss=0.9675 val_rank_acc=0.816 val_progress_mae=0.245 +epoch=73 train_loss=0.7412 val_loss=0.9626 val_rank_acc=0.820 val_progress_mae=0.242 +epoch=74 train_loss=0.7354 val_loss=0.9445 val_rank_acc=0.823 val_progress_mae=0.239 +epoch=75 train_loss=0.7321 val_loss=0.9401 val_rank_acc=0.820 val_progress_mae=0.239 +epoch=76 train_loss=0.7219 val_loss=0.9602 val_rank_acc=0.823 val_progress_mae=0.245 +epoch=77 train_loss=0.7267 val_loss=0.9530 val_rank_acc=0.816 val_progress_mae=0.242 +epoch=78 train_loss=0.7302 val_loss=0.9496 val_rank_acc=0.820 val_progress_mae=0.236 +epoch=79 train_loss=0.7292 val_loss=0.9582 val_rank_acc=0.824 val_progress_mae=0.245 +epoch=80 train_loss=0.7287 val_loss=0.9804 val_rank_acc=0.824 val_progress_mae=0.238 +epoch=81 train_loss=0.7223 val_loss=0.9740 val_rank_acc=0.821 val_progress_mae=0.239 +epoch=82 train_loss=0.7120 val_loss=0.9629 val_rank_acc=0.816 val_progress_mae=0.238 +epoch=83 train_loss=0.7253 val_loss=0.9712 val_rank_acc=0.821 val_progress_mae=0.241 +epoch=84 train_loss=0.7220 val_loss=0.9818 val_rank_acc=0.816 val_progress_mae=0.240 +epoch=85 train_loss=0.7164 val_loss=0.9541 val_rank_acc=0.821 val_progress_mae=0.238 +epoch=86 train_loss=0.7068 val_loss=0.9689 val_rank_acc=0.808 val_progress_mae=0.242 +epoch=87 train_loss=0.7015 val_loss=0.9601 val_rank_acc=0.821 val_progress_mae=0.239 +epoch=88 train_loss=0.7073 val_loss=0.9811 val_rank_acc=0.813 val_progress_mae=0.243 +epoch=89 train_loss=0.7135 val_loss=0.9511 val_rank_acc=0.815 val_progress_mae=0.243 +epoch=90 train_loss=0.7164 val_loss=0.9684 val_rank_acc=0.813 val_progress_mae=0.244 +epoch=91 train_loss=0.7046 val_loss=0.9706 val_rank_acc=0.818 val_progress_mae=0.238 +epoch=92 train_loss=0.7080 val_loss=0.9909 val_rank_acc=0.816 val_progress_mae=0.240 +epoch=93 train_loss=0.7001 val_loss=0.9745 val_rank_acc=0.817 val_progress_mae=0.242 +epoch=94 train_loss=0.7094 val_loss=0.9593 val_rank_acc=0.819 val_progress_mae=0.238 +epoch=95 train_loss=0.6982 val_loss=0.9685 val_rank_acc=0.818 val_progress_mae=0.238 +epoch=96 train_loss=0.7069 val_loss=0.9935 val_rank_acc=0.816 val_progress_mae=0.239 +epoch=97 train_loss=0.6953 val_loss=0.9658 val_rank_acc=0.821 val_progress_mae=0.234 +epoch=98 train_loss=0.6975 val_loss=0.9704 val_rank_acc=0.815 val_progress_mae=0.238 +epoch=99 train_loss=0.6946 val_loss=0.9824 val_rank_acc=0.816 val_progress_mae=0.242 +epoch=100 train_loss=0.6979 val_loss=0.9772 val_rank_acc=0.811 val_progress_mae=0.238 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a2_large_model/seed_1 +best val rank_acc=0.8331 + +✅ Phase A2 complete: Large model trained (seed 1) + +Next: Run phase_a3_eval_large_model.sbatch diff --git a/workspace/logs/phase_a4_hparam_14623006_0.err b/workspace/logs/phase_a4_hparam_14623006_0.err new file mode 100644 index 0000000000000000000000000000000000000000..7dcefb199ea7b8461d0c458972851674bddcb56f --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_0.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --warmup-steps 500 diff --git a/workspace/logs/phase_a4_hparam_14623006_0.out b/workspace/logs/phase_a4_hparam_14623006_0.out new file mode 100644 index 0000000000000000000000000000000000000000..a92c8243914f7bec1fbb97731cf05fe909c35131 --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_0.out @@ -0,0 +1,3 @@ +=== Phase A4: Hyperparameter Sweep === +Config 0: LR=0.0001, Hidden=256 + diff --git a/workspace/logs/phase_a4_hparam_14623006_1.err b/workspace/logs/phase_a4_hparam_14623006_1.err new file mode 100644 index 0000000000000000000000000000000000000000..7dcefb199ea7b8461d0c458972851674bddcb56f --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_1.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --warmup-steps 500 diff --git a/workspace/logs/phase_a4_hparam_14623006_1.out b/workspace/logs/phase_a4_hparam_14623006_1.out new file mode 100644 index 0000000000000000000000000000000000000000..4238e397c02bef8af0963f9ceeb8b7101d6b8066 --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_1.out @@ -0,0 +1,3 @@ +=== Phase A4: Hyperparameter Sweep === +Config 1: LR=0.0001, Hidden=512 + diff --git a/workspace/logs/phase_a4_hparam_14623006_2.err b/workspace/logs/phase_a4_hparam_14623006_2.err new file mode 100644 index 0000000000000000000000000000000000000000..7dcefb199ea7b8461d0c458972851674bddcb56f --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_2.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --warmup-steps 500 diff --git a/workspace/logs/phase_a4_hparam_14623006_2.out b/workspace/logs/phase_a4_hparam_14623006_2.out new file mode 100644 index 0000000000000000000000000000000000000000..2fd8ef4a3486522948ea993f2ab700397f3afc85 --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_2.out @@ -0,0 +1,3 @@ +=== Phase A4: Hyperparameter Sweep === +Config 2: LR=0.0001, Hidden=1024 + diff --git a/workspace/logs/phase_a4_hparam_14623006_3.err b/workspace/logs/phase_a4_hparam_14623006_3.err new file mode 100644 index 0000000000000000000000000000000000000000..7dcefb199ea7b8461d0c458972851674bddcb56f --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_3.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --warmup-steps 500 diff --git a/workspace/logs/phase_a4_hparam_14623006_3.out b/workspace/logs/phase_a4_hparam_14623006_3.out new file mode 100644 index 0000000000000000000000000000000000000000..e0b0b424b9b71837e788f13c5de7ed45b88dbe3f --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_3.out @@ -0,0 +1,3 @@ +=== Phase A4: Hyperparameter Sweep === +Config 3: LR=0.0003, Hidden=256 + diff --git a/workspace/logs/phase_a4_hparam_14623006_4.err b/workspace/logs/phase_a4_hparam_14623006_4.err new file mode 100644 index 0000000000000000000000000000000000000000..7dcefb199ea7b8461d0c458972851674bddcb56f --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_4.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --warmup-steps 500 diff --git a/workspace/logs/phase_a4_hparam_14623006_4.out b/workspace/logs/phase_a4_hparam_14623006_4.out new file mode 100644 index 0000000000000000000000000000000000000000..0b1a8152155a8c6f7762296e0192fd6a616c1abe --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_4.out @@ -0,0 +1,3 @@ +=== Phase A4: Hyperparameter Sweep === +Config 4: LR=0.0003, Hidden=512 + diff --git a/workspace/logs/phase_a4_hparam_14623006_5.err b/workspace/logs/phase_a4_hparam_14623006_5.err new file mode 100644 index 0000000000000000000000000000000000000000..7dcefb199ea7b8461d0c458972851674bddcb56f --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_5.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --warmup-steps 500 diff --git a/workspace/logs/phase_a4_hparam_14623006_5.out b/workspace/logs/phase_a4_hparam_14623006_5.out new file mode 100644 index 0000000000000000000000000000000000000000..9882db49da106abf363700e8ac80aac52a6ce18e --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_5.out @@ -0,0 +1,3 @@ +=== Phase A4: Hyperparameter Sweep === +Config 5: LR=0.0003, Hidden=1024 + diff --git a/workspace/logs/phase_a4_hparam_14623006_6.err b/workspace/logs/phase_a4_hparam_14623006_6.err new file mode 100644 index 0000000000000000000000000000000000000000..7dcefb199ea7b8461d0c458972851674bddcb56f --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_6.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --warmup-steps 500 diff --git a/workspace/logs/phase_a4_hparam_14623006_6.out b/workspace/logs/phase_a4_hparam_14623006_6.out new file mode 100644 index 0000000000000000000000000000000000000000..eabd2e704d1a0b019a91d742c10ef50418695114 --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_6.out @@ -0,0 +1,3 @@ +=== Phase A4: Hyperparameter Sweep === +Config 6: LR=0.001, Hidden=256 + diff --git a/workspace/logs/phase_a4_hparam_14623006_7.err b/workspace/logs/phase_a4_hparam_14623006_7.err new file mode 100644 index 0000000000000000000000000000000000000000..7dcefb199ea7b8461d0c458972851674bddcb56f --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_7.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --warmup-steps 500 diff --git a/workspace/logs/phase_a4_hparam_14623006_7.out b/workspace/logs/phase_a4_hparam_14623006_7.out new file mode 100644 index 0000000000000000000000000000000000000000..0f627fe44db0c3bf7954a255ec10d8bc71333c9e --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_7.out @@ -0,0 +1,3 @@ +=== Phase A4: Hyperparameter Sweep === +Config 7: LR=0.001, Hidden=512 + diff --git a/workspace/logs/phase_a4_hparam_14623006_8.err b/workspace/logs/phase_a4_hparam_14623006_8.err new file mode 100644 index 0000000000000000000000000000000000000000..7dcefb199ea7b8461d0c458972851674bddcb56f --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_8.err @@ -0,0 +1,20 @@ +usage: train_dovla.py [-h] --dataset DATASET --out OUT [--epochs EPOCHS] + [--batch-groups BATCH_GROUPS] + [--records-per-group RECORDS_PER_GROUP] + [--pair-count-per-group PAIR_COUNT_PER_GROUP] + [--hidden-dim HIDDEN_DIM] [--obs-dim OBS_DIM] + [--observation-mode {state,rgb}] [--lang-dim LANG_DIM] + [--action-dim ACTION_DIM] + [--action-horizon ACTION_HORIZON] + [--effect-dim EFFECT_DIM] [--backbone {native,clip}] + [--backbone-model BACKBONE_MODEL] [--finetune-backbone] + [--backbone-feature-cache BACKBONE_FEATURE_CACHE] + [--backbone-feature-batch-size BACKBONE_FEATURE_BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--device DEVICE] [--seed SEED] + [--val-fraction VAL_FRACTION] [--wandb] + [--objective {lattice_field,legacy}] + [--lattice-neighbors LATTICE_NEIGHBORS] + [--pair-scope {same_state,cross_state}] + [--loss-weight NAME=VALUE] +train_dovla.py: error: unrecognized arguments: --warmup-steps 500 diff --git a/workspace/logs/phase_a4_hparam_14623006_8.out b/workspace/logs/phase_a4_hparam_14623006_8.out new file mode 100644 index 0000000000000000000000000000000000000000..4ca5d14052de35811d6a21962139791e3af66ba8 --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623006_8.out @@ -0,0 +1,3 @@ +=== Phase A4: Hyperparameter Sweep === +Config 8: LR=0.001, Hidden=1024 + diff --git a/workspace/logs/phase_a4_hparam_14623493_0.err b/workspace/logs/phase_a4_hparam_14623493_0.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_0.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a4_hparam_14623493_0.out b/workspace/logs/phase_a4_hparam_14623493_0.out new file mode 100644 index 0000000000000000000000000000000000000000..9f282092c1f86ccac7d6c4fcb2e39db7c215a225 --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_0.out @@ -0,0 +1,56 @@ +=== Phase A4: Hyperparameter Sweep === +Config 0: LR=0.0001, Hidden=256 + +epoch=1 train_loss=1.5626 val_loss=1.3660 val_rank_acc=0.801 val_progress_mae=0.224 +epoch=2 train_loss=1.3533 val_loss=1.3538 val_rank_acc=0.810 val_progress_mae=0.235 +epoch=3 train_loss=1.3180 val_loss=1.2853 val_rank_acc=0.820 val_progress_mae=0.227 +epoch=4 train_loss=1.3085 val_loss=1.2788 val_rank_acc=0.811 val_progress_mae=0.239 +epoch=5 train_loss=1.2859 val_loss=1.2753 val_rank_acc=0.818 val_progress_mae=0.237 +epoch=6 train_loss=1.2899 val_loss=1.2989 val_rank_acc=0.814 val_progress_mae=0.233 +epoch=7 train_loss=1.2559 val_loss=1.2778 val_rank_acc=0.821 val_progress_mae=0.228 +epoch=8 train_loss=1.2545 val_loss=1.2739 val_rank_acc=0.817 val_progress_mae=0.233 +epoch=9 train_loss=1.2414 val_loss=1.2532 val_rank_acc=0.827 val_progress_mae=0.232 +epoch=10 train_loss=1.2347 val_loss=1.2719 val_rank_acc=0.819 val_progress_mae=0.233 +epoch=11 train_loss=1.2431 val_loss=1.2600 val_rank_acc=0.826 val_progress_mae=0.233 +epoch=12 train_loss=1.2398 val_loss=1.2576 val_rank_acc=0.826 val_progress_mae=0.231 +epoch=13 train_loss=1.2276 val_loss=1.2571 val_rank_acc=0.825 val_progress_mae=0.231 +epoch=14 train_loss=1.2259 val_loss=1.2583 val_rank_acc=0.825 val_progress_mae=0.234 +epoch=15 train_loss=1.2174 val_loss=1.2448 val_rank_acc=0.833 val_progress_mae=0.232 +epoch=16 train_loss=1.2137 val_loss=1.2540 val_rank_acc=0.830 val_progress_mae=0.233 +epoch=17 train_loss=1.2052 val_loss=1.2459 val_rank_acc=0.830 val_progress_mae=0.230 +epoch=18 train_loss=1.2062 val_loss=1.2380 val_rank_acc=0.824 val_progress_mae=0.232 +epoch=19 train_loss=1.2045 val_loss=1.2535 val_rank_acc=0.825 val_progress_mae=0.218 +epoch=20 train_loss=1.1997 val_loss=1.2731 val_rank_acc=0.825 val_progress_mae=0.237 +epoch=21 train_loss=1.1839 val_loss=1.2672 val_rank_acc=0.822 val_progress_mae=0.234 +epoch=22 train_loss=1.1772 val_loss=1.2503 val_rank_acc=0.830 val_progress_mae=0.236 +epoch=23 train_loss=1.1911 val_loss=1.2340 val_rank_acc=0.831 val_progress_mae=0.233 +epoch=24 train_loss=1.1778 val_loss=1.2249 val_rank_acc=0.823 val_progress_mae=0.227 +epoch=25 train_loss=1.1873 val_loss=1.2481 val_rank_acc=0.826 val_progress_mae=0.230 +epoch=26 train_loss=1.1760 val_loss=1.2292 val_rank_acc=0.830 val_progress_mae=0.231 +epoch=27 train_loss=1.1662 val_loss=1.2490 val_rank_acc=0.833 val_progress_mae=0.227 +epoch=28 train_loss=1.1612 val_loss=1.2505 val_rank_acc=0.831 val_progress_mae=0.228 +epoch=29 train_loss=1.1577 val_loss=1.2715 val_rank_acc=0.822 val_progress_mae=0.236 +epoch=30 train_loss=1.1439 val_loss=1.2390 val_rank_acc=0.826 val_progress_mae=0.230 +epoch=31 train_loss=1.1445 val_loss=1.2361 val_rank_acc=0.829 val_progress_mae=0.235 +epoch=32 train_loss=1.1406 val_loss=1.2396 val_rank_acc=0.825 val_progress_mae=0.236 +epoch=33 train_loss=1.1497 val_loss=1.2644 val_rank_acc=0.821 val_progress_mae=0.236 +epoch=34 train_loss=1.1342 val_loss=1.2427 val_rank_acc=0.822 val_progress_mae=0.232 +epoch=35 train_loss=1.1462 val_loss=1.2486 val_rank_acc=0.834 val_progress_mae=0.229 +epoch=36 train_loss=1.1357 val_loss=1.2445 val_rank_acc=0.821 val_progress_mae=0.236 +epoch=37 train_loss=1.1286 val_loss=1.2321 val_rank_acc=0.829 val_progress_mae=0.233 +epoch=38 train_loss=1.1314 val_loss=1.2507 val_rank_acc=0.820 val_progress_mae=0.229 +epoch=39 train_loss=1.1172 val_loss=1.2188 val_rank_acc=0.829 val_progress_mae=0.232 +epoch=40 train_loss=1.1205 val_loss=1.2435 val_rank_acc=0.825 val_progress_mae=0.228 +epoch=41 train_loss=1.1251 val_loss=1.2378 val_rank_acc=0.828 val_progress_mae=0.230 +epoch=42 train_loss=1.1167 val_loss=1.2468 val_rank_acc=0.821 val_progress_mae=0.225 +epoch=43 train_loss=1.1151 val_loss=1.2528 val_rank_acc=0.816 val_progress_mae=0.227 +epoch=44 train_loss=1.1094 val_loss=1.2543 val_rank_acc=0.823 val_progress_mae=0.232 +epoch=45 train_loss=1.1095 val_loss=1.2525 val_rank_acc=0.821 val_progress_mae=0.239 +epoch=46 train_loss=1.1026 val_loss=1.2590 val_rank_acc=0.824 val_progress_mae=0.231 +epoch=47 train_loss=1.1073 val_loss=1.2433 val_rank_acc=0.820 val_progress_mae=0.230 +epoch=48 train_loss=1.1012 val_loss=1.2561 val_rank_acc=0.821 val_progress_mae=0.232 +epoch=49 train_loss=1.0918 val_loss=1.2314 val_rank_acc=0.826 val_progress_mae=0.234 +epoch=50 train_loss=1.0863 val_loss=1.2652 val_rank_acc=0.824 val_progress_mae=0.232 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0001_h256 +best val rank_acc=0.8337 + diff --git a/workspace/logs/phase_a4_hparam_14623493_1.err b/workspace/logs/phase_a4_hparam_14623493_1.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_1.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a4_hparam_14623493_1.out b/workspace/logs/phase_a4_hparam_14623493_1.out new file mode 100644 index 0000000000000000000000000000000000000000..f9c9b8a35ea421ef695b8b5a5632e3c5f3d89d8c --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_1.out @@ -0,0 +1,56 @@ +=== Phase A4: Hyperparameter Sweep === +Config 1: LR=0.0001, Hidden=512 + +epoch=1 train_loss=1.4799 val_loss=1.3593 val_rank_acc=0.804 val_progress_mae=0.233 +epoch=2 train_loss=1.3486 val_loss=1.3503 val_rank_acc=0.811 val_progress_mae=0.228 +epoch=3 train_loss=1.3164 val_loss=1.2853 val_rank_acc=0.819 val_progress_mae=0.232 +epoch=4 train_loss=1.3060 val_loss=1.2778 val_rank_acc=0.815 val_progress_mae=0.241 +epoch=5 train_loss=1.2841 val_loss=1.2772 val_rank_acc=0.823 val_progress_mae=0.233 +epoch=6 train_loss=1.2842 val_loss=1.2939 val_rank_acc=0.818 val_progress_mae=0.234 +epoch=7 train_loss=1.2509 val_loss=1.2727 val_rank_acc=0.826 val_progress_mae=0.228 +epoch=8 train_loss=1.2486 val_loss=1.2685 val_rank_acc=0.819 val_progress_mae=0.233 +epoch=9 train_loss=1.2360 val_loss=1.2582 val_rank_acc=0.825 val_progress_mae=0.234 +epoch=10 train_loss=1.2285 val_loss=1.2662 val_rank_acc=0.824 val_progress_mae=0.238 +epoch=11 train_loss=1.2384 val_loss=1.2601 val_rank_acc=0.829 val_progress_mae=0.232 +epoch=12 train_loss=1.2332 val_loss=1.2553 val_rank_acc=0.831 val_progress_mae=0.231 +epoch=13 train_loss=1.2220 val_loss=1.2511 val_rank_acc=0.830 val_progress_mae=0.231 +epoch=14 train_loss=1.2190 val_loss=1.2574 val_rank_acc=0.829 val_progress_mae=0.232 +epoch=15 train_loss=1.2093 val_loss=1.2364 val_rank_acc=0.837 val_progress_mae=0.232 +epoch=16 train_loss=1.2063 val_loss=1.2538 val_rank_acc=0.832 val_progress_mae=0.230 +epoch=17 train_loss=1.1978 val_loss=1.2450 val_rank_acc=0.837 val_progress_mae=0.231 +epoch=18 train_loss=1.1991 val_loss=1.2354 val_rank_acc=0.829 val_progress_mae=0.231 +epoch=19 train_loss=1.1957 val_loss=1.2479 val_rank_acc=0.830 val_progress_mae=0.221 +epoch=20 train_loss=1.1910 val_loss=1.2733 val_rank_acc=0.833 val_progress_mae=0.235 +epoch=21 train_loss=1.1747 val_loss=1.2615 val_rank_acc=0.829 val_progress_mae=0.230 +epoch=22 train_loss=1.1652 val_loss=1.2507 val_rank_acc=0.829 val_progress_mae=0.236 +epoch=23 train_loss=1.1834 val_loss=1.2342 val_rank_acc=0.837 val_progress_mae=0.228 +epoch=24 train_loss=1.1667 val_loss=1.2285 val_rank_acc=0.829 val_progress_mae=0.233 +epoch=25 train_loss=1.1764 val_loss=1.2419 val_rank_acc=0.834 val_progress_mae=0.231 +epoch=26 train_loss=1.1645 val_loss=1.2339 val_rank_acc=0.832 val_progress_mae=0.225 +epoch=27 train_loss=1.1557 val_loss=1.2452 val_rank_acc=0.837 val_progress_mae=0.225 +epoch=28 train_loss=1.1510 val_loss=1.2609 val_rank_acc=0.832 val_progress_mae=0.231 +epoch=29 train_loss=1.1468 val_loss=1.2659 val_rank_acc=0.829 val_progress_mae=0.232 +epoch=30 train_loss=1.1311 val_loss=1.2401 val_rank_acc=0.832 val_progress_mae=0.227 +epoch=31 train_loss=1.1323 val_loss=1.2298 val_rank_acc=0.830 val_progress_mae=0.237 +epoch=32 train_loss=1.1269 val_loss=1.2362 val_rank_acc=0.832 val_progress_mae=0.235 +epoch=33 train_loss=1.1358 val_loss=1.2655 val_rank_acc=0.823 val_progress_mae=0.231 +epoch=34 train_loss=1.1207 val_loss=1.2447 val_rank_acc=0.824 val_progress_mae=0.232 +epoch=35 train_loss=1.1317 val_loss=1.2500 val_rank_acc=0.836 val_progress_mae=0.225 +epoch=36 train_loss=1.1220 val_loss=1.2426 val_rank_acc=0.825 val_progress_mae=0.232 +epoch=37 train_loss=1.1140 val_loss=1.2345 val_rank_acc=0.835 val_progress_mae=0.235 +epoch=38 train_loss=1.1158 val_loss=1.2545 val_rank_acc=0.822 val_progress_mae=0.232 +epoch=39 train_loss=1.1030 val_loss=1.2175 val_rank_acc=0.833 val_progress_mae=0.231 +epoch=40 train_loss=1.1041 val_loss=1.2472 val_rank_acc=0.826 val_progress_mae=0.229 +epoch=41 train_loss=1.1109 val_loss=1.2406 val_rank_acc=0.830 val_progress_mae=0.223 +epoch=42 train_loss=1.1015 val_loss=1.2437 val_rank_acc=0.823 val_progress_mae=0.222 +epoch=43 train_loss=1.0998 val_loss=1.2542 val_rank_acc=0.823 val_progress_mae=0.227 +epoch=44 train_loss=1.0939 val_loss=1.2579 val_rank_acc=0.826 val_progress_mae=0.233 +epoch=45 train_loss=1.0929 val_loss=1.2494 val_rank_acc=0.827 val_progress_mae=0.233 +epoch=46 train_loss=1.0825 val_loss=1.2652 val_rank_acc=0.828 val_progress_mae=0.231 +epoch=47 train_loss=1.0928 val_loss=1.2491 val_rank_acc=0.825 val_progress_mae=0.228 +epoch=48 train_loss=1.0846 val_loss=1.2621 val_rank_acc=0.826 val_progress_mae=0.231 +epoch=49 train_loss=1.0755 val_loss=1.2482 val_rank_acc=0.829 val_progress_mae=0.232 +epoch=50 train_loss=1.0696 val_loss=1.2832 val_rank_acc=0.820 val_progress_mae=0.227 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0001_h512 +best val rank_acc=0.8374 + diff --git a/workspace/logs/phase_a4_hparam_14623493_2.err b/workspace/logs/phase_a4_hparam_14623493_2.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_2.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a4_hparam_14623493_2.out b/workspace/logs/phase_a4_hparam_14623493_2.out new file mode 100644 index 0000000000000000000000000000000000000000..e63242085f659923a4b212164066c56c042e60d7 --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_2.out @@ -0,0 +1,56 @@ +=== Phase A4: Hyperparameter Sweep === +Config 2: LR=0.0001, Hidden=1024 + +epoch=1 train_loss=1.4622 val_loss=1.3592 val_rank_acc=0.803 val_progress_mae=0.227 +epoch=2 train_loss=1.3519 val_loss=1.3490 val_rank_acc=0.804 val_progress_mae=0.226 +epoch=3 train_loss=1.3193 val_loss=1.2850 val_rank_acc=0.817 val_progress_mae=0.230 +epoch=4 train_loss=1.3106 val_loss=1.2863 val_rank_acc=0.811 val_progress_mae=0.239 +epoch=5 train_loss=1.2900 val_loss=1.2819 val_rank_acc=0.825 val_progress_mae=0.236 +epoch=6 train_loss=1.2879 val_loss=1.2950 val_rank_acc=0.823 val_progress_mae=0.238 +epoch=7 train_loss=1.2572 val_loss=1.2753 val_rank_acc=0.828 val_progress_mae=0.227 +epoch=8 train_loss=1.2546 val_loss=1.2729 val_rank_acc=0.822 val_progress_mae=0.234 +epoch=9 train_loss=1.2419 val_loss=1.2650 val_rank_acc=0.822 val_progress_mae=0.231 +epoch=10 train_loss=1.2349 val_loss=1.2728 val_rank_acc=0.822 val_progress_mae=0.233 +epoch=11 train_loss=1.2445 val_loss=1.2643 val_rank_acc=0.828 val_progress_mae=0.236 +epoch=12 train_loss=1.2389 val_loss=1.2591 val_rank_acc=0.831 val_progress_mae=0.229 +epoch=13 train_loss=1.2295 val_loss=1.2532 val_rank_acc=0.835 val_progress_mae=0.232 +epoch=14 train_loss=1.2251 val_loss=1.2609 val_rank_acc=0.831 val_progress_mae=0.230 +epoch=15 train_loss=1.2146 val_loss=1.2448 val_rank_acc=0.838 val_progress_mae=0.230 +epoch=16 train_loss=1.2137 val_loss=1.2527 val_rank_acc=0.833 val_progress_mae=0.230 +epoch=17 train_loss=1.2039 val_loss=1.2420 val_rank_acc=0.837 val_progress_mae=0.233 +epoch=18 train_loss=1.2052 val_loss=1.2361 val_rank_acc=0.831 val_progress_mae=0.233 +epoch=19 train_loss=1.2016 val_loss=1.2484 val_rank_acc=0.830 val_progress_mae=0.224 +epoch=20 train_loss=1.1989 val_loss=1.2701 val_rank_acc=0.835 val_progress_mae=0.231 +epoch=21 train_loss=1.1793 val_loss=1.2642 val_rank_acc=0.831 val_progress_mae=0.225 +epoch=22 train_loss=1.1707 val_loss=1.2507 val_rank_acc=0.835 val_progress_mae=0.235 +epoch=23 train_loss=1.1878 val_loss=1.2323 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=24 train_loss=1.1719 val_loss=1.2255 val_rank_acc=0.831 val_progress_mae=0.230 +epoch=25 train_loss=1.1810 val_loss=1.2392 val_rank_acc=0.836 val_progress_mae=0.230 +epoch=26 train_loss=1.1695 val_loss=1.2345 val_rank_acc=0.835 val_progress_mae=0.227 +epoch=27 train_loss=1.1600 val_loss=1.2426 val_rank_acc=0.840 val_progress_mae=0.225 +epoch=28 train_loss=1.1551 val_loss=1.2523 val_rank_acc=0.832 val_progress_mae=0.227 +epoch=29 train_loss=1.1538 val_loss=1.2586 val_rank_acc=0.830 val_progress_mae=0.230 +epoch=30 train_loss=1.1333 val_loss=1.2422 val_rank_acc=0.834 val_progress_mae=0.226 +epoch=31 train_loss=1.1378 val_loss=1.2268 val_rank_acc=0.833 val_progress_mae=0.235 +epoch=32 train_loss=1.1329 val_loss=1.2321 val_rank_acc=0.835 val_progress_mae=0.232 +epoch=33 train_loss=1.1400 val_loss=1.2734 val_rank_acc=0.821 val_progress_mae=0.234 +epoch=34 train_loss=1.1235 val_loss=1.2303 val_rank_acc=0.832 val_progress_mae=0.228 +epoch=35 train_loss=1.1361 val_loss=1.2446 val_rank_acc=0.836 val_progress_mae=0.226 +epoch=36 train_loss=1.1262 val_loss=1.2474 val_rank_acc=0.828 val_progress_mae=0.230 +epoch=37 train_loss=1.1163 val_loss=1.2398 val_rank_acc=0.834 val_progress_mae=0.231 +epoch=38 train_loss=1.1185 val_loss=1.2576 val_rank_acc=0.824 val_progress_mae=0.232 +epoch=39 train_loss=1.1067 val_loss=1.2128 val_rank_acc=0.834 val_progress_mae=0.230 +epoch=40 train_loss=1.1083 val_loss=1.2452 val_rank_acc=0.832 val_progress_mae=0.231 +epoch=41 train_loss=1.1161 val_loss=1.2397 val_rank_acc=0.836 val_progress_mae=0.223 +epoch=42 train_loss=1.1009 val_loss=1.2445 val_rank_acc=0.829 val_progress_mae=0.226 +epoch=43 train_loss=1.1035 val_loss=1.2413 val_rank_acc=0.826 val_progress_mae=0.224 +epoch=44 train_loss=1.0983 val_loss=1.2615 val_rank_acc=0.831 val_progress_mae=0.231 +epoch=45 train_loss=1.0966 val_loss=1.2456 val_rank_acc=0.832 val_progress_mae=0.233 +epoch=46 train_loss=1.0850 val_loss=1.2666 val_rank_acc=0.831 val_progress_mae=0.231 +epoch=47 train_loss=1.0964 val_loss=1.2433 val_rank_acc=0.832 val_progress_mae=0.231 +epoch=48 train_loss=1.0858 val_loss=1.2594 val_rank_acc=0.830 val_progress_mae=0.231 +epoch=49 train_loss=1.0779 val_loss=1.2554 val_rank_acc=0.831 val_progress_mae=0.232 +epoch=50 train_loss=1.0749 val_loss=1.2832 val_rank_acc=0.826 val_progress_mae=0.232 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0001_h1024 +best val rank_acc=0.8399 + diff --git a/workspace/logs/phase_a4_hparam_14623493_3.err b/workspace/logs/phase_a4_hparam_14623493_3.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_3.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a4_hparam_14623493_3.out b/workspace/logs/phase_a4_hparam_14623493_3.out new file mode 100644 index 0000000000000000000000000000000000000000..8bb49b13772c5947629389e30d7ba3179dd51971 --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_3.out @@ -0,0 +1,56 @@ +=== Phase A4: Hyperparameter Sweep === +Config 3: LR=0.0003, Hidden=256 + +epoch=1 train_loss=1.4835 val_loss=1.3480 val_rank_acc=0.802 val_progress_mae=0.236 +epoch=2 train_loss=1.3433 val_loss=1.3357 val_rank_acc=0.811 val_progress_mae=0.239 +epoch=3 train_loss=1.3120 val_loss=1.2737 val_rank_acc=0.820 val_progress_mae=0.231 +epoch=4 train_loss=1.3024 val_loss=1.2683 val_rank_acc=0.814 val_progress_mae=0.236 +epoch=5 train_loss=1.2826 val_loss=1.2812 val_rank_acc=0.819 val_progress_mae=0.231 +epoch=6 train_loss=1.2846 val_loss=1.2882 val_rank_acc=0.820 val_progress_mae=0.238 +epoch=7 train_loss=1.2499 val_loss=1.2644 val_rank_acc=0.827 val_progress_mae=0.227 +epoch=8 train_loss=1.2491 val_loss=1.2667 val_rank_acc=0.820 val_progress_mae=0.232 +epoch=9 train_loss=1.2373 val_loss=1.2430 val_rank_acc=0.826 val_progress_mae=0.228 +epoch=10 train_loss=1.2299 val_loss=1.2591 val_rank_acc=0.824 val_progress_mae=0.232 +epoch=11 train_loss=1.2379 val_loss=1.2589 val_rank_acc=0.827 val_progress_mae=0.233 +epoch=12 train_loss=1.2335 val_loss=1.2498 val_rank_acc=0.831 val_progress_mae=0.229 +epoch=13 train_loss=1.2218 val_loss=1.2526 val_rank_acc=0.829 val_progress_mae=0.229 +epoch=14 train_loss=1.2180 val_loss=1.2507 val_rank_acc=0.829 val_progress_mae=0.233 +epoch=15 train_loss=1.2098 val_loss=1.2239 val_rank_acc=0.835 val_progress_mae=0.227 +epoch=16 train_loss=1.2049 val_loss=1.2510 val_rank_acc=0.832 val_progress_mae=0.231 +epoch=17 train_loss=1.1975 val_loss=1.2379 val_rank_acc=0.836 val_progress_mae=0.230 +epoch=18 train_loss=1.2006 val_loss=1.2340 val_rank_acc=0.829 val_progress_mae=0.230 +epoch=19 train_loss=1.1951 val_loss=1.2452 val_rank_acc=0.834 val_progress_mae=0.223 +epoch=20 train_loss=1.1934 val_loss=1.2795 val_rank_acc=0.829 val_progress_mae=0.230 +epoch=21 train_loss=1.1760 val_loss=1.2598 val_rank_acc=0.829 val_progress_mae=0.227 +epoch=22 train_loss=1.1664 val_loss=1.2421 val_rank_acc=0.832 val_progress_mae=0.233 +epoch=23 train_loss=1.1834 val_loss=1.2242 val_rank_acc=0.836 val_progress_mae=0.231 +epoch=24 train_loss=1.1697 val_loss=1.2266 val_rank_acc=0.831 val_progress_mae=0.226 +epoch=25 train_loss=1.1786 val_loss=1.2320 val_rank_acc=0.832 val_progress_mae=0.228 +epoch=26 train_loss=1.1656 val_loss=1.2284 val_rank_acc=0.835 val_progress_mae=0.232 +epoch=27 train_loss=1.1556 val_loss=1.2450 val_rank_acc=0.832 val_progress_mae=0.223 +epoch=28 train_loss=1.1532 val_loss=1.2507 val_rank_acc=0.833 val_progress_mae=0.227 +epoch=29 train_loss=1.1494 val_loss=1.2603 val_rank_acc=0.829 val_progress_mae=0.230 +epoch=30 train_loss=1.1329 val_loss=1.2337 val_rank_acc=0.827 val_progress_mae=0.226 +epoch=31 train_loss=1.1354 val_loss=1.2328 val_rank_acc=0.833 val_progress_mae=0.235 +epoch=32 train_loss=1.1301 val_loss=1.2294 val_rank_acc=0.832 val_progress_mae=0.236 +epoch=33 train_loss=1.1415 val_loss=1.2660 val_rank_acc=0.819 val_progress_mae=0.230 +epoch=34 train_loss=1.1245 val_loss=1.2347 val_rank_acc=0.829 val_progress_mae=0.229 +epoch=35 train_loss=1.1371 val_loss=1.2484 val_rank_acc=0.838 val_progress_mae=0.228 +epoch=36 train_loss=1.1269 val_loss=1.2342 val_rank_acc=0.829 val_progress_mae=0.226 +epoch=37 train_loss=1.1207 val_loss=1.2308 val_rank_acc=0.835 val_progress_mae=0.229 +epoch=38 train_loss=1.1212 val_loss=1.2423 val_rank_acc=0.827 val_progress_mae=0.230 +epoch=39 train_loss=1.1066 val_loss=1.2129 val_rank_acc=0.834 val_progress_mae=0.230 +epoch=40 train_loss=1.1099 val_loss=1.2428 val_rank_acc=0.834 val_progress_mae=0.225 +epoch=41 train_loss=1.1194 val_loss=1.2241 val_rank_acc=0.836 val_progress_mae=0.225 +epoch=42 train_loss=1.1064 val_loss=1.2331 val_rank_acc=0.827 val_progress_mae=0.225 +epoch=43 train_loss=1.1041 val_loss=1.2447 val_rank_acc=0.822 val_progress_mae=0.225 +epoch=44 train_loss=1.1000 val_loss=1.2629 val_rank_acc=0.826 val_progress_mae=0.229 +epoch=45 train_loss=1.0993 val_loss=1.2481 val_rank_acc=0.826 val_progress_mae=0.230 +epoch=46 train_loss=1.0908 val_loss=1.2726 val_rank_acc=0.821 val_progress_mae=0.227 +epoch=47 train_loss=1.0993 val_loss=1.2330 val_rank_acc=0.831 val_progress_mae=0.225 +epoch=48 train_loss=1.0928 val_loss=1.2500 val_rank_acc=0.831 val_progress_mae=0.228 +epoch=49 train_loss=1.0826 val_loss=1.2392 val_rank_acc=0.834 val_progress_mae=0.233 +epoch=50 train_loss=1.0786 val_loss=1.2682 val_rank_acc=0.826 val_progress_mae=0.229 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0003_h256 +best val rank_acc=0.8382 + diff --git a/workspace/logs/phase_a4_hparam_14623493_4.err b/workspace/logs/phase_a4_hparam_14623493_4.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_4.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a4_hparam_14623493_4.out b/workspace/logs/phase_a4_hparam_14623493_4.out new file mode 100644 index 0000000000000000000000000000000000000000..3ecd4e99cdf8fa95f6cfa18ed8a741e5e04738c4 --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_4.out @@ -0,0 +1,56 @@ +=== Phase A4: Hyperparameter Sweep === +Config 4: LR=0.0003, Hidden=512 + +epoch=1 train_loss=1.4663 val_loss=1.3593 val_rank_acc=0.803 val_progress_mae=0.239 +epoch=2 train_loss=1.3562 val_loss=1.3552 val_rank_acc=0.804 val_progress_mae=0.237 +epoch=3 train_loss=1.3229 val_loss=1.2884 val_rank_acc=0.818 val_progress_mae=0.227 +epoch=4 train_loss=1.3117 val_loss=1.2830 val_rank_acc=0.811 val_progress_mae=0.236 +epoch=5 train_loss=1.2911 val_loss=1.2841 val_rank_acc=0.819 val_progress_mae=0.235 +epoch=6 train_loss=1.2895 val_loss=1.2962 val_rank_acc=0.819 val_progress_mae=0.238 +epoch=7 train_loss=1.2566 val_loss=1.2728 val_rank_acc=0.827 val_progress_mae=0.226 +epoch=8 train_loss=1.2545 val_loss=1.2686 val_rank_acc=0.821 val_progress_mae=0.234 +epoch=9 train_loss=1.2404 val_loss=1.2544 val_rank_acc=0.830 val_progress_mae=0.227 +epoch=10 train_loss=1.2359 val_loss=1.2760 val_rank_acc=0.827 val_progress_mae=0.241 +epoch=11 train_loss=1.2482 val_loss=1.2601 val_rank_acc=0.829 val_progress_mae=0.236 +epoch=12 train_loss=1.2393 val_loss=1.2565 val_rank_acc=0.832 val_progress_mae=0.229 +epoch=13 train_loss=1.2296 val_loss=1.2545 val_rank_acc=0.830 val_progress_mae=0.227 +epoch=14 train_loss=1.2261 val_loss=1.2580 val_rank_acc=0.833 val_progress_mae=0.230 +epoch=15 train_loss=1.2180 val_loss=1.2313 val_rank_acc=0.838 val_progress_mae=0.230 +epoch=16 train_loss=1.2129 val_loss=1.2484 val_rank_acc=0.835 val_progress_mae=0.228 +epoch=17 train_loss=1.2043 val_loss=1.2433 val_rank_acc=0.838 val_progress_mae=0.231 +epoch=18 train_loss=1.2089 val_loss=1.2339 val_rank_acc=0.831 val_progress_mae=0.234 +epoch=19 train_loss=1.2017 val_loss=1.2413 val_rank_acc=0.829 val_progress_mae=0.221 +epoch=20 train_loss=1.2021 val_loss=1.2750 val_rank_acc=0.833 val_progress_mae=0.229 +epoch=21 train_loss=1.1814 val_loss=1.2592 val_rank_acc=0.830 val_progress_mae=0.229 +epoch=22 train_loss=1.1722 val_loss=1.2411 val_rank_acc=0.837 val_progress_mae=0.238 +epoch=23 train_loss=1.1933 val_loss=1.2193 val_rank_acc=0.844 val_progress_mae=0.226 +epoch=24 train_loss=1.1766 val_loss=1.2210 val_rank_acc=0.833 val_progress_mae=0.223 +epoch=25 train_loss=1.1863 val_loss=1.2293 val_rank_acc=0.838 val_progress_mae=0.225 +epoch=26 train_loss=1.1723 val_loss=1.2295 val_rank_acc=0.835 val_progress_mae=0.229 +epoch=27 train_loss=1.1667 val_loss=1.2359 val_rank_acc=0.835 val_progress_mae=0.220 +epoch=28 train_loss=1.1617 val_loss=1.2441 val_rank_acc=0.834 val_progress_mae=0.227 +epoch=29 train_loss=1.1584 val_loss=1.2527 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=30 train_loss=1.1384 val_loss=1.2359 val_rank_acc=0.833 val_progress_mae=0.222 +epoch=31 train_loss=1.1451 val_loss=1.2239 val_rank_acc=0.836 val_progress_mae=0.232 +epoch=32 train_loss=1.1397 val_loss=1.2216 val_rank_acc=0.837 val_progress_mae=0.236 +epoch=33 train_loss=1.1458 val_loss=1.2568 val_rank_acc=0.826 val_progress_mae=0.229 +epoch=34 train_loss=1.1309 val_loss=1.2214 val_rank_acc=0.834 val_progress_mae=0.228 +epoch=35 train_loss=1.1454 val_loss=1.2428 val_rank_acc=0.840 val_progress_mae=0.223 +epoch=36 train_loss=1.1343 val_loss=1.2351 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=37 train_loss=1.1242 val_loss=1.2295 val_rank_acc=0.840 val_progress_mae=0.227 +epoch=38 train_loss=1.1262 val_loss=1.2518 val_rank_acc=0.833 val_progress_mae=0.232 +epoch=39 train_loss=1.1152 val_loss=1.2129 val_rank_acc=0.837 val_progress_mae=0.229 +epoch=40 train_loss=1.1159 val_loss=1.2478 val_rank_acc=0.835 val_progress_mae=0.226 +epoch=41 train_loss=1.1249 val_loss=1.2369 val_rank_acc=0.839 val_progress_mae=0.227 +epoch=42 train_loss=1.1119 val_loss=1.2320 val_rank_acc=0.829 val_progress_mae=0.228 +epoch=43 train_loss=1.1095 val_loss=1.2343 val_rank_acc=0.830 val_progress_mae=0.228 +epoch=44 train_loss=1.1051 val_loss=1.2550 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=45 train_loss=1.1052 val_loss=1.2420 val_rank_acc=0.838 val_progress_mae=0.231 +epoch=46 train_loss=1.1004 val_loss=1.2514 val_rank_acc=0.834 val_progress_mae=0.228 +epoch=47 train_loss=1.1056 val_loss=1.2414 val_rank_acc=0.834 val_progress_mae=0.228 +epoch=48 train_loss=1.0973 val_loss=1.2479 val_rank_acc=0.833 val_progress_mae=0.225 +epoch=49 train_loss=1.0863 val_loss=1.2355 val_rank_acc=0.838 val_progress_mae=0.232 +epoch=50 train_loss=1.0823 val_loss=1.2760 val_rank_acc=0.829 val_progress_mae=0.228 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0003_h512 +best val rank_acc=0.8437 + diff --git a/workspace/logs/phase_a4_hparam_14623493_5.err b/workspace/logs/phase_a4_hparam_14623493_5.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_5.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a4_hparam_14623493_5.out b/workspace/logs/phase_a4_hparam_14623493_5.out new file mode 100644 index 0000000000000000000000000000000000000000..d20bcd7b9fae7f6db04eb1960e9ad1a39ce756b7 --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_5.out @@ -0,0 +1,56 @@ +=== Phase A4: Hyperparameter Sweep === +Config 5: LR=0.0003, Hidden=1024 + +epoch=1 train_loss=1.4771 val_loss=1.3638 val_rank_acc=0.791 val_progress_mae=0.241 +epoch=2 train_loss=1.3672 val_loss=1.3646 val_rank_acc=0.804 val_progress_mae=0.231 +epoch=3 train_loss=1.3366 val_loss=1.2971 val_rank_acc=0.814 val_progress_mae=0.234 +epoch=4 train_loss=1.3215 val_loss=1.2936 val_rank_acc=0.809 val_progress_mae=0.235 +epoch=5 train_loss=1.3020 val_loss=1.2995 val_rank_acc=0.821 val_progress_mae=0.230 +epoch=6 train_loss=1.2997 val_loss=1.3144 val_rank_acc=0.820 val_progress_mae=0.236 +epoch=7 train_loss=1.2684 val_loss=1.2849 val_rank_acc=0.827 val_progress_mae=0.225 +epoch=8 train_loss=1.2623 val_loss=1.2794 val_rank_acc=0.820 val_progress_mae=0.231 +epoch=9 train_loss=1.2518 val_loss=1.2521 val_rank_acc=0.828 val_progress_mae=0.228 +epoch=10 train_loss=1.2450 val_loss=1.2901 val_rank_acc=0.826 val_progress_mae=0.238 +epoch=11 train_loss=1.2583 val_loss=1.2643 val_rank_acc=0.831 val_progress_mae=0.239 +epoch=12 train_loss=1.2487 val_loss=1.2559 val_rank_acc=0.832 val_progress_mae=0.224 +epoch=13 train_loss=1.2407 val_loss=1.2654 val_rank_acc=0.832 val_progress_mae=0.232 +epoch=14 train_loss=1.2377 val_loss=1.2654 val_rank_acc=0.832 val_progress_mae=0.233 +epoch=15 train_loss=1.2323 val_loss=1.2413 val_rank_acc=0.833 val_progress_mae=0.230 +epoch=16 train_loss=1.2269 val_loss=1.2526 val_rank_acc=0.831 val_progress_mae=0.228 +epoch=17 train_loss=1.2181 val_loss=1.2421 val_rank_acc=0.837 val_progress_mae=0.232 +epoch=18 train_loss=1.2208 val_loss=1.2330 val_rank_acc=0.833 val_progress_mae=0.230 +epoch=19 train_loss=1.2186 val_loss=1.2440 val_rank_acc=0.834 val_progress_mae=0.216 +epoch=20 train_loss=1.2147 val_loss=1.2861 val_rank_acc=0.837 val_progress_mae=0.238 +epoch=21 train_loss=1.1993 val_loss=1.2628 val_rank_acc=0.832 val_progress_mae=0.223 +epoch=22 train_loss=1.1883 val_loss=1.2429 val_rank_acc=0.836 val_progress_mae=0.238 +epoch=23 train_loss=1.2095 val_loss=1.2177 val_rank_acc=0.839 val_progress_mae=0.234 +epoch=24 train_loss=1.1898 val_loss=1.2161 val_rank_acc=0.834 val_progress_mae=0.225 +epoch=25 train_loss=1.2005 val_loss=1.2315 val_rank_acc=0.833 val_progress_mae=0.226 +epoch=26 train_loss=1.1899 val_loss=1.2424 val_rank_acc=0.836 val_progress_mae=0.230 +epoch=27 train_loss=1.1829 val_loss=1.2337 val_rank_acc=0.839 val_progress_mae=0.225 +epoch=28 train_loss=1.1757 val_loss=1.2494 val_rank_acc=0.833 val_progress_mae=0.228 +epoch=29 train_loss=1.1752 val_loss=1.2672 val_rank_acc=0.831 val_progress_mae=0.234 +epoch=30 train_loss=1.1608 val_loss=1.2284 val_rank_acc=0.838 val_progress_mae=0.225 +epoch=31 train_loss=1.1589 val_loss=1.2188 val_rank_acc=0.834 val_progress_mae=0.229 +epoch=32 train_loss=1.1578 val_loss=1.2141 val_rank_acc=0.840 val_progress_mae=0.235 +epoch=33 train_loss=1.1638 val_loss=1.2604 val_rank_acc=0.825 val_progress_mae=0.227 +epoch=34 train_loss=1.1486 val_loss=1.2267 val_rank_acc=0.836 val_progress_mae=0.229 +epoch=35 train_loss=1.1607 val_loss=1.2340 val_rank_acc=0.839 val_progress_mae=0.224 +epoch=36 train_loss=1.1480 val_loss=1.2358 val_rank_acc=0.834 val_progress_mae=0.231 +epoch=37 train_loss=1.1409 val_loss=1.2216 val_rank_acc=0.839 val_progress_mae=0.225 +epoch=38 train_loss=1.1425 val_loss=1.2338 val_rank_acc=0.836 val_progress_mae=0.227 +epoch=39 train_loss=1.1291 val_loss=1.2072 val_rank_acc=0.839 val_progress_mae=0.232 +epoch=40 train_loss=1.1288 val_loss=1.2344 val_rank_acc=0.837 val_progress_mae=0.230 +epoch=41 train_loss=1.1418 val_loss=1.2207 val_rank_acc=0.844 val_progress_mae=0.227 +epoch=42 train_loss=1.1249 val_loss=1.2272 val_rank_acc=0.832 val_progress_mae=0.230 +epoch=43 train_loss=1.1232 val_loss=1.2260 val_rank_acc=0.832 val_progress_mae=0.227 +epoch=44 train_loss=1.1222 val_loss=1.2527 val_rank_acc=0.832 val_progress_mae=0.230 +epoch=45 train_loss=1.1205 val_loss=1.2366 val_rank_acc=0.840 val_progress_mae=0.237 +epoch=46 train_loss=1.1151 val_loss=1.2534 val_rank_acc=0.832 val_progress_mae=0.227 +epoch=47 train_loss=1.1192 val_loss=1.2386 val_rank_acc=0.835 val_progress_mae=0.227 +epoch=48 train_loss=1.1123 val_loss=1.2452 val_rank_acc=0.835 val_progress_mae=0.228 +epoch=49 train_loss=1.1028 val_loss=1.2402 val_rank_acc=0.836 val_progress_mae=0.233 +epoch=50 train_loss=1.0965 val_loss=1.2735 val_rank_acc=0.829 val_progress_mae=0.230 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.0003_h1024 +best val rank_acc=0.8436 + diff --git a/workspace/logs/phase_a4_hparam_14623493_6.err b/workspace/logs/phase_a4_hparam_14623493_6.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_6.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a4_hparam_14623493_6.out b/workspace/logs/phase_a4_hparam_14623493_6.out new file mode 100644 index 0000000000000000000000000000000000000000..ef3114b1920d5e1afc24bbdff41d7380b824c3fc --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_6.out @@ -0,0 +1,56 @@ +=== Phase A4: Hyperparameter Sweep === +Config 6: LR=0.001, Hidden=256 + +epoch=1 train_loss=1.4677 val_loss=1.3531 val_rank_acc=0.797 val_progress_mae=0.229 +epoch=2 train_loss=1.3560 val_loss=1.3530 val_rank_acc=0.808 val_progress_mae=0.230 +epoch=3 train_loss=1.3266 val_loss=1.2934 val_rank_acc=0.815 val_progress_mae=0.233 +epoch=4 train_loss=1.3156 val_loss=1.2889 val_rank_acc=0.809 val_progress_mae=0.231 +epoch=5 train_loss=1.2923 val_loss=1.2840 val_rank_acc=0.820 val_progress_mae=0.226 +epoch=6 train_loss=1.2941 val_loss=1.2974 val_rank_acc=0.820 val_progress_mae=0.237 +epoch=7 train_loss=1.2576 val_loss=1.2705 val_rank_acc=0.827 val_progress_mae=0.225 +epoch=8 train_loss=1.2541 val_loss=1.2801 val_rank_acc=0.818 val_progress_mae=0.235 +epoch=9 train_loss=1.2446 val_loss=1.2496 val_rank_acc=0.827 val_progress_mae=0.228 +epoch=10 train_loss=1.2377 val_loss=1.2669 val_rank_acc=0.832 val_progress_mae=0.240 +epoch=11 train_loss=1.2486 val_loss=1.2556 val_rank_acc=0.830 val_progress_mae=0.235 +epoch=12 train_loss=1.2423 val_loss=1.2439 val_rank_acc=0.831 val_progress_mae=0.226 +epoch=13 train_loss=1.2376 val_loss=1.2550 val_rank_acc=0.831 val_progress_mae=0.236 +epoch=14 train_loss=1.2291 val_loss=1.2572 val_rank_acc=0.835 val_progress_mae=0.232 +epoch=15 train_loss=1.2244 val_loss=1.2309 val_rank_acc=0.837 val_progress_mae=0.227 +epoch=16 train_loss=1.2192 val_loss=1.2468 val_rank_acc=0.826 val_progress_mae=0.227 +epoch=17 train_loss=1.2141 val_loss=1.2386 val_rank_acc=0.840 val_progress_mae=0.230 +epoch=18 train_loss=1.2179 val_loss=1.2261 val_rank_acc=0.831 val_progress_mae=0.238 +epoch=19 train_loss=1.2086 val_loss=1.2414 val_rank_acc=0.835 val_progress_mae=0.217 +epoch=20 train_loss=1.2087 val_loss=1.2841 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=21 train_loss=1.1890 val_loss=1.2578 val_rank_acc=0.832 val_progress_mae=0.232 +epoch=22 train_loss=1.1816 val_loss=1.2338 val_rank_acc=0.832 val_progress_mae=0.243 +epoch=23 train_loss=1.2028 val_loss=1.2095 val_rank_acc=0.844 val_progress_mae=0.232 +epoch=24 train_loss=1.1834 val_loss=1.2159 val_rank_acc=0.833 val_progress_mae=0.219 +epoch=25 train_loss=1.1949 val_loss=1.2268 val_rank_acc=0.837 val_progress_mae=0.232 +epoch=26 train_loss=1.1836 val_loss=1.2263 val_rank_acc=0.841 val_progress_mae=0.232 +epoch=27 train_loss=1.1767 val_loss=1.2328 val_rank_acc=0.840 val_progress_mae=0.224 +epoch=28 train_loss=1.1710 val_loss=1.2422 val_rank_acc=0.832 val_progress_mae=0.219 +epoch=29 train_loss=1.1664 val_loss=1.2488 val_rank_acc=0.829 val_progress_mae=0.230 +epoch=30 train_loss=1.1564 val_loss=1.2234 val_rank_acc=0.835 val_progress_mae=0.226 +epoch=31 train_loss=1.1562 val_loss=1.2160 val_rank_acc=0.833 val_progress_mae=0.228 +epoch=32 train_loss=1.1528 val_loss=1.2075 val_rank_acc=0.839 val_progress_mae=0.239 +epoch=33 train_loss=1.1615 val_loss=1.2409 val_rank_acc=0.831 val_progress_mae=0.222 +epoch=34 train_loss=1.1426 val_loss=1.2149 val_rank_acc=0.839 val_progress_mae=0.227 +epoch=35 train_loss=1.1600 val_loss=1.2352 val_rank_acc=0.842 val_progress_mae=0.228 +epoch=36 train_loss=1.1492 val_loss=1.2250 val_rank_acc=0.836 val_progress_mae=0.232 +epoch=37 train_loss=1.1407 val_loss=1.2236 val_rank_acc=0.837 val_progress_mae=0.229 +epoch=38 train_loss=1.1420 val_loss=1.2212 val_rank_acc=0.833 val_progress_mae=0.228 +epoch=39 train_loss=1.1269 val_loss=1.2094 val_rank_acc=0.839 val_progress_mae=0.230 +epoch=40 train_loss=1.1315 val_loss=1.2195 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=41 train_loss=1.1401 val_loss=1.2281 val_rank_acc=0.839 val_progress_mae=0.224 +epoch=42 train_loss=1.1280 val_loss=1.2229 val_rank_acc=0.833 val_progress_mae=0.225 +epoch=43 train_loss=1.1256 val_loss=1.2259 val_rank_acc=0.830 val_progress_mae=0.230 +epoch=44 train_loss=1.1214 val_loss=1.2510 val_rank_acc=0.834 val_progress_mae=0.232 +epoch=45 train_loss=1.1239 val_loss=1.2323 val_rank_acc=0.839 val_progress_mae=0.233 +epoch=46 train_loss=1.1166 val_loss=1.2467 val_rank_acc=0.833 val_progress_mae=0.227 +epoch=47 train_loss=1.1229 val_loss=1.2193 val_rank_acc=0.836 val_progress_mae=0.231 +epoch=48 train_loss=1.1204 val_loss=1.2310 val_rank_acc=0.836 val_progress_mae=0.234 +epoch=49 train_loss=1.1078 val_loss=1.2152 val_rank_acc=0.838 val_progress_mae=0.236 +epoch=50 train_loss=1.1028 val_loss=1.2501 val_rank_acc=0.832 val_progress_mae=0.218 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.001_h256 +best val rank_acc=0.8438 + diff --git a/workspace/logs/phase_a4_hparam_14623493_7.err b/workspace/logs/phase_a4_hparam_14623493_7.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_7.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a4_hparam_14623493_7.out b/workspace/logs/phase_a4_hparam_14623493_7.out new file mode 100644 index 0000000000000000000000000000000000000000..f18803255db4919f9d4a33aa863c8d246ae63a0d --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_7.out @@ -0,0 +1,56 @@ +=== Phase A4: Hyperparameter Sweep === +Config 7: LR=0.001, Hidden=512 + +epoch=1 train_loss=1.4887 val_loss=1.3702 val_rank_acc=0.791 val_progress_mae=0.240 +epoch=2 train_loss=1.3664 val_loss=1.3566 val_rank_acc=0.804 val_progress_mae=0.228 +epoch=3 train_loss=1.3365 val_loss=1.3126 val_rank_acc=0.813 val_progress_mae=0.225 +epoch=4 train_loss=1.3251 val_loss=1.2961 val_rank_acc=0.804 val_progress_mae=0.225 +epoch=5 train_loss=1.3000 val_loss=1.2973 val_rank_acc=0.816 val_progress_mae=0.234 +epoch=6 train_loss=1.2978 val_loss=1.3094 val_rank_acc=0.820 val_progress_mae=0.241 +epoch=7 train_loss=1.2647 val_loss=1.2816 val_rank_acc=0.823 val_progress_mae=0.218 +epoch=8 train_loss=1.2594 val_loss=1.2786 val_rank_acc=0.817 val_progress_mae=0.237 +epoch=9 train_loss=1.2499 val_loss=1.2563 val_rank_acc=0.829 val_progress_mae=0.228 +epoch=10 train_loss=1.2439 val_loss=1.2785 val_rank_acc=0.825 val_progress_mae=0.234 +epoch=11 train_loss=1.2529 val_loss=1.2578 val_rank_acc=0.829 val_progress_mae=0.238 +epoch=12 train_loss=1.2494 val_loss=1.2506 val_rank_acc=0.831 val_progress_mae=0.223 +epoch=13 train_loss=1.2382 val_loss=1.2530 val_rank_acc=0.828 val_progress_mae=0.231 +epoch=14 train_loss=1.2392 val_loss=1.2577 val_rank_acc=0.832 val_progress_mae=0.231 +epoch=15 train_loss=1.2304 val_loss=1.2332 val_rank_acc=0.835 val_progress_mae=0.224 +epoch=16 train_loss=1.2271 val_loss=1.2570 val_rank_acc=0.830 val_progress_mae=0.231 +epoch=17 train_loss=1.2249 val_loss=1.2359 val_rank_acc=0.837 val_progress_mae=0.233 +epoch=18 train_loss=1.2243 val_loss=1.2248 val_rank_acc=0.829 val_progress_mae=0.239 +epoch=19 train_loss=1.2196 val_loss=1.2458 val_rank_acc=0.831 val_progress_mae=0.216 +epoch=20 train_loss=1.2185 val_loss=1.2806 val_rank_acc=0.833 val_progress_mae=0.235 +epoch=21 train_loss=1.2001 val_loss=1.2599 val_rank_acc=0.833 val_progress_mae=0.230 +epoch=22 train_loss=1.1928 val_loss=1.2362 val_rank_acc=0.834 val_progress_mae=0.243 +epoch=23 train_loss=1.2108 val_loss=1.2089 val_rank_acc=0.843 val_progress_mae=0.232 +epoch=24 train_loss=1.1924 val_loss=1.2175 val_rank_acc=0.832 val_progress_mae=0.220 +epoch=25 train_loss=1.2043 val_loss=1.2251 val_rank_acc=0.837 val_progress_mae=0.231 +epoch=26 train_loss=1.1925 val_loss=1.2200 val_rank_acc=0.837 val_progress_mae=0.233 +epoch=27 train_loss=1.1871 val_loss=1.2199 val_rank_acc=0.839 val_progress_mae=0.226 +epoch=28 train_loss=1.1801 val_loss=1.2444 val_rank_acc=0.833 val_progress_mae=0.217 +epoch=29 train_loss=1.1764 val_loss=1.2567 val_rank_acc=0.832 val_progress_mae=0.232 +epoch=30 train_loss=1.1668 val_loss=1.2183 val_rank_acc=0.836 val_progress_mae=0.223 +epoch=31 train_loss=1.1677 val_loss=1.2101 val_rank_acc=0.835 val_progress_mae=0.223 +epoch=32 train_loss=1.1609 val_loss=1.2030 val_rank_acc=0.839 val_progress_mae=0.243 +epoch=33 train_loss=1.1726 val_loss=1.2436 val_rank_acc=0.825 val_progress_mae=0.225 +epoch=34 train_loss=1.1536 val_loss=1.2066 val_rank_acc=0.834 val_progress_mae=0.219 +epoch=35 train_loss=1.1666 val_loss=1.2383 val_rank_acc=0.840 val_progress_mae=0.227 +epoch=36 train_loss=1.1609 val_loss=1.2215 val_rank_acc=0.833 val_progress_mae=0.232 +epoch=37 train_loss=1.1474 val_loss=1.2098 val_rank_acc=0.837 val_progress_mae=0.226 +epoch=38 train_loss=1.1546 val_loss=1.2217 val_rank_acc=0.837 val_progress_mae=0.223 +epoch=39 train_loss=1.1395 val_loss=1.1973 val_rank_acc=0.838 val_progress_mae=0.231 +epoch=40 train_loss=1.1420 val_loss=1.2210 val_rank_acc=0.833 val_progress_mae=0.221 +epoch=41 train_loss=1.1478 val_loss=1.2150 val_rank_acc=0.841 val_progress_mae=0.226 +epoch=42 train_loss=1.1350 val_loss=1.2217 val_rank_acc=0.832 val_progress_mae=0.226 +epoch=43 train_loss=1.1342 val_loss=1.2181 val_rank_acc=0.830 val_progress_mae=0.233 +epoch=44 train_loss=1.1280 val_loss=1.2385 val_rank_acc=0.833 val_progress_mae=0.233 +epoch=45 train_loss=1.1273 val_loss=1.2296 val_rank_acc=0.836 val_progress_mae=0.232 +epoch=46 train_loss=1.1251 val_loss=1.2421 val_rank_acc=0.832 val_progress_mae=0.222 +epoch=47 train_loss=1.1352 val_loss=1.2193 val_rank_acc=0.835 val_progress_mae=0.231 +epoch=48 train_loss=1.1289 val_loss=1.2347 val_rank_acc=0.836 val_progress_mae=0.230 +epoch=49 train_loss=1.1180 val_loss=1.2074 val_rank_acc=0.835 val_progress_mae=0.235 +epoch=50 train_loss=1.1109 val_loss=1.2668 val_rank_acc=0.831 val_progress_mae=0.222 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.001_h512 +best val rank_acc=0.8429 + diff --git a/workspace/logs/phase_a4_hparam_14623493_8.err b/workspace/logs/phase_a4_hparam_14623493_8.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_8.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a4_hparam_14623493_8.out b/workspace/logs/phase_a4_hparam_14623493_8.out new file mode 100644 index 0000000000000000000000000000000000000000..6304738e4f25364dd2d11af19dc7ca186b353685 --- /dev/null +++ b/workspace/logs/phase_a4_hparam_14623493_8.out @@ -0,0 +1,56 @@ +=== Phase A4: Hyperparameter Sweep === +Config 8: LR=0.001, Hidden=1024 + +epoch=1 train_loss=1.5233 val_loss=1.3789 val_rank_acc=0.789 val_progress_mae=0.230 +epoch=2 train_loss=1.3780 val_loss=1.3697 val_rank_acc=0.797 val_progress_mae=0.237 +epoch=3 train_loss=1.3428 val_loss=1.3098 val_rank_acc=0.812 val_progress_mae=0.230 +epoch=4 train_loss=1.3338 val_loss=1.3138 val_rank_acc=0.799 val_progress_mae=0.219 +epoch=5 train_loss=1.3131 val_loss=1.3113 val_rank_acc=0.811 val_progress_mae=0.223 +epoch=6 train_loss=1.3089 val_loss=1.3131 val_rank_acc=0.816 val_progress_mae=0.245 +epoch=7 train_loss=1.2731 val_loss=1.2781 val_rank_acc=0.823 val_progress_mae=0.225 +epoch=8 train_loss=1.2698 val_loss=1.2957 val_rank_acc=0.817 val_progress_mae=0.242 +epoch=9 train_loss=1.2586 val_loss=1.2573 val_rank_acc=0.825 val_progress_mae=0.232 +epoch=10 train_loss=1.2558 val_loss=1.2919 val_rank_acc=0.825 val_progress_mae=0.232 +epoch=11 train_loss=1.2624 val_loss=1.2564 val_rank_acc=0.824 val_progress_mae=0.236 +epoch=12 train_loss=1.2548 val_loss=1.2472 val_rank_acc=0.829 val_progress_mae=0.224 +epoch=13 train_loss=1.2462 val_loss=1.2580 val_rank_acc=0.828 val_progress_mae=0.228 +epoch=14 train_loss=1.2481 val_loss=1.2642 val_rank_acc=0.830 val_progress_mae=0.236 +epoch=15 train_loss=1.2376 val_loss=1.2281 val_rank_acc=0.835 val_progress_mae=0.221 +epoch=16 train_loss=1.2346 val_loss=1.2500 val_rank_acc=0.834 val_progress_mae=0.231 +epoch=17 train_loss=1.2259 val_loss=1.2396 val_rank_acc=0.838 val_progress_mae=0.231 +epoch=18 train_loss=1.2302 val_loss=1.2309 val_rank_acc=0.830 val_progress_mae=0.245 +epoch=19 train_loss=1.2258 val_loss=1.2383 val_rank_acc=0.832 val_progress_mae=0.215 +epoch=20 train_loss=1.2265 val_loss=1.2774 val_rank_acc=0.832 val_progress_mae=0.237 +epoch=21 train_loss=1.2089 val_loss=1.2635 val_rank_acc=0.832 val_progress_mae=0.225 +epoch=22 train_loss=1.2011 val_loss=1.2389 val_rank_acc=0.833 val_progress_mae=0.237 +epoch=23 train_loss=1.2208 val_loss=1.2145 val_rank_acc=0.842 val_progress_mae=0.233 +epoch=24 train_loss=1.2030 val_loss=1.2108 val_rank_acc=0.831 val_progress_mae=0.220 +epoch=25 train_loss=1.2116 val_loss=1.2233 val_rank_acc=0.836 val_progress_mae=0.229 +epoch=26 train_loss=1.2036 val_loss=1.2345 val_rank_acc=0.839 val_progress_mae=0.235 +epoch=27 train_loss=1.1975 val_loss=1.2390 val_rank_acc=0.837 val_progress_mae=0.223 +epoch=28 train_loss=1.1909 val_loss=1.2458 val_rank_acc=0.833 val_progress_mae=0.214 +epoch=29 train_loss=1.1846 val_loss=1.2573 val_rank_acc=0.831 val_progress_mae=0.231 +epoch=30 train_loss=1.1744 val_loss=1.2209 val_rank_acc=0.830 val_progress_mae=0.221 +epoch=31 train_loss=1.1738 val_loss=1.2133 val_rank_acc=0.835 val_progress_mae=0.211 +epoch=32 train_loss=1.1706 val_loss=1.2117 val_rank_acc=0.833 val_progress_mae=0.244 +epoch=33 train_loss=1.1805 val_loss=1.2431 val_rank_acc=0.827 val_progress_mae=0.230 +epoch=34 train_loss=1.1611 val_loss=1.2150 val_rank_acc=0.832 val_progress_mae=0.220 +epoch=35 train_loss=1.1757 val_loss=1.2325 val_rank_acc=0.841 val_progress_mae=0.220 +epoch=36 train_loss=1.1666 val_loss=1.2206 val_rank_acc=0.833 val_progress_mae=0.226 +epoch=37 train_loss=1.1613 val_loss=1.2137 val_rank_acc=0.839 val_progress_mae=0.226 +epoch=38 train_loss=1.1638 val_loss=1.2203 val_rank_acc=0.836 val_progress_mae=0.222 +epoch=39 train_loss=1.1485 val_loss=1.2075 val_rank_acc=0.839 val_progress_mae=0.228 +epoch=40 train_loss=1.1486 val_loss=1.2286 val_rank_acc=0.833 val_progress_mae=0.216 +epoch=41 train_loss=1.1583 val_loss=1.2225 val_rank_acc=0.839 val_progress_mae=0.224 +epoch=42 train_loss=1.1470 val_loss=1.2207 val_rank_acc=0.831 val_progress_mae=0.224 +epoch=43 train_loss=1.1414 val_loss=1.2220 val_rank_acc=0.830 val_progress_mae=0.231 +epoch=44 train_loss=1.1380 val_loss=1.2439 val_rank_acc=0.831 val_progress_mae=0.234 +epoch=45 train_loss=1.1382 val_loss=1.2337 val_rank_acc=0.836 val_progress_mae=0.237 +epoch=46 train_loss=1.1317 val_loss=1.2500 val_rank_acc=0.833 val_progress_mae=0.225 +epoch=47 train_loss=1.1421 val_loss=1.2273 val_rank_acc=0.834 val_progress_mae=0.233 +epoch=48 train_loss=1.1371 val_loss=1.2326 val_rank_acc=0.835 val_progress_mae=0.232 +epoch=49 train_loss=1.1213 val_loss=1.2183 val_rank_acc=0.836 val_progress_mae=0.232 +epoch=50 train_loss=1.1146 val_loss=1.2683 val_rank_acc=0.825 val_progress_mae=0.218 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a4_hparam_sweep/lr0.001_h1024 +best val rank_acc=0.8416 + diff --git a/workspace/logs/phase_a5_horizon_14623007_0.err b/workspace/logs/phase_a5_horizon_14623007_0.err new file mode 100644 index 0000000000000000000000000000000000000000..81d12df8e1400f685e2d8f6604b831b6474be204 --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623007_0.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/train_dovla.py", line 153, in + raise SystemExit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_dovla.py", line 125, in main + result = DoVLATrainer(config).train() + ^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/training/trainer.py", line 108, in __init__ + self.dataset = CILDataset(config.dataset_dir) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/datasets.py", line 65, in __init__ + self.reader = ShardReader(self.dataset_dir) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/sharding.py", line 242, in __init__ + self.index = ShardIndex.from_path(dataset_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/index.py", line 41, in from_path + raise ValueError(f"Unsupported CIL dataset path: {target}") +ValueError: Unsupported CIL dataset path: /scratch/knguy52/dovla/experiments/phase_a_10k_collection/merged_10k diff --git a/workspace/logs/phase_a5_horizon_14623007_0.out b/workspace/logs/phase_a5_horizon_14623007_0.out new file mode 100644 index 0000000000000000000000000000000000000000..beabb87b4c114031839d48da20a4e3085f3a8a92 --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623007_0.out @@ -0,0 +1,3 @@ +=== Phase A5: Action Horizon Sweep === +Horizon: 4 (current baseline: 4) + diff --git a/workspace/logs/phase_a5_horizon_14623007_1.err b/workspace/logs/phase_a5_horizon_14623007_1.err new file mode 100644 index 0000000000000000000000000000000000000000..81d12df8e1400f685e2d8f6604b831b6474be204 --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623007_1.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/train_dovla.py", line 153, in + raise SystemExit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_dovla.py", line 125, in main + result = DoVLATrainer(config).train() + ^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/training/trainer.py", line 108, in __init__ + self.dataset = CILDataset(config.dataset_dir) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/datasets.py", line 65, in __init__ + self.reader = ShardReader(self.dataset_dir) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/sharding.py", line 242, in __init__ + self.index = ShardIndex.from_path(dataset_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/index.py", line 41, in from_path + raise ValueError(f"Unsupported CIL dataset path: {target}") +ValueError: Unsupported CIL dataset path: /scratch/knguy52/dovla/experiments/phase_a_10k_collection/merged_10k diff --git a/workspace/logs/phase_a5_horizon_14623007_1.out b/workspace/logs/phase_a5_horizon_14623007_1.out new file mode 100644 index 0000000000000000000000000000000000000000..87b6c7461a21a61c5201f2e03842557b678fba21 --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623007_1.out @@ -0,0 +1,3 @@ +=== Phase A5: Action Horizon Sweep === +Horizon: 8 (current baseline: 4) + diff --git a/workspace/logs/phase_a5_horizon_14623007_2.err b/workspace/logs/phase_a5_horizon_14623007_2.err new file mode 100644 index 0000000000000000000000000000000000000000..81d12df8e1400f685e2d8f6604b831b6474be204 --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623007_2.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/train_dovla.py", line 153, in + raise SystemExit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_dovla.py", line 125, in main + result = DoVLATrainer(config).train() + ^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/training/trainer.py", line 108, in __init__ + self.dataset = CILDataset(config.dataset_dir) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/datasets.py", line 65, in __init__ + self.reader = ShardReader(self.dataset_dir) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/sharding.py", line 242, in __init__ + self.index = ShardIndex.from_path(dataset_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/index.py", line 41, in from_path + raise ValueError(f"Unsupported CIL dataset path: {target}") +ValueError: Unsupported CIL dataset path: /scratch/knguy52/dovla/experiments/phase_a_10k_collection/merged_10k diff --git a/workspace/logs/phase_a5_horizon_14623007_2.out b/workspace/logs/phase_a5_horizon_14623007_2.out new file mode 100644 index 0000000000000000000000000000000000000000..29d6f1cff16f5d2f6f38d6160414d7b26cdcf07a --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623007_2.out @@ -0,0 +1,3 @@ +=== Phase A5: Action Horizon Sweep === +Horizon: 12 (current baseline: 4) + diff --git a/workspace/logs/phase_a5_horizon_14623007_3.err b/workspace/logs/phase_a5_horizon_14623007_3.err new file mode 100644 index 0000000000000000000000000000000000000000..81d12df8e1400f685e2d8f6604b831b6474be204 --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623007_3.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/train_dovla.py", line 153, in + raise SystemExit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_dovla.py", line 125, in main + result = DoVLATrainer(config).train() + ^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/training/trainer.py", line 108, in __init__ + self.dataset = CILDataset(config.dataset_dir) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/datasets.py", line 65, in __init__ + self.reader = ShardReader(self.dataset_dir) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/sharding.py", line 242, in __init__ + self.index = ShardIndex.from_path(dataset_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/index.py", line 41, in from_path + raise ValueError(f"Unsupported CIL dataset path: {target}") +ValueError: Unsupported CIL dataset path: /scratch/knguy52/dovla/experiments/phase_a_10k_collection/merged_10k diff --git a/workspace/logs/phase_a5_horizon_14623007_3.out b/workspace/logs/phase_a5_horizon_14623007_3.out new file mode 100644 index 0000000000000000000000000000000000000000..8d4bfaa03be9e000acf14b05f46f4fc1280778e8 --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623007_3.out @@ -0,0 +1,3 @@ +=== Phase A5: Action Horizon Sweep === +Horizon: 16 (current baseline: 4) + diff --git a/workspace/logs/phase_a5_horizon_14623494_0.err b/workspace/logs/phase_a5_horizon_14623494_0.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623494_0.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a5_horizon_14623494_0.out b/workspace/logs/phase_a5_horizon_14623494_0.out new file mode 100644 index 0000000000000000000000000000000000000000..6003a756cd7b3deec3a0981d17ae5b62063652b5 --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623494_0.out @@ -0,0 +1,56 @@ +=== Phase A5: Action Horizon Sweep === +Horizon: 4 (current baseline: 4) + +epoch=1 train_loss=1.4663 val_loss=1.3593 val_rank_acc=0.803 val_progress_mae=0.239 +epoch=2 train_loss=1.3562 val_loss=1.3552 val_rank_acc=0.804 val_progress_mae=0.237 +epoch=3 train_loss=1.3229 val_loss=1.2884 val_rank_acc=0.818 val_progress_mae=0.227 +epoch=4 train_loss=1.3117 val_loss=1.2830 val_rank_acc=0.811 val_progress_mae=0.236 +epoch=5 train_loss=1.2911 val_loss=1.2841 val_rank_acc=0.819 val_progress_mae=0.235 +epoch=6 train_loss=1.2895 val_loss=1.2962 val_rank_acc=0.819 val_progress_mae=0.238 +epoch=7 train_loss=1.2566 val_loss=1.2728 val_rank_acc=0.827 val_progress_mae=0.226 +epoch=8 train_loss=1.2545 val_loss=1.2686 val_rank_acc=0.821 val_progress_mae=0.234 +epoch=9 train_loss=1.2404 val_loss=1.2544 val_rank_acc=0.830 val_progress_mae=0.227 +epoch=10 train_loss=1.2359 val_loss=1.2760 val_rank_acc=0.827 val_progress_mae=0.241 +epoch=11 train_loss=1.2482 val_loss=1.2601 val_rank_acc=0.829 val_progress_mae=0.236 +epoch=12 train_loss=1.2393 val_loss=1.2565 val_rank_acc=0.832 val_progress_mae=0.229 +epoch=13 train_loss=1.2296 val_loss=1.2545 val_rank_acc=0.830 val_progress_mae=0.227 +epoch=14 train_loss=1.2261 val_loss=1.2580 val_rank_acc=0.833 val_progress_mae=0.230 +epoch=15 train_loss=1.2180 val_loss=1.2313 val_rank_acc=0.838 val_progress_mae=0.230 +epoch=16 train_loss=1.2129 val_loss=1.2484 val_rank_acc=0.835 val_progress_mae=0.228 +epoch=17 train_loss=1.2043 val_loss=1.2433 val_rank_acc=0.838 val_progress_mae=0.231 +epoch=18 train_loss=1.2089 val_loss=1.2339 val_rank_acc=0.831 val_progress_mae=0.234 +epoch=19 train_loss=1.2017 val_loss=1.2413 val_rank_acc=0.829 val_progress_mae=0.221 +epoch=20 train_loss=1.2021 val_loss=1.2750 val_rank_acc=0.833 val_progress_mae=0.229 +epoch=21 train_loss=1.1814 val_loss=1.2592 val_rank_acc=0.830 val_progress_mae=0.229 +epoch=22 train_loss=1.1722 val_loss=1.2411 val_rank_acc=0.837 val_progress_mae=0.238 +epoch=23 train_loss=1.1933 val_loss=1.2193 val_rank_acc=0.844 val_progress_mae=0.226 +epoch=24 train_loss=1.1766 val_loss=1.2210 val_rank_acc=0.833 val_progress_mae=0.223 +epoch=25 train_loss=1.1863 val_loss=1.2293 val_rank_acc=0.838 val_progress_mae=0.225 +epoch=26 train_loss=1.1723 val_loss=1.2295 val_rank_acc=0.835 val_progress_mae=0.229 +epoch=27 train_loss=1.1667 val_loss=1.2359 val_rank_acc=0.835 val_progress_mae=0.220 +epoch=28 train_loss=1.1617 val_loss=1.2441 val_rank_acc=0.834 val_progress_mae=0.227 +epoch=29 train_loss=1.1584 val_loss=1.2527 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=30 train_loss=1.1384 val_loss=1.2359 val_rank_acc=0.833 val_progress_mae=0.222 +epoch=31 train_loss=1.1451 val_loss=1.2239 val_rank_acc=0.836 val_progress_mae=0.232 +epoch=32 train_loss=1.1397 val_loss=1.2216 val_rank_acc=0.837 val_progress_mae=0.236 +epoch=33 train_loss=1.1458 val_loss=1.2568 val_rank_acc=0.826 val_progress_mae=0.229 +epoch=34 train_loss=1.1309 val_loss=1.2214 val_rank_acc=0.834 val_progress_mae=0.228 +epoch=35 train_loss=1.1454 val_loss=1.2428 val_rank_acc=0.840 val_progress_mae=0.223 +epoch=36 train_loss=1.1343 val_loss=1.2351 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=37 train_loss=1.1242 val_loss=1.2295 val_rank_acc=0.840 val_progress_mae=0.227 +epoch=38 train_loss=1.1262 val_loss=1.2518 val_rank_acc=0.833 val_progress_mae=0.232 +epoch=39 train_loss=1.1152 val_loss=1.2129 val_rank_acc=0.837 val_progress_mae=0.229 +epoch=40 train_loss=1.1159 val_loss=1.2478 val_rank_acc=0.835 val_progress_mae=0.226 +epoch=41 train_loss=1.1249 val_loss=1.2369 val_rank_acc=0.839 val_progress_mae=0.227 +epoch=42 train_loss=1.1119 val_loss=1.2320 val_rank_acc=0.829 val_progress_mae=0.228 +epoch=43 train_loss=1.1095 val_loss=1.2343 val_rank_acc=0.830 val_progress_mae=0.228 +epoch=44 train_loss=1.1051 val_loss=1.2550 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=45 train_loss=1.1052 val_loss=1.2420 val_rank_acc=0.838 val_progress_mae=0.231 +epoch=46 train_loss=1.1004 val_loss=1.2514 val_rank_acc=0.834 val_progress_mae=0.228 +epoch=47 train_loss=1.1056 val_loss=1.2414 val_rank_acc=0.834 val_progress_mae=0.228 +epoch=48 train_loss=1.0973 val_loss=1.2479 val_rank_acc=0.833 val_progress_mae=0.225 +epoch=49 train_loss=1.0863 val_loss=1.2355 val_rank_acc=0.838 val_progress_mae=0.232 +epoch=50 train_loss=1.0823 val_loss=1.2760 val_rank_acc=0.829 val_progress_mae=0.228 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a5_horizon_sweep/h4 +best val rank_acc=0.8437 + diff --git a/workspace/logs/phase_a5_horizon_14623494_1.err b/workspace/logs/phase_a5_horizon_14623494_1.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623494_1.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a5_horizon_14623494_1.out b/workspace/logs/phase_a5_horizon_14623494_1.out new file mode 100644 index 0000000000000000000000000000000000000000..3246eab162179093d033c4fffa67a0d060c80904 --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623494_1.out @@ -0,0 +1,56 @@ +=== Phase A5: Action Horizon Sweep === +Horizon: 8 (current baseline: 4) + +epoch=1 train_loss=1.2174 val_loss=1.1096 val_rank_acc=0.798 val_progress_mae=0.239 +epoch=2 train_loss=1.1078 val_loss=1.0982 val_rank_acc=0.805 val_progress_mae=0.221 +epoch=3 train_loss=1.0906 val_loss=1.0586 val_rank_acc=0.810 val_progress_mae=0.233 +epoch=4 train_loss=1.0736 val_loss=1.0480 val_rank_acc=0.811 val_progress_mae=0.231 +epoch=5 train_loss=1.0586 val_loss=1.0434 val_rank_acc=0.817 val_progress_mae=0.224 +epoch=6 train_loss=1.0621 val_loss=1.0680 val_rank_acc=0.814 val_progress_mae=0.241 +epoch=7 train_loss=1.0362 val_loss=1.0577 val_rank_acc=0.822 val_progress_mae=0.224 +epoch=8 train_loss=1.0336 val_loss=1.0526 val_rank_acc=0.815 val_progress_mae=0.229 +epoch=9 train_loss=1.0267 val_loss=1.0268 val_rank_acc=0.825 val_progress_mae=0.224 +epoch=10 train_loss=1.0216 val_loss=1.0409 val_rank_acc=0.822 val_progress_mae=0.236 +epoch=11 train_loss=1.0279 val_loss=1.0259 val_rank_acc=0.829 val_progress_mae=0.239 +epoch=12 train_loss=1.0188 val_loss=1.0219 val_rank_acc=0.829 val_progress_mae=0.228 +epoch=13 train_loss=1.0114 val_loss=1.0326 val_rank_acc=0.830 val_progress_mae=0.223 +epoch=14 train_loss=1.0151 val_loss=1.0240 val_rank_acc=0.834 val_progress_mae=0.231 +epoch=15 train_loss=1.0060 val_loss=1.0072 val_rank_acc=0.836 val_progress_mae=0.227 +epoch=16 train_loss=1.0028 val_loss=1.0239 val_rank_acc=0.830 val_progress_mae=0.232 +epoch=17 train_loss=0.9970 val_loss=1.0139 val_rank_acc=0.839 val_progress_mae=0.232 +epoch=18 train_loss=0.9990 val_loss=1.0088 val_rank_acc=0.832 val_progress_mae=0.235 +epoch=19 train_loss=0.9948 val_loss=1.0044 val_rank_acc=0.832 val_progress_mae=0.221 +epoch=20 train_loss=0.9906 val_loss=1.0391 val_rank_acc=0.836 val_progress_mae=0.237 +epoch=21 train_loss=0.9794 val_loss=1.0287 val_rank_acc=0.828 val_progress_mae=0.226 +epoch=22 train_loss=0.9735 val_loss=1.0209 val_rank_acc=0.835 val_progress_mae=0.243 +epoch=23 train_loss=0.9880 val_loss=1.0012 val_rank_acc=0.843 val_progress_mae=0.230 +epoch=24 train_loss=0.9764 val_loss=0.9966 val_rank_acc=0.834 val_progress_mae=0.222 +epoch=25 train_loss=0.9784 val_loss=1.0058 val_rank_acc=0.839 val_progress_mae=0.227 +epoch=26 train_loss=0.9729 val_loss=1.0076 val_rank_acc=0.838 val_progress_mae=0.226 +epoch=27 train_loss=0.9696 val_loss=1.0101 val_rank_acc=0.841 val_progress_mae=0.226 +epoch=28 train_loss=0.9626 val_loss=1.0191 val_rank_acc=0.833 val_progress_mae=0.230 +epoch=29 train_loss=0.9655 val_loss=1.0222 val_rank_acc=0.836 val_progress_mae=0.232 +epoch=30 train_loss=0.9530 val_loss=1.0082 val_rank_acc=0.834 val_progress_mae=0.219 +epoch=31 train_loss=0.9549 val_loss=0.9997 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=32 train_loss=0.9525 val_loss=0.9983 val_rank_acc=0.841 val_progress_mae=0.234 +epoch=33 train_loss=0.9555 val_loss=1.0285 val_rank_acc=0.830 val_progress_mae=0.234 +epoch=34 train_loss=0.9464 val_loss=1.0002 val_rank_acc=0.833 val_progress_mae=0.232 +epoch=35 train_loss=0.9544 val_loss=1.0030 val_rank_acc=0.839 val_progress_mae=0.223 +epoch=36 train_loss=0.9467 val_loss=1.0005 val_rank_acc=0.835 val_progress_mae=0.232 +epoch=37 train_loss=0.9421 val_loss=0.9964 val_rank_acc=0.841 val_progress_mae=0.223 +epoch=38 train_loss=0.9430 val_loss=1.0075 val_rank_acc=0.834 val_progress_mae=0.231 +epoch=39 train_loss=0.9344 val_loss=0.9979 val_rank_acc=0.838 val_progress_mae=0.233 +epoch=40 train_loss=0.9389 val_loss=1.0110 val_rank_acc=0.836 val_progress_mae=0.225 +epoch=41 train_loss=0.9445 val_loss=1.0033 val_rank_acc=0.836 val_progress_mae=0.224 +epoch=42 train_loss=0.9321 val_loss=0.9992 val_rank_acc=0.834 val_progress_mae=0.225 +epoch=43 train_loss=0.9297 val_loss=0.9989 val_rank_acc=0.833 val_progress_mae=0.228 +epoch=44 train_loss=0.9313 val_loss=1.0078 val_rank_acc=0.837 val_progress_mae=0.231 +epoch=45 train_loss=0.9276 val_loss=1.0051 val_rank_acc=0.839 val_progress_mae=0.232 +epoch=46 train_loss=0.9285 val_loss=1.0177 val_rank_acc=0.837 val_progress_mae=0.226 +epoch=47 train_loss=0.9307 val_loss=1.0007 val_rank_acc=0.838 val_progress_mae=0.226 +epoch=48 train_loss=0.9237 val_loss=1.0080 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=49 train_loss=0.9199 val_loss=1.0060 val_rank_acc=0.836 val_progress_mae=0.231 +epoch=50 train_loss=0.9138 val_loss=1.0253 val_rank_acc=0.832 val_progress_mae=0.225 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a5_horizon_sweep/h8 +best val rank_acc=0.8428 + diff --git a/workspace/logs/phase_a5_horizon_14623494_2.err b/workspace/logs/phase_a5_horizon_14623494_2.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623494_2.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a5_horizon_14623494_2.out b/workspace/logs/phase_a5_horizon_14623494_2.out new file mode 100644 index 0000000000000000000000000000000000000000..6350f5bdfe723cb6c476c559c8f5c13b210fba8c --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623494_2.out @@ -0,0 +1,56 @@ +=== Phase A5: Action Horizon Sweep === +Horizon: 12 (current baseline: 4) + +epoch=1 train_loss=1.1562 val_loss=1.0823 val_rank_acc=0.768 val_progress_mae=0.214 +epoch=2 train_loss=1.0399 val_loss=1.0146 val_rank_acc=0.804 val_progress_mae=0.227 +epoch=3 train_loss=1.0173 val_loss=0.9891 val_rank_acc=0.808 val_progress_mae=0.227 +epoch=4 train_loss=1.0019 val_loss=0.9790 val_rank_acc=0.805 val_progress_mae=0.232 +epoch=5 train_loss=0.9877 val_loss=0.9738 val_rank_acc=0.812 val_progress_mae=0.218 +epoch=6 train_loss=0.9938 val_loss=0.9919 val_rank_acc=0.808 val_progress_mae=0.230 +epoch=7 train_loss=0.9676 val_loss=0.9938 val_rank_acc=0.816 val_progress_mae=0.224 +epoch=8 train_loss=0.9645 val_loss=0.9752 val_rank_acc=0.813 val_progress_mae=0.229 +epoch=9 train_loss=0.9610 val_loss=0.9613 val_rank_acc=0.822 val_progress_mae=0.226 +epoch=10 train_loss=0.9540 val_loss=0.9750 val_rank_acc=0.823 val_progress_mae=0.235 +epoch=11 train_loss=0.9604 val_loss=0.9655 val_rank_acc=0.823 val_progress_mae=0.240 +epoch=12 train_loss=0.9542 val_loss=0.9499 val_rank_acc=0.827 val_progress_mae=0.223 +epoch=13 train_loss=0.9438 val_loss=0.9586 val_rank_acc=0.822 val_progress_mae=0.221 +epoch=14 train_loss=0.9484 val_loss=0.9553 val_rank_acc=0.831 val_progress_mae=0.232 +epoch=15 train_loss=0.9405 val_loss=0.9410 val_rank_acc=0.831 val_progress_mae=0.226 +epoch=16 train_loss=0.9361 val_loss=0.9545 val_rank_acc=0.826 val_progress_mae=0.226 +epoch=17 train_loss=0.9328 val_loss=0.9395 val_rank_acc=0.834 val_progress_mae=0.233 +epoch=18 train_loss=0.9349 val_loss=0.9372 val_rank_acc=0.828 val_progress_mae=0.231 +epoch=19 train_loss=0.9281 val_loss=0.9435 val_rank_acc=0.830 val_progress_mae=0.217 +epoch=20 train_loss=0.9275 val_loss=0.9556 val_rank_acc=0.832 val_progress_mae=0.231 +epoch=21 train_loss=0.9164 val_loss=0.9498 val_rank_acc=0.830 val_progress_mae=0.222 +epoch=22 train_loss=0.9092 val_loss=0.9463 val_rank_acc=0.836 val_progress_mae=0.239 +epoch=23 train_loss=0.9222 val_loss=0.9306 val_rank_acc=0.839 val_progress_mae=0.229 +epoch=24 train_loss=0.9141 val_loss=0.9278 val_rank_acc=0.830 val_progress_mae=0.220 +epoch=25 train_loss=0.9148 val_loss=0.9301 val_rank_acc=0.837 val_progress_mae=0.231 +epoch=26 train_loss=0.9104 val_loss=0.9384 val_rank_acc=0.838 val_progress_mae=0.232 +epoch=27 train_loss=0.9055 val_loss=0.9355 val_rank_acc=0.838 val_progress_mae=0.226 +epoch=28 train_loss=0.9004 val_loss=0.9496 val_rank_acc=0.829 val_progress_mae=0.229 +epoch=29 train_loss=0.9052 val_loss=0.9560 val_rank_acc=0.829 val_progress_mae=0.234 +epoch=30 train_loss=0.8942 val_loss=0.9359 val_rank_acc=0.834 val_progress_mae=0.223 +epoch=31 train_loss=0.8943 val_loss=0.9302 val_rank_acc=0.837 val_progress_mae=0.230 +epoch=32 train_loss=0.8932 val_loss=0.9288 val_rank_acc=0.838 val_progress_mae=0.229 +epoch=33 train_loss=0.8954 val_loss=0.9526 val_rank_acc=0.827 val_progress_mae=0.236 +epoch=34 train_loss=0.8866 val_loss=0.9262 val_rank_acc=0.835 val_progress_mae=0.230 +epoch=35 train_loss=0.8915 val_loss=0.9249 val_rank_acc=0.839 val_progress_mae=0.220 +epoch=36 train_loss=0.8881 val_loss=0.9301 val_rank_acc=0.835 val_progress_mae=0.237 +epoch=37 train_loss=0.8825 val_loss=0.9225 val_rank_acc=0.837 val_progress_mae=0.225 +epoch=38 train_loss=0.8830 val_loss=0.9305 val_rank_acc=0.834 val_progress_mae=0.229 +epoch=39 train_loss=0.8760 val_loss=0.9268 val_rank_acc=0.839 val_progress_mae=0.235 +epoch=40 train_loss=0.8800 val_loss=0.9419 val_rank_acc=0.837 val_progress_mae=0.225 +epoch=41 train_loss=0.8824 val_loss=0.9200 val_rank_acc=0.841 val_progress_mae=0.226 +epoch=42 train_loss=0.8748 val_loss=0.9184 val_rank_acc=0.836 val_progress_mae=0.224 +epoch=43 train_loss=0.8712 val_loss=0.9218 val_rank_acc=0.833 val_progress_mae=0.227 +epoch=44 train_loss=0.8745 val_loss=0.9385 val_rank_acc=0.835 val_progress_mae=0.229 +epoch=45 train_loss=0.8724 val_loss=0.9256 val_rank_acc=0.840 val_progress_mae=0.230 +epoch=46 train_loss=0.8728 val_loss=0.9383 val_rank_acc=0.836 val_progress_mae=0.228 +epoch=47 train_loss=0.8741 val_loss=0.9251 val_rank_acc=0.835 val_progress_mae=0.229 +epoch=48 train_loss=0.8683 val_loss=0.9274 val_rank_acc=0.839 val_progress_mae=0.234 +epoch=49 train_loss=0.8643 val_loss=0.9377 val_rank_acc=0.836 val_progress_mae=0.232 +epoch=50 train_loss=0.8608 val_loss=0.9433 val_rank_acc=0.833 val_progress_mae=0.226 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a5_horizon_sweep/h12 +best val rank_acc=0.8407 + diff --git a/workspace/logs/phase_a5_horizon_14623494_3.err b/workspace/logs/phase_a5_horizon_14623494_3.err new file mode 100644 index 0000000000000000000000000000000000000000..585c0d8022419668b669f86d771448756e7ce3ca --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623494_3.err @@ -0,0 +1,4 @@ +usage: eval_lattice_checkpoint.py [-h] --checkpoint CHECKPOINT --dataset + DATASET --out OUT [--device DEVICE] + [--training-k TRAINING_K] [--all-groups] +eval_lattice_checkpoint.py: error: unrecognized arguments: --mode field_only diff --git a/workspace/logs/phase_a5_horizon_14623494_3.out b/workspace/logs/phase_a5_horizon_14623494_3.out new file mode 100644 index 0000000000000000000000000000000000000000..1a2d00002c5290dfb9589ecb729fadbd3dd76abe --- /dev/null +++ b/workspace/logs/phase_a5_horizon_14623494_3.out @@ -0,0 +1,56 @@ +=== Phase A5: Action Horizon Sweep === +Horizon: 16 (current baseline: 4) + +epoch=1 train_loss=1.1007 val_loss=1.0153 val_rank_acc=0.785 val_progress_mae=0.224 +epoch=2 train_loss=1.0003 val_loss=0.9793 val_rank_acc=0.793 val_progress_mae=0.223 +epoch=3 train_loss=0.9801 val_loss=0.9542 val_rank_acc=0.804 val_progress_mae=0.232 +epoch=4 train_loss=0.9664 val_loss=0.9478 val_rank_acc=0.803 val_progress_mae=0.231 +epoch=5 train_loss=0.9537 val_loss=0.9451 val_rank_acc=0.805 val_progress_mae=0.220 +epoch=6 train_loss=0.9589 val_loss=0.9579 val_rank_acc=0.796 val_progress_mae=0.236 +epoch=7 train_loss=0.9332 val_loss=0.9577 val_rank_acc=0.814 val_progress_mae=0.231 +epoch=8 train_loss=0.9302 val_loss=0.9359 val_rank_acc=0.810 val_progress_mae=0.223 +epoch=9 train_loss=0.9272 val_loss=0.9251 val_rank_acc=0.819 val_progress_mae=0.226 +epoch=10 train_loss=0.9217 val_loss=0.9483 val_rank_acc=0.822 val_progress_mae=0.244 +epoch=11 train_loss=0.9280 val_loss=0.9247 val_rank_acc=0.818 val_progress_mae=0.249 +epoch=12 train_loss=0.9196 val_loss=0.9133 val_rank_acc=0.826 val_progress_mae=0.221 +epoch=13 train_loss=0.9114 val_loss=0.9272 val_rank_acc=0.824 val_progress_mae=0.222 +epoch=14 train_loss=0.9184 val_loss=0.9323 val_rank_acc=0.825 val_progress_mae=0.244 +epoch=15 train_loss=0.9110 val_loss=0.9108 val_rank_acc=0.824 val_progress_mae=0.233 +epoch=16 train_loss=0.9061 val_loss=0.9194 val_rank_acc=0.823 val_progress_mae=0.231 +epoch=17 train_loss=0.9023 val_loss=0.9076 val_rank_acc=0.828 val_progress_mae=0.231 +epoch=18 train_loss=0.9045 val_loss=0.9116 val_rank_acc=0.823 val_progress_mae=0.238 +epoch=19 train_loss=0.8964 val_loss=0.9047 val_rank_acc=0.829 val_progress_mae=0.219 +epoch=20 train_loss=0.8972 val_loss=0.9237 val_rank_acc=0.822 val_progress_mae=0.234 +epoch=21 train_loss=0.8881 val_loss=0.9201 val_rank_acc=0.819 val_progress_mae=0.227 +epoch=22 train_loss=0.8804 val_loss=0.9118 val_rank_acc=0.825 val_progress_mae=0.242 +epoch=23 train_loss=0.8930 val_loss=0.9015 val_rank_acc=0.834 val_progress_mae=0.236 +epoch=24 train_loss=0.8832 val_loss=0.8965 val_rank_acc=0.825 val_progress_mae=0.222 +epoch=25 train_loss=0.8831 val_loss=0.8967 val_rank_acc=0.832 val_progress_mae=0.236 +epoch=26 train_loss=0.8808 val_loss=0.9011 val_rank_acc=0.833 val_progress_mae=0.230 +epoch=27 train_loss=0.8766 val_loss=0.8955 val_rank_acc=0.833 val_progress_mae=0.226 +epoch=28 train_loss=0.8712 val_loss=0.9119 val_rank_acc=0.824 val_progress_mae=0.226 +epoch=29 train_loss=0.8745 val_loss=0.9181 val_rank_acc=0.828 val_progress_mae=0.238 +epoch=30 train_loss=0.8661 val_loss=0.9004 val_rank_acc=0.832 val_progress_mae=0.221 +epoch=31 train_loss=0.8665 val_loss=0.8972 val_rank_acc=0.830 val_progress_mae=0.230 +epoch=32 train_loss=0.8650 val_loss=0.9001 val_rank_acc=0.834 val_progress_mae=0.225 +epoch=33 train_loss=0.8666 val_loss=0.9168 val_rank_acc=0.827 val_progress_mae=0.231 +epoch=34 train_loss=0.8564 val_loss=0.8958 val_rank_acc=0.826 val_progress_mae=0.230 +epoch=35 train_loss=0.8641 val_loss=0.8916 val_rank_acc=0.838 val_progress_mae=0.225 +epoch=36 train_loss=0.8591 val_loss=0.8951 val_rank_acc=0.829 val_progress_mae=0.238 +epoch=37 train_loss=0.8544 val_loss=0.8865 val_rank_acc=0.840 val_progress_mae=0.228 +epoch=38 train_loss=0.8559 val_loss=0.8906 val_rank_acc=0.831 val_progress_mae=0.227 +epoch=39 train_loss=0.8489 val_loss=0.8879 val_rank_acc=0.836 val_progress_mae=0.234 +epoch=40 train_loss=0.8537 val_loss=0.9099 val_rank_acc=0.832 val_progress_mae=0.225 +epoch=41 train_loss=0.8558 val_loss=0.8850 val_rank_acc=0.839 val_progress_mae=0.229 +epoch=42 train_loss=0.8495 val_loss=0.8876 val_rank_acc=0.836 val_progress_mae=0.221 +epoch=43 train_loss=0.8446 val_loss=0.8903 val_rank_acc=0.829 val_progress_mae=0.224 +epoch=44 train_loss=0.8487 val_loss=0.8982 val_rank_acc=0.832 val_progress_mae=0.235 +epoch=45 train_loss=0.8457 val_loss=0.8858 val_rank_acc=0.836 val_progress_mae=0.232 +epoch=46 train_loss=0.8454 val_loss=0.9043 val_rank_acc=0.837 val_progress_mae=0.227 +epoch=47 train_loss=0.8479 val_loss=0.8859 val_rank_acc=0.835 val_progress_mae=0.233 +epoch=48 train_loss=0.8427 val_loss=0.8902 val_rank_acc=0.837 val_progress_mae=0.236 +epoch=49 train_loss=0.8399 val_loss=0.8976 val_rank_acc=0.836 val_progress_mae=0.232 +epoch=50 train_loss=0.8349 val_loss=0.9058 val_rank_acc=0.832 val_progress_mae=0.224 +wrote checkpoints to /scratch/knguy52/dovla/experiments/phase_a5_horizon_sweep/h16 +best val rank_acc=0.8402 + diff --git a/workspace/logs/status_report_14759129.err b/workspace/logs/status_report_14759129.err new file mode 100644 index 0000000000000000000000000000000000000000..6794e4575ece03364e0b8d26cb923893203dacf3 --- /dev/null +++ b/workspace/logs/status_report_14759129.err @@ -0,0 +1 @@ +[2026-06-26T11:35:13.006] error: *** JOB 14759129 ON rc31810 CANCELLED AT 2026-06-26T11:35:13 DUE to SIGNAL Terminated *** diff --git a/workspace/logs/status_report_14759129.out b/workspace/logs/status_report_14759129.out new file mode 100644 index 0000000000000000000000000000000000000000..f902fa88a420fff3dbc5e0fc6a239fe204d083ee --- /dev/null +++ b/workspace/logs/status_report_14759129.out @@ -0,0 +1,31 @@ +=== Status Reporter Started === +Generating reports every hour + +[2026-06-26 00:20:40] Generating status report... +✅ Status report generated: STATUS_LIVE.md + ✅ Report uploaded to HF + +[2026-06-26 01:29:36] Generating status report... +✅ Status report generated: STATUS_LIVE.md + ✅ Report uploaded to HF + +[2026-06-26 02:38:33] Generating status report... +✅ Status report generated: STATUS_LIVE.md + ✅ Report uploaded to HF + +[2026-06-26 03:47:26] Generating status report... +✅ Status report generated: STATUS_LIVE.md + ✅ Report uploaded to HF + +[2026-06-26 04:56:19] Generating status report... +✅ Status report generated: STATUS_LIVE.md + ✅ Report uploaded to HF + +[2026-06-26 06:05:11] Generating status report... +✅ Status report generated: STATUS_LIVE.md + ✅ Report uploaded to HF + +[2026-06-26 07:14:13] Generating status report... +✅ Status report generated: STATUS_LIVE.md + ✅ Report uploaded to HF + diff --git a/workspace/logs/train_h16_14748764_0.err b/workspace/logs/train_h16_14748764_0.err new file mode 100644 index 0000000000000000000000000000000000000000..e092d4f07d3063dc272b67ce3f54a156b8ddb74f --- /dev/null +++ b/workspace/logs/train_h16_14748764_0.err @@ -0,0 +1,8 @@ +usage: train_hybrid_direct.py [-h] --dataset DATASET --out OUT + [--d-model D_MODEL] [--n-heads N_HEADS] + [--n-layers N_LAYERS] [--d-ff D_FF] + [--epochs EPOCHS] [--batch-size BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--warmup-steps WARMUP_STEPS] [--seed SEED] + [--val-fraction VAL_FRACTION] [--device DEVICE] +train_hybrid_direct.py: error: unrecognized arguments: --hidden-dim 256 diff --git a/workspace/logs/train_h16_14748764_0.out b/workspace/logs/train_h16_14748764_0.out new file mode 100644 index 0000000000000000000000000000000000000000..2b24810a8bcdbda3de2136a57aa5f7a007943bb6 --- /dev/null +++ b/workspace/logs/train_h16_14748764_0.out @@ -0,0 +1,7 @@ +================================================== +Training Policy on h=16 Collection +Seed: 0 +Dataset: /scratch/knguy52/dovla/experiments/six_task_h16_collection +Expected oracle: 94.76% +Expected val top-1: 85-90% +================================================== diff --git a/workspace/logs/train_h16_14748764_1.err b/workspace/logs/train_h16_14748764_1.err new file mode 100644 index 0000000000000000000000000000000000000000..e092d4f07d3063dc272b67ce3f54a156b8ddb74f --- /dev/null +++ b/workspace/logs/train_h16_14748764_1.err @@ -0,0 +1,8 @@ +usage: train_hybrid_direct.py [-h] --dataset DATASET --out OUT + [--d-model D_MODEL] [--n-heads N_HEADS] + [--n-layers N_LAYERS] [--d-ff D_FF] + [--epochs EPOCHS] [--batch-size BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--warmup-steps WARMUP_STEPS] [--seed SEED] + [--val-fraction VAL_FRACTION] [--device DEVICE] +train_hybrid_direct.py: error: unrecognized arguments: --hidden-dim 256 diff --git a/workspace/logs/train_h16_14748764_1.out b/workspace/logs/train_h16_14748764_1.out new file mode 100644 index 0000000000000000000000000000000000000000..88f023f7e7cfead3494b62e18db5885c8b91d2fb --- /dev/null +++ b/workspace/logs/train_h16_14748764_1.out @@ -0,0 +1,7 @@ +================================================== +Training Policy on h=16 Collection +Seed: 1 +Dataset: /scratch/knguy52/dovla/experiments/six_task_h16_collection +Expected oracle: 94.76% +Expected val top-1: 85-90% +================================================== diff --git a/workspace/logs/train_h16_14748764_2.err b/workspace/logs/train_h16_14748764_2.err new file mode 100644 index 0000000000000000000000000000000000000000..e092d4f07d3063dc272b67ce3f54a156b8ddb74f --- /dev/null +++ b/workspace/logs/train_h16_14748764_2.err @@ -0,0 +1,8 @@ +usage: train_hybrid_direct.py [-h] --dataset DATASET --out OUT + [--d-model D_MODEL] [--n-heads N_HEADS] + [--n-layers N_LAYERS] [--d-ff D_FF] + [--epochs EPOCHS] [--batch-size BATCH_SIZE] + [--lr LR] [--weight-decay WEIGHT_DECAY] + [--warmup-steps WARMUP_STEPS] [--seed SEED] + [--val-fraction VAL_FRACTION] [--device DEVICE] +train_hybrid_direct.py: error: unrecognized arguments: --hidden-dim 256 diff --git a/workspace/logs/train_h16_14748764_2.out b/workspace/logs/train_h16_14748764_2.out new file mode 100644 index 0000000000000000000000000000000000000000..2cafb9798794c3cb44976fdd405fe4f24e55ac1c --- /dev/null +++ b/workspace/logs/train_h16_14748764_2.out @@ -0,0 +1,7 @@ +================================================== +Training Policy on h=16 Collection +Seed: 2 +Dataset: /scratch/knguy52/dovla/experiments/six_task_h16_collection +Expected oracle: 94.76% +Expected val top-1: 85-90% +================================================== diff --git a/workspace/logs/train_h16_14749139_0.err b/workspace/logs/train_h16_14749139_0.err new file mode 100644 index 0000000000000000000000000000000000000000..82c5c9aa8b2f4914d9375f922cde6764b46a03c1 --- /dev/null +++ b/workspace/logs/train_h16_14749139_0.err @@ -0,0 +1,16 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 348, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 242, in main + dataset = CILDataset(args.dataset) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/datasets.py", line 65, in __init__ + self.reader = ShardReader(self.dataset_dir) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/sharding.py", line 242, in __init__ + self.index = ShardIndex.from_path(dataset_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/index.py", line 35, in from_path + raise FileNotFoundError(f"No metadata.json or manifest.json in {target}") +FileNotFoundError: No metadata.json or manifest.json in /scratch/knguy52/dovla/experiments/six_task_h16_collection diff --git a/workspace/logs/train_h16_14749139_0.out b/workspace/logs/train_h16_14749139_0.out new file mode 100644 index 0000000000000000000000000000000000000000..e4d43f2c9fdb2f8df78a3a173b71180fc0883651 --- /dev/null +++ b/workspace/logs/train_h16_14749139_0.out @@ -0,0 +1,15 @@ +================================================== +Training Policy on h=16 Collection +Seed: 0 +Dataset: /scratch/knguy52/dovla/experiments/six_task_h16_collection +Expected oracle: 94.76% +Expected val top-1: 85-90% +================================================== +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/six_task_h16_collection +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + diff --git a/workspace/logs/train_h16_14749139_1.err b/workspace/logs/train_h16_14749139_1.err new file mode 100644 index 0000000000000000000000000000000000000000..82c5c9aa8b2f4914d9375f922cde6764b46a03c1 --- /dev/null +++ b/workspace/logs/train_h16_14749139_1.err @@ -0,0 +1,16 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 348, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 242, in main + dataset = CILDataset(args.dataset) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/datasets.py", line 65, in __init__ + self.reader = ShardReader(self.dataset_dir) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/sharding.py", line 242, in __init__ + self.index = ShardIndex.from_path(dataset_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/index.py", line 35, in from_path + raise FileNotFoundError(f"No metadata.json or manifest.json in {target}") +FileNotFoundError: No metadata.json or manifest.json in /scratch/knguy52/dovla/experiments/six_task_h16_collection diff --git a/workspace/logs/train_h16_14749139_1.out b/workspace/logs/train_h16_14749139_1.out new file mode 100644 index 0000000000000000000000000000000000000000..3df519319e334f2bc9d7afc1ab477fada125eb47 --- /dev/null +++ b/workspace/logs/train_h16_14749139_1.out @@ -0,0 +1,15 @@ +================================================== +Training Policy on h=16 Collection +Seed: 1 +Dataset: /scratch/knguy52/dovla/experiments/six_task_h16_collection +Expected oracle: 94.76% +Expected val top-1: 85-90% +================================================== +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/six_task_h16_collection +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + diff --git a/workspace/logs/train_h16_14749139_2.err b/workspace/logs/train_h16_14749139_2.err new file mode 100644 index 0000000000000000000000000000000000000000..82c5c9aa8b2f4914d9375f922cde6764b46a03c1 --- /dev/null +++ b/workspace/logs/train_h16_14749139_2.err @@ -0,0 +1,16 @@ +Traceback (most recent call last): + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 348, in + sys.exit(main()) + ^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/scripts/train_hybrid_direct.py", line 242, in main + dataset = CILDataset(args.dataset) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/datasets.py", line 65, in __init__ + self.reader = ShardReader(self.dataset_dir) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/sharding.py", line 242, in __init__ + self.index = ShardIndex.from_path(dataset_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/lustre09/project/6037638/knguy52/vla/dovla_cil/data/index.py", line 35, in from_path + raise FileNotFoundError(f"No metadata.json or manifest.json in {target}") +FileNotFoundError: No metadata.json or manifest.json in /scratch/knguy52/dovla/experiments/six_task_h16_collection diff --git a/workspace/logs/train_h16_14749139_2.out b/workspace/logs/train_h16_14749139_2.out new file mode 100644 index 0000000000000000000000000000000000000000..b7cc9a362ed7330c777c091e0640113a9adf718c --- /dev/null +++ b/workspace/logs/train_h16_14749139_2.out @@ -0,0 +1,15 @@ +================================================== +Training Policy on h=16 Collection +Seed: 2 +Dataset: /scratch/knguy52/dovla/experiments/six_task_h16_collection +Expected oracle: 94.76% +Expected val top-1: 85-90% +================================================== +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/six_task_h16_collection +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + diff --git a/workspace/logs/train_h16_14756014_0.err b/workspace/logs/train_h16_14756014_0.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/train_h16_14756014_0.out b/workspace/logs/train_h16_14756014_0.out new file mode 100644 index 0000000000000000000000000000000000000000..7273b48922c29211760fe9dee5a72f98e49c41f5 --- /dev/null +++ b/workspace/logs/train_h16_14756014_0.out @@ -0,0 +1,76 @@ +================================================== +Training Policy on h=16 Collection +Seed: 0 +Dataset: /scratch/knguy52/dovla/experiments/h16_merged_dataset +Expected oracle: 94.76% +Expected val top-1: 85-90% +================================================== +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/h16_merged_dataset +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + +Total: 2873, Train: 2298, Val: 575 + +Model parameters: 6,673,410 + +Starting training... + +Epoch 1/50: r_loss=1.0015, s_loss=0.7599, val_top1=0.5148 +Epoch 2/50: r_loss=0.8883, s_loss=0.7378, val_top1=0.5026 +Epoch 3/50: r_loss=0.6785, s_loss=0.6583, val_top1=0.5270 +Epoch 4/50: r_loss=0.5876, s_loss=0.5215, val_top1=0.6278 +Epoch 5/50: r_loss=0.5187, s_loss=0.4254, val_top1=0.7426 +Epoch 6/50: r_loss=0.4498, s_loss=0.3956, val_top1=0.7774 +Epoch 7/50: r_loss=0.3911, s_loss=0.3804, val_top1=0.7791 +Epoch 8/50: r_loss=0.3595, s_loss=0.3629, val_top1=0.7948 +Epoch 9/50: r_loss=0.3377, s_loss=0.3414, val_top1=0.7930 +Epoch 10/50: r_loss=0.3137, s_loss=0.3163, val_top1=0.7930 +Epoch 11/50: r_loss=0.2982, s_loss=0.2952, val_top1=0.7930 +Epoch 12/50: r_loss=0.2800, s_loss=0.2790, val_top1=0.8035 +Epoch 13/50: r_loss=0.2713, s_loss=0.2687, val_top1=0.8052 +Epoch 14/50: r_loss=0.2574, s_loss=0.2582, val_top1=0.8087 +Epoch 15/50: r_loss=0.2504, s_loss=0.2545, val_top1=0.8087 +Epoch 16/50: r_loss=0.2439, s_loss=0.2496, val_top1=0.8087 +Epoch 17/50: r_loss=0.2374, s_loss=0.2447, val_top1=0.8070 +Epoch 18/50: r_loss=0.2339, s_loss=0.2418, val_top1=0.8087 +Epoch 19/50: r_loss=0.2291, s_loss=0.2397, val_top1=0.8070 +Epoch 20/50: r_loss=0.2246, s_loss=0.2344, val_top1=0.8087 +Epoch 21/50: r_loss=0.2236, s_loss=0.2335, val_top1=0.8087 +Epoch 22/50: r_loss=0.2204, s_loss=0.2297, val_top1=0.8087 +Epoch 23/50: r_loss=0.2180, s_loss=0.2274, val_top1=0.8087 +Epoch 24/50: r_loss=0.2138, s_loss=0.2244, val_top1=0.8052 +Epoch 25/50: r_loss=0.2133, s_loss=0.2239, val_top1=0.8017 +Epoch 26/50: r_loss=0.2103, s_loss=0.2189, val_top1=0.8000 +Epoch 27/50: r_loss=0.2080, s_loss=0.2168, val_top1=0.8017 +Epoch 28/50: r_loss=0.2046, s_loss=0.2124, val_top1=0.8000 +Epoch 29/50: r_loss=0.2038, s_loss=0.2120, val_top1=0.7983 +Epoch 30/50: r_loss=0.1991, s_loss=0.2090, val_top1=0.8000 +Epoch 31/50: r_loss=0.1986, s_loss=0.2072, val_top1=0.8104 +Epoch 32/50: r_loss=0.1949, s_loss=0.2039, val_top1=0.8104 +Epoch 33/50: r_loss=0.1925, s_loss=0.2001, val_top1=0.8052 +Epoch 34/50: r_loss=0.1903, s_loss=0.1983, val_top1=0.8070 +Epoch 35/50: r_loss=0.1903, s_loss=0.1977, val_top1=0.8087 +Epoch 36/50: r_loss=0.1857, s_loss=0.1941, val_top1=0.8052 +Epoch 37/50: r_loss=0.1831, s_loss=0.1918, val_top1=0.8070 +Epoch 38/50: r_loss=0.1812, s_loss=0.1909, val_top1=0.8052 +Epoch 39/50: r_loss=0.1805, s_loss=0.1873, val_top1=0.8052 +Epoch 40/50: r_loss=0.1781, s_loss=0.1862, val_top1=0.8087 +Epoch 41/50: r_loss=0.1788, s_loss=0.1852, val_top1=0.8035 +Epoch 42/50: r_loss=0.1772, s_loss=0.1836, val_top1=0.8000 +Epoch 43/50: r_loss=0.1737, s_loss=0.1805, val_top1=0.8070 +Epoch 44/50: r_loss=0.1739, s_loss=0.1809, val_top1=0.8017 +Epoch 45/50: r_loss=0.1721, s_loss=0.1792, val_top1=0.8035 +Epoch 46/50: r_loss=0.1713, s_loss=0.1798, val_top1=0.7913 +Epoch 47/50: r_loss=0.1699, s_loss=0.1784, val_top1=0.8017 +Epoch 48/50: r_loss=0.1678, s_loss=0.1754, val_top1=0.8000 +Epoch 49/50: r_loss=0.1662, s_loss=0.1744, val_top1=0.7930 +Epoch 50/50: r_loss=0.1663, s_loss=0.1744, val_top1=0.8000 + +✅ Training complete! Best val top-1: 0.8104 + +✅ Training complete for seed 0 +Best checkpoint: /scratch/knguy52/dovla/experiments/h16_policy_runs/seed_0/best.pt diff --git a/workspace/logs/train_h16_14756014_1.err b/workspace/logs/train_h16_14756014_1.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/train_h16_14756014_1.out b/workspace/logs/train_h16_14756014_1.out new file mode 100644 index 0000000000000000000000000000000000000000..a9e88bbc0e62b02f9ae6b3245f88602335c7b528 --- /dev/null +++ b/workspace/logs/train_h16_14756014_1.out @@ -0,0 +1,76 @@ +================================================== +Training Policy on h=16 Collection +Seed: 1 +Dataset: /scratch/knguy52/dovla/experiments/h16_merged_dataset +Expected oracle: 94.76% +Expected val top-1: 85-90% +================================================== +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/h16_merged_dataset +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + +Total: 2873, Train: 2298, Val: 575 + +Model parameters: 6,673,410 + +Starting training... + +Epoch 1/50: r_loss=0.7606, s_loss=0.6786, val_top1=0.2852 +Epoch 2/50: r_loss=0.7343, s_loss=0.6523, val_top1=0.3774 +Epoch 3/50: r_loss=0.6635, s_loss=0.5740, val_top1=0.4696 +Epoch 4/50: r_loss=0.6255, s_loss=0.4748, val_top1=0.5843 +Epoch 5/50: r_loss=0.5436, s_loss=0.4285, val_top1=0.6557 +Epoch 6/50: r_loss=0.4725, s_loss=0.4175, val_top1=0.6557 +Epoch 7/50: r_loss=0.4264, s_loss=0.4071, val_top1=0.6904 +Epoch 8/50: r_loss=0.3872, s_loss=0.3955, val_top1=0.6974 +Epoch 9/50: r_loss=0.3508, s_loss=0.3794, val_top1=0.7304 +Epoch 10/50: r_loss=0.3292, s_loss=0.3591, val_top1=0.7548 +Epoch 11/50: r_loss=0.3120, s_loss=0.3332, val_top1=0.7635 +Epoch 12/50: r_loss=0.2974, s_loss=0.3073, val_top1=0.7687 +Epoch 13/50: r_loss=0.2824, s_loss=0.2863, val_top1=0.7704 +Epoch 14/50: r_loss=0.2730, s_loss=0.2750, val_top1=0.7826 +Epoch 15/50: r_loss=0.2613, s_loss=0.2642, val_top1=0.7948 +Epoch 16/50: r_loss=0.2531, s_loss=0.2576, val_top1=0.7965 +Epoch 17/50: r_loss=0.2468, s_loss=0.2536, val_top1=0.8000 +Epoch 18/50: r_loss=0.2440, s_loss=0.2491, val_top1=0.8000 +Epoch 19/50: r_loss=0.2363, s_loss=0.2461, val_top1=0.8000 +Epoch 20/50: r_loss=0.2344, s_loss=0.2436, val_top1=0.8000 +Epoch 21/50: r_loss=0.2324, s_loss=0.2409, val_top1=0.8000 +Epoch 22/50: r_loss=0.2282, s_loss=0.2366, val_top1=0.8000 +Epoch 23/50: r_loss=0.2243, s_loss=0.2338, val_top1=0.8000 +Epoch 24/50: r_loss=0.2200, s_loss=0.2320, val_top1=0.8000 +Epoch 25/50: r_loss=0.2200, s_loss=0.2295, val_top1=0.8000 +Epoch 26/50: r_loss=0.2160, s_loss=0.2277, val_top1=0.8000 +Epoch 27/50: r_loss=0.2141, s_loss=0.2235, val_top1=0.8000 +Epoch 28/50: r_loss=0.2110, s_loss=0.2216, val_top1=0.8000 +Epoch 29/50: r_loss=0.2110, s_loss=0.2193, val_top1=0.8000 +Epoch 30/50: r_loss=0.2059, s_loss=0.2151, val_top1=0.8000 +Epoch 31/50: r_loss=0.2056, s_loss=0.2162, val_top1=0.8000 +Epoch 32/50: r_loss=0.2030, s_loss=0.2134, val_top1=0.8035 +Epoch 33/50: r_loss=0.2006, s_loss=0.2089, val_top1=0.8035 +Epoch 34/50: r_loss=0.1994, s_loss=0.2069, val_top1=0.8070 +Epoch 35/50: r_loss=0.1968, s_loss=0.2036, val_top1=0.8070 +Epoch 36/50: r_loss=0.1961, s_loss=0.2034, val_top1=0.8070 +Epoch 37/50: r_loss=0.1910, s_loss=0.1992, val_top1=0.8087 +Epoch 38/50: r_loss=0.1903, s_loss=0.1960, val_top1=0.8087 +Epoch 39/50: r_loss=0.1869, s_loss=0.1951, val_top1=0.8087 +Epoch 40/50: r_loss=0.1864, s_loss=0.1942, val_top1=0.8070 +Epoch 41/50: r_loss=0.1855, s_loss=0.1917, val_top1=0.8052 +Epoch 42/50: r_loss=0.1828, s_loss=0.1901, val_top1=0.8035 +Epoch 43/50: r_loss=0.1789, s_loss=0.1868, val_top1=0.7983 +Epoch 44/50: r_loss=0.1799, s_loss=0.1882, val_top1=0.8000 +Epoch 45/50: r_loss=0.1778, s_loss=0.1857, val_top1=0.7965 +Epoch 46/50: r_loss=0.1776, s_loss=0.1842, val_top1=0.8000 +Epoch 47/50: r_loss=0.1743, s_loss=0.1813, val_top1=0.7948 +Epoch 48/50: r_loss=0.1764, s_loss=0.1830, val_top1=0.7913 +Epoch 49/50: r_loss=0.1746, s_loss=0.1823, val_top1=0.7791 +Epoch 50/50: r_loss=0.1724, s_loss=0.1791, val_top1=0.7791 + +✅ Training complete! Best val top-1: 0.8087 + +✅ Training complete for seed 1 +Best checkpoint: /scratch/knguy52/dovla/experiments/h16_policy_runs/seed_1/best.pt diff --git a/workspace/logs/train_h16_14756014_2.err b/workspace/logs/train_h16_14756014_2.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/train_h16_14756014_2.out b/workspace/logs/train_h16_14756014_2.out new file mode 100644 index 0000000000000000000000000000000000000000..24ec95e7787c38b877fe90e63471f6258813c80d --- /dev/null +++ b/workspace/logs/train_h16_14756014_2.out @@ -0,0 +1,76 @@ +================================================== +Training Policy on h=16 Collection +Seed: 2 +Dataset: /scratch/knguy52/dovla/experiments/h16_merged_dataset +Expected oracle: 94.76% +Expected val top-1: 85-90% +================================================== +====================================================================== +DoVLA-Hybrid: DIRECT Scoring (NOT Pairwise) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/h16_merged_dataset +Device: cuda +Approach: Predict reward + success DIRECTLY +Expected: 45-48% (vs 37% pairwise baseline) + +Total: 2873, Train: 2298, Val: 575 + +Model parameters: 6,673,410 + +Starting training... + +Epoch 1/50: r_loss=4.0634, s_loss=2.0762, val_top1=0.3443 +Epoch 2/50: r_loss=3.6266, s_loss=2.0301, val_top1=0.3078 +Epoch 3/50: r_loss=2.5239, s_loss=1.9127, val_top1=0.2522 +Epoch 4/50: r_loss=1.3168, s_loss=1.6318, val_top1=0.2122 +Epoch 5/50: r_loss=0.7195, s_loss=1.0953, val_top1=0.4365 +Epoch 6/50: r_loss=0.6098, s_loss=0.5535, val_top1=0.6452 +Epoch 7/50: r_loss=0.5154, s_loss=0.4020, val_top1=0.7513 +Epoch 8/50: r_loss=0.4330, s_loss=0.3831, val_top1=0.7861 +Epoch 9/50: r_loss=0.3787, s_loss=0.3646, val_top1=0.7983 +Epoch 10/50: r_loss=0.3443, s_loss=0.3416, val_top1=0.8035 +Epoch 11/50: r_loss=0.3289, s_loss=0.3172, val_top1=0.7983 +Epoch 12/50: r_loss=0.3096, s_loss=0.2968, val_top1=0.8070 +Epoch 13/50: r_loss=0.2912, s_loss=0.2813, val_top1=0.8139 +Epoch 14/50: r_loss=0.2803, s_loss=0.2712, val_top1=0.8157 +Epoch 15/50: r_loss=0.2698, s_loss=0.2632, val_top1=0.8191 +Epoch 16/50: r_loss=0.2629, s_loss=0.2600, val_top1=0.8174 +Epoch 17/50: r_loss=0.2534, s_loss=0.2560, val_top1=0.8174 +Epoch 18/50: r_loss=0.2498, s_loss=0.2534, val_top1=0.8174 +Epoch 19/50: r_loss=0.2455, s_loss=0.2514, val_top1=0.8174 +Epoch 20/50: r_loss=0.2403, s_loss=0.2481, val_top1=0.8157 +Epoch 21/50: r_loss=0.2390, s_loss=0.2446, val_top1=0.8174 +Epoch 22/50: r_loss=0.2335, s_loss=0.2418, val_top1=0.8157 +Epoch 23/50: r_loss=0.2326, s_loss=0.2406, val_top1=0.8191 +Epoch 24/50: r_loss=0.2272, s_loss=0.2381, val_top1=0.8157 +Epoch 25/50: r_loss=0.2265, s_loss=0.2356, val_top1=0.8157 +Epoch 26/50: r_loss=0.2233, s_loss=0.2334, val_top1=0.8157 +Epoch 27/50: r_loss=0.2223, s_loss=0.2310, val_top1=0.8104 +Epoch 28/50: r_loss=0.2183, s_loss=0.2283, val_top1=0.8209 +Epoch 29/50: r_loss=0.2153, s_loss=0.2268, val_top1=0.8174 +Epoch 30/50: r_loss=0.2150, s_loss=0.2237, val_top1=0.8157 +Epoch 31/50: r_loss=0.2119, s_loss=0.2202, val_top1=0.8122 +Epoch 32/50: r_loss=0.2094, s_loss=0.2183, val_top1=0.8174 +Epoch 33/50: r_loss=0.2076, s_loss=0.2158, val_top1=0.8174 +Epoch 34/50: r_loss=0.2051, s_loss=0.2111, val_top1=0.8139 +Epoch 35/50: r_loss=0.2032, s_loss=0.2103, val_top1=0.8191 +Epoch 36/50: r_loss=0.1994, s_loss=0.2074, val_top1=0.8226 +Epoch 37/50: r_loss=0.2001, s_loss=0.2057, val_top1=0.8278 +Epoch 38/50: r_loss=0.1982, s_loss=0.2043, val_top1=0.8278 +Epoch 39/50: r_loss=0.1941, s_loss=0.1999, val_top1=0.8278 +Epoch 40/50: r_loss=0.1926, s_loss=0.2002, val_top1=0.8278 +Epoch 41/50: r_loss=0.1905, s_loss=0.1968, val_top1=0.8296 +Epoch 42/50: r_loss=0.1901, s_loss=0.1952, val_top1=0.8296 +Epoch 43/50: r_loss=0.1860, s_loss=0.1931, val_top1=0.8313 +Epoch 44/50: r_loss=0.1851, s_loss=0.1912, val_top1=0.8261 +Epoch 45/50: r_loss=0.1826, s_loss=0.1887, val_top1=0.8261 +Epoch 46/50: r_loss=0.1847, s_loss=0.1905, val_top1=0.8209 +Epoch 47/50: r_loss=0.1805, s_loss=0.1870, val_top1=0.8157 +Epoch 48/50: r_loss=0.1803, s_loss=0.1864, val_top1=0.8104 +Epoch 49/50: r_loss=0.1771, s_loss=0.1837, val_top1=0.8052 +Epoch 50/50: r_loss=0.1773, s_loss=0.1846, val_top1=0.8174 + +✅ Training complete! Best val top-1: 0.8313 + +✅ Training complete for seed 2 +Best checkpoint: /scratch/knguy52/dovla/experiments/h16_policy_runs/seed_2/best.pt diff --git a/workspace/logs/training_output_sync.log b/workspace/logs/training_output_sync.log new file mode 100644 index 0000000000000000000000000000000000000000..1e9bcd8a9eeb74a055b0538fcf4fe5b7cb2d4ac6 --- /dev/null +++ b/workspace/logs/training_output_sync.log @@ -0,0 +1,9 @@ +============================================================ +🔄 Training Output Monitor & Uploader +Monitoring jobs: [14749139] +Check interval: 300s +============================================================ + +[22:43:41] Job 14749139: FAILED +❌ Job 14749139 failed, skipping upload +✅ All jobs processed, exiting diff --git a/workspace/logs/training_sync.pid b/workspace/logs/training_sync.pid new file mode 100644 index 0000000000000000000000000000000000000000..b8cc61a30f2916602d65615fd0fdb085de5fcc2f --- /dev/null +++ b/workspace/logs/training_sync.pid @@ -0,0 +1 @@ +849690 diff --git a/workspace/logs/transformer_lang_14709801_0.err b/workspace/logs/transformer_lang_14709801_0.err new file mode 100644 index 0000000000000000000000000000000000000000..0ec2198a77499cf19fd0353f7374d5f418956d8d --- /dev/null +++ b/workspace/logs/transformer_lang_14709801_0.err @@ -0,0 +1 @@ +[2026-06-25T12:52:23.183] error: *** JOB 14709893 ON rg13402 CANCELLED AT 2026-06-25T12:52:23 DUE to SIGNAL Terminated *** diff --git a/workspace/logs/transformer_lang_14709801_0.out b/workspace/logs/transformer_lang_14709801_0.out new file mode 100644 index 0000000000000000000000000000000000000000..bdebc1508ca29a359580bbb5ccfeb14bf5981584 --- /dev/null +++ b/workspace/logs/transformer_lang_14709801_0.out @@ -0,0 +1,17 @@ += = = = = = = = = = = = = = = = +DoVLA-Transformer WITH LANGUAGE += = = = = = = = = = = = = = = = + +NEW FEATURE: Instruction embeddings (768-dim) + - Baseline (no language): 42-44% + - WITH language: 50-55% expected + - Improvement: +8-11% + +Architecture: + - Pure Transformer (8 heads, 3 layers) + - Language dimension: 768 + - Cross-attention: obs + lang → context + +Dataset: 3,500 groups +Seed: 0 + diff --git a/workspace/logs/transformer_lang_14709801_1.err b/workspace/logs/transformer_lang_14709801_1.err new file mode 100644 index 0000000000000000000000000000000000000000..6e851b20a5c9c96cb207a2e6621e474a32b38e89 --- /dev/null +++ b/workspace/logs/transformer_lang_14709801_1.err @@ -0,0 +1 @@ +[2026-06-25T12:52:23.183] error: *** JOB 14709959 ON rg21708 CANCELLED AT 2026-06-25T12:52:23 DUE to SIGNAL Terminated *** diff --git a/workspace/logs/transformer_lang_14709801_1.out b/workspace/logs/transformer_lang_14709801_1.out new file mode 100644 index 0000000000000000000000000000000000000000..6c4aa089261840065d13e2c52065e5ad936f5d93 --- /dev/null +++ b/workspace/logs/transformer_lang_14709801_1.out @@ -0,0 +1,17 @@ += = = = = = = = = = = = = = = = +DoVLA-Transformer WITH LANGUAGE += = = = = = = = = = = = = = = = + +NEW FEATURE: Instruction embeddings (768-dim) + - Baseline (no language): 42-44% + - WITH language: 50-55% expected + - Improvement: +8-11% + +Architecture: + - Pure Transformer (8 heads, 3 layers) + - Language dimension: 768 + - Cross-attention: obs + lang → context + +Dataset: 3,500 groups +Seed: 1 + diff --git a/workspace/logs/transformer_lang_14709801_2.err b/workspace/logs/transformer_lang_14709801_2.err new file mode 100644 index 0000000000000000000000000000000000000000..45e20324de9165dd64ccaa06af3359c228574546 --- /dev/null +++ b/workspace/logs/transformer_lang_14709801_2.err @@ -0,0 +1 @@ +[2026-06-25T12:52:23.183] error: *** JOB 14709801 ON rg13403 CANCELLED AT 2026-06-25T12:52:23 DUE to SIGNAL Terminated *** diff --git a/workspace/logs/transformer_lang_14709801_2.out b/workspace/logs/transformer_lang_14709801_2.out new file mode 100644 index 0000000000000000000000000000000000000000..c3ca5073908e35daed9aa89d1a2d083fadfa5997 --- /dev/null +++ b/workspace/logs/transformer_lang_14709801_2.out @@ -0,0 +1,17 @@ += = = = = = = = = = = = = = = = +DoVLA-Transformer WITH LANGUAGE += = = = = = = = = = = = = = = = + +NEW FEATURE: Instruction embeddings (768-dim) + - Baseline (no language): 42-44% + - WITH language: 50-55% expected + - Improvement: +8-11% + +Architecture: + - Pure Transformer (8 heads, 3 layers) + - Language dimension: 768 + - Cross-attention: obs + lang → context + +Dataset: 3,500 groups +Seed: 2 + diff --git a/workspace/logs/transformer_train_14707188_0.err b/workspace/logs/transformer_train_14707188_0.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/transformer_train_14707188_0.out b/workspace/logs/transformer_train_14707188_0.out new file mode 100644 index 0000000000000000000000000000000000000000..4f025f029da73eb0186fcf51de95bdd1fe26a395 --- /dev/null +++ b/workspace/logs/transformer_train_14707188_0.out @@ -0,0 +1,97 @@ += = = = = = = = = = = = = = = = = = +DoVLA-Transformer: BREAKTHROUGH Architecture += = = = = = = = = = = = = = = = = = + +Pure Transformer Components: + - Multi-head self-attention (8 heads) + - Cross-attention for obs-lang fusion + - 3 Transformer encoder layers + - Positional encoding + - Residual connections everywhere + +Key Improvements: + - Higher LR: 0.001 (vs 0.0003 failed Enhanced) + - Warmup scheduler: 500 steps + - No custom GNN (proven Transformer only) + - Proper gradient flow (residuals) + +Dataset: 3,500 groups (fair comparison) +Seed: 0 + +Expected: 42-47% success +vs Baseline: 38.43% +vs Enhanced (failed): 36.31% + +====================================================================== +DoVLA-Transformer Training (BREAKTHROUGH) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Architecture: Pure Transformer (d=256, heads=8, layers=3) +LR: 0.001 (higher than failed Enhanced 0.0003) +Warmup: 500 steps +Seed: 0 + +Loading dataset... +Total: 3500, Train: 2800, Val: 700 + +Model parameters: 5,851,649 + +Starting training... + +Epoch 1/50: loss=0.7805, val_top1=0.2257, lr=0.000002 +Epoch 2/50: loss=0.6882, val_top1=0.4914, lr=0.000004 +Epoch 3/50: loss=0.5378, val_top1=0.5186, lr=0.000006 +Epoch 4/50: loss=0.4797, val_top1=0.5786, lr=0.000008 +Epoch 5/50: loss=0.4599, val_top1=0.6000, lr=0.000010 +Epoch 6/50: loss=0.4465, val_top1=0.6200, lr=0.000012 +Epoch 7/50: loss=0.4370, val_top1=0.6257, lr=0.000014 +Epoch 8/50: loss=0.4269, val_top1=0.6300, lr=0.000016 +Epoch 9/50: loss=0.4187, val_top1=0.6314, lr=0.000018 +Epoch 10/50: loss=0.4094, val_top1=0.6314, lr=0.000020 +Epoch 11/50: loss=0.3987, val_top1=0.6343, lr=0.000022 +Epoch 12/50: loss=0.3895, val_top1=0.6300, lr=0.000024 +Epoch 13/50: loss=0.3800, val_top1=0.6257, lr=0.000026 +Epoch 14/50: loss=0.3711, val_top1=0.6086, lr=0.000028 +Epoch 15/50: loss=0.3642, val_top1=0.6257, lr=0.000030 +Epoch 16/50: loss=0.3593, val_top1=0.6086, lr=0.000032 +Epoch 17/50: loss=0.3540, val_top1=0.6100, lr=0.000034 +Epoch 18/50: loss=0.3492, val_top1=0.6100, lr=0.000036 +Epoch 19/50: loss=0.3447, val_top1=0.6143, lr=0.000038 +Epoch 20/50: loss=0.3413, val_top1=0.6086, lr=0.000040 +Epoch 21/50: loss=0.3376, val_top1=0.6057, lr=0.000042 +Epoch 22/50: loss=0.3347, val_top1=0.6086, lr=0.000044 +Epoch 23/50: loss=0.3306, val_top1=0.6114, lr=0.000046 +Epoch 24/50: loss=0.3279, val_top1=0.6157, lr=0.000048 +Epoch 25/50: loss=0.3241, val_top1=0.6186, lr=0.000050 +Epoch 26/50: loss=0.3225, val_top1=0.6186, lr=0.000052 +Epoch 27/50: loss=0.3169, val_top1=0.6286, lr=0.000054 +Epoch 28/50: loss=0.3135, val_top1=0.6186, lr=0.000056 +Epoch 29/50: loss=0.3111, val_top1=0.6143, lr=0.000058 +Epoch 30/50: loss=0.3069, val_top1=0.6300, lr=0.000060 +Epoch 31/50: loss=0.3015, val_top1=0.6157, lr=0.000062 +Epoch 32/50: loss=0.2988, val_top1=0.6386, lr=0.000064 +Epoch 33/50: loss=0.2952, val_top1=0.6271, lr=0.000066 +Epoch 34/50: loss=0.2910, val_top1=0.6357, lr=0.000068 +Epoch 35/50: loss=0.2892, val_top1=0.6457, lr=0.000070 +Epoch 36/50: loss=0.2850, val_top1=0.6271, lr=0.000072 +Epoch 37/50: loss=0.2836, val_top1=0.6400, lr=0.000074 +Epoch 38/50: loss=0.2812, val_top1=0.6400, lr=0.000076 +Epoch 39/50: loss=0.2784, val_top1=0.6329, lr=0.000078 +Epoch 40/50: loss=0.2765, val_top1=0.6457, lr=0.000080 +Epoch 41/50: loss=0.2737, val_top1=0.6171, lr=0.000082 +Epoch 42/50: loss=0.2709, val_top1=0.6429, lr=0.000084 +Epoch 43/50: loss=0.2681, val_top1=0.6343, lr=0.000086 +Epoch 44/50: loss=0.2671, val_top1=0.6400, lr=0.000088 +Epoch 45/50: loss=0.2664, val_top1=0.6386, lr=0.000090 +Epoch 46/50: loss=0.2658, val_top1=0.6457, lr=0.000092 +Epoch 47/50: loss=0.2623, val_top1=0.6200, lr=0.000094 +Epoch 48/50: loss=0.2603, val_top1=0.6229, lr=0.000096 +Epoch 49/50: loss=0.2564, val_top1=0.6314, lr=0.000098 +Epoch 50/50: loss=0.2554, val_top1=0.6243, lr=0.000100 + +✅ Training complete! Best val top-1: 0.6457 + +✅ Transformer training complete (seed 0) + +Next: Evaluate and compare diff --git a/workspace/logs/transformer_train_14707188_1.err b/workspace/logs/transformer_train_14707188_1.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/transformer_train_14707188_1.out b/workspace/logs/transformer_train_14707188_1.out new file mode 100644 index 0000000000000000000000000000000000000000..bb786c055d4b66f65efdb3ac1c69ddde0c9046e0 --- /dev/null +++ b/workspace/logs/transformer_train_14707188_1.out @@ -0,0 +1,97 @@ += = = = = = = = = = = = = = = = = = +DoVLA-Transformer: BREAKTHROUGH Architecture += = = = = = = = = = = = = = = = = = + +Pure Transformer Components: + - Multi-head self-attention (8 heads) + - Cross-attention for obs-lang fusion + - 3 Transformer encoder layers + - Positional encoding + - Residual connections everywhere + +Key Improvements: + - Higher LR: 0.001 (vs 0.0003 failed Enhanced) + - Warmup scheduler: 500 steps + - No custom GNN (proven Transformer only) + - Proper gradient flow (residuals) + +Dataset: 3,500 groups (fair comparison) +Seed: 1 + +Expected: 42-47% success +vs Baseline: 38.43% +vs Enhanced (failed): 36.31% + +====================================================================== +DoVLA-Transformer Training (BREAKTHROUGH) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Architecture: Pure Transformer (d=256, heads=8, layers=3) +LR: 0.001 (higher than failed Enhanced 0.0003) +Warmup: 500 steps +Seed: 1 + +Loading dataset... +Total: 3500, Train: 2800, Val: 700 + +Model parameters: 5,851,649 + +Starting training... + +Epoch 1/50: loss=1.2321, val_top1=0.1814, lr=0.000002 +Epoch 2/50: loss=0.7357, val_top1=0.3657, lr=0.000004 +Epoch 3/50: loss=0.5378, val_top1=0.4243, lr=0.000006 +Epoch 4/50: loss=0.4795, val_top1=0.5900, lr=0.000008 +Epoch 5/50: loss=0.4603, val_top1=0.5929, lr=0.000010 +Epoch 6/50: loss=0.4471, val_top1=0.6000, lr=0.000012 +Epoch 7/50: loss=0.4356, val_top1=0.6014, lr=0.000014 +Epoch 8/50: loss=0.4280, val_top1=0.6014, lr=0.000016 +Epoch 9/50: loss=0.4204, val_top1=0.6029, lr=0.000018 +Epoch 10/50: loss=0.4140, val_top1=0.6043, lr=0.000020 +Epoch 11/50: loss=0.4046, val_top1=0.6043, lr=0.000022 +Epoch 12/50: loss=0.3956, val_top1=0.6057, lr=0.000024 +Epoch 13/50: loss=0.3852, val_top1=0.6100, lr=0.000026 +Epoch 14/50: loss=0.3737, val_top1=0.6214, lr=0.000028 +Epoch 15/50: loss=0.3665, val_top1=0.6186, lr=0.000030 +Epoch 16/50: loss=0.3616, val_top1=0.6257, lr=0.000032 +Epoch 17/50: loss=0.3548, val_top1=0.6271, lr=0.000034 +Epoch 18/50: loss=0.3514, val_top1=0.6286, lr=0.000036 +Epoch 19/50: loss=0.3458, val_top1=0.6314, lr=0.000038 +Epoch 20/50: loss=0.3427, val_top1=0.6271, lr=0.000040 +Epoch 21/50: loss=0.3385, val_top1=0.6257, lr=0.000042 +Epoch 22/50: loss=0.3349, val_top1=0.6143, lr=0.000044 +Epoch 23/50: loss=0.3327, val_top1=0.6229, lr=0.000046 +Epoch 24/50: loss=0.3289, val_top1=0.6200, lr=0.000048 +Epoch 25/50: loss=0.3264, val_top1=0.6129, lr=0.000050 +Epoch 26/50: loss=0.3234, val_top1=0.6114, lr=0.000052 +Epoch 27/50: loss=0.3195, val_top1=0.6171, lr=0.000054 +Epoch 28/50: loss=0.3168, val_top1=0.6300, lr=0.000056 +Epoch 29/50: loss=0.3126, val_top1=0.6143, lr=0.000058 +Epoch 30/50: loss=0.3082, val_top1=0.6100, lr=0.000060 +Epoch 31/50: loss=0.3067, val_top1=0.6186, lr=0.000062 +Epoch 32/50: loss=0.3014, val_top1=0.6086, lr=0.000064 +Epoch 33/50: loss=0.2981, val_top1=0.6114, lr=0.000066 +Epoch 34/50: loss=0.2946, val_top1=0.6071, lr=0.000068 +Epoch 35/50: loss=0.2914, val_top1=0.6143, lr=0.000070 +Epoch 36/50: loss=0.2888, val_top1=0.6229, lr=0.000072 +Epoch 37/50: loss=0.2862, val_top1=0.6100, lr=0.000074 +Epoch 38/50: loss=0.2824, val_top1=0.6200, lr=0.000076 +Epoch 39/50: loss=0.2812, val_top1=0.6129, lr=0.000078 +Epoch 40/50: loss=0.2788, val_top1=0.6200, lr=0.000080 +Epoch 41/50: loss=0.2763, val_top1=0.6129, lr=0.000082 +Epoch 42/50: loss=0.2757, val_top1=0.6143, lr=0.000084 +Epoch 43/50: loss=0.2717, val_top1=0.6300, lr=0.000086 +Epoch 44/50: loss=0.2707, val_top1=0.6171, lr=0.000088 +Epoch 45/50: loss=0.2681, val_top1=0.6114, lr=0.000090 +Epoch 46/50: loss=0.2668, val_top1=0.6114, lr=0.000092 +Epoch 47/50: loss=0.2639, val_top1=0.6057, lr=0.000094 +Epoch 48/50: loss=0.2616, val_top1=0.6057, lr=0.000096 +Epoch 49/50: loss=0.2592, val_top1=0.6014, lr=0.000098 +Epoch 50/50: loss=0.2575, val_top1=0.6086, lr=0.000100 + +✅ Training complete! Best val top-1: 0.6314 + +✅ Transformer training complete (seed 1) + +Next: Evaluate and compare diff --git a/workspace/logs/transformer_train_14707188_2.err b/workspace/logs/transformer_train_14707188_2.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/workspace/logs/transformer_train_14707188_2.out b/workspace/logs/transformer_train_14707188_2.out new file mode 100644 index 0000000000000000000000000000000000000000..a0c83a14472dfabccbd4678b26058edcd9ca3f8f --- /dev/null +++ b/workspace/logs/transformer_train_14707188_2.out @@ -0,0 +1,97 @@ += = = = = = = = = = = = = = = = = = +DoVLA-Transformer: BREAKTHROUGH Architecture += = = = = = = = = = = = = = = = = = + +Pure Transformer Components: + - Multi-head self-attention (8 heads) + - Cross-attention for obs-lang fusion + - 3 Transformer encoder layers + - Positional encoding + - Residual connections everywhere + +Key Improvements: + - Higher LR: 0.001 (vs 0.0003 failed Enhanced) + - Warmup scheduler: 500 steps + - No custom GNN (proven Transformer only) + - Proper gradient flow (residuals) + +Dataset: 3,500 groups (fair comparison) +Seed: 2 + +Expected: 42-47% success +vs Baseline: 38.43% +vs Enhanced (failed): 36.31% + +====================================================================== +DoVLA-Transformer Training (BREAKTHROUGH) +====================================================================== +Dataset: /scratch/knguy52/dovla/experiments/maniskill_presuccess_six_task_collection +Device: cuda +Architecture: Pure Transformer (d=256, heads=8, layers=3) +LR: 0.001 (higher than failed Enhanced 0.0003) +Warmup: 500 steps +Seed: 2 + +Loading dataset... +Total: 3500, Train: 2800, Val: 700 + +Model parameters: 5,851,649 + +Starting training... + +Epoch 1/50: loss=0.7525, val_top1=0.1000, lr=0.000002 +Epoch 2/50: loss=0.6838, val_top1=0.5700, lr=0.000004 +Epoch 3/50: loss=0.5478, val_top1=0.5600, lr=0.000006 +Epoch 4/50: loss=0.4787, val_top1=0.6157, lr=0.000008 +Epoch 5/50: loss=0.4602, val_top1=0.6129, lr=0.000010 +Epoch 6/50: loss=0.4455, val_top1=0.6114, lr=0.000012 +Epoch 7/50: loss=0.4358, val_top1=0.6071, lr=0.000014 +Epoch 8/50: loss=0.4277, val_top1=0.6114, lr=0.000016 +Epoch 9/50: loss=0.4208, val_top1=0.6129, lr=0.000018 +Epoch 10/50: loss=0.4138, val_top1=0.6171, lr=0.000020 +Epoch 11/50: loss=0.4061, val_top1=0.6143, lr=0.000022 +Epoch 12/50: loss=0.3971, val_top1=0.6214, lr=0.000024 +Epoch 13/50: loss=0.3880, val_top1=0.6186, lr=0.000026 +Epoch 14/50: loss=0.3786, val_top1=0.6214, lr=0.000028 +Epoch 15/50: loss=0.3709, val_top1=0.6257, lr=0.000030 +Epoch 16/50: loss=0.3622, val_top1=0.6329, lr=0.000032 +Epoch 17/50: loss=0.3563, val_top1=0.6243, lr=0.000034 +Epoch 18/50: loss=0.3511, val_top1=0.6300, lr=0.000036 +Epoch 19/50: loss=0.3454, val_top1=0.6186, lr=0.000038 +Epoch 20/50: loss=0.3409, val_top1=0.6143, lr=0.000040 +Epoch 21/50: loss=0.3383, val_top1=0.6200, lr=0.000042 +Epoch 22/50: loss=0.3352, val_top1=0.6129, lr=0.000044 +Epoch 23/50: loss=0.3318, val_top1=0.6114, lr=0.000046 +Epoch 24/50: loss=0.3265, val_top1=0.6100, lr=0.000048 +Epoch 25/50: loss=0.3248, val_top1=0.6086, lr=0.000050 +Epoch 26/50: loss=0.3195, val_top1=0.6143, lr=0.000052 +Epoch 27/50: loss=0.3179, val_top1=0.6114, lr=0.000054 +Epoch 28/50: loss=0.3123, val_top1=0.6129, lr=0.000056 +Epoch 29/50: loss=0.3104, val_top1=0.6186, lr=0.000058 +Epoch 30/50: loss=0.3063, val_top1=0.6071, lr=0.000060 +Epoch 31/50: loss=0.3040, val_top1=0.6086, lr=0.000062 +Epoch 32/50: loss=0.2996, val_top1=0.6157, lr=0.000064 +Epoch 33/50: loss=0.2964, val_top1=0.6057, lr=0.000066 +Epoch 34/50: loss=0.2945, val_top1=0.6186, lr=0.000068 +Epoch 35/50: loss=0.2911, val_top1=0.6171, lr=0.000070 +Epoch 36/50: loss=0.2883, val_top1=0.6171, lr=0.000072 +Epoch 37/50: loss=0.2859, val_top1=0.6100, lr=0.000074 +Epoch 38/50: loss=0.2853, val_top1=0.6171, lr=0.000076 +Epoch 39/50: loss=0.2811, val_top1=0.6114, lr=0.000078 +Epoch 40/50: loss=0.2796, val_top1=0.6157, lr=0.000080 +Epoch 41/50: loss=0.2780, val_top1=0.6129, lr=0.000082 +Epoch 42/50: loss=0.2747, val_top1=0.6157, lr=0.000084 +Epoch 43/50: loss=0.2723, val_top1=0.6157, lr=0.000086 +Epoch 44/50: loss=0.2711, val_top1=0.6129, lr=0.000088 +Epoch 45/50: loss=0.2687, val_top1=0.6129, lr=0.000090 +Epoch 46/50: loss=0.2668, val_top1=0.6100, lr=0.000092 +Epoch 47/50: loss=0.2651, val_top1=0.6143, lr=0.000094 +Epoch 48/50: loss=0.2622, val_top1=0.6129, lr=0.000096 +Epoch 49/50: loss=0.2609, val_top1=0.6129, lr=0.000098 +Epoch 50/50: loss=0.2603, val_top1=0.6071, lr=0.000100 + +✅ Training complete! Best val top-1: 0.6329 + +✅ Transformer training complete (seed 2) + +Next: Evaluate and compare diff --git a/workspace/logs/workflow/master_workflow_20260623_093145.log b/workspace/logs/workflow/master_workflow_20260623_093145.log new file mode 100644 index 0000000000000000000000000000000000000000..6abc2b3b33376248c59bc41cb42bd53624dffb5d --- /dev/null +++ b/workspace/logs/workflow/master_workflow_20260623_093145.log @@ -0,0 +1,50 @@ +[2026-06-23 09:31:45] = = = = = = = = = = = = = = = = = = +[2026-06-23 09:31:45] DoVLA-CIL A* Paper Workflow - Master Orchestration +[2026-06-23 09:31:45] = = = = = = = = = = = = = = = = = = +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] Target: A* oral paper with 9/10 novelty +[2026-06-23 09:31:45] Timeline: 6-8 weeks +[2026-06-23 09:31:45] Compute: ~250-350 GPU hours +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] 🔍 DRY RUN MODE - No jobs will be submitted +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] = = = = = = = = = = = = = = = = = = +[2026-06-23 09:31:45] PHASE A: PERFORMANCE IMPROVEMENT +[2026-06-23 09:31:45] Target: 40%+ policy success (vs 29.67% baseline) +[2026-06-23 09:31:45] = = = = = = = = = = = = = = = = = = +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] Phase A1: Generate 10K group dataset +[2026-06-23 09:31:45] Expected: 3-4 days, ~20 GPU hours +[2026-06-23 09:31:45] Output: /scratch/knguy52/dovla/experiments/phase_a_10k_collection +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] [DRY RUN] Would submit: scripts/slurm/phase_a1_generate_10k.sbatch +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] Phase A2: Train large capacity model (3 seeds) +[2026-06-23 09:31:45] Expected: 2-3 days, ~30 GPU hours per seed +[2026-06-23 09:31:45] Config: hidden_dim=512, 100 epochs +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] [DRY RUN] Would submit: scripts/slurm/phase_a2_train_large_model.sbatch +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] Phase A3: Evaluate large model +[2026-06-23 09:31:45] Lattice eval + policy rollout on 700 held-out groups +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] [DRY RUN] Would submit: scripts/slurm/phase_a3_eval_large_model.sbatch +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] Phase A4 & A5: Hyperparameter and horizon sweeps (parallel) +[2026-06-23 09:31:45] A4: 9 configs (3 LR × 3 hidden_dim) +[2026-06-23 09:31:45] A5: 4 horizons (H=4,8,12,16) +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] [DRY RUN] Would submit parallel: +[2026-06-23 09:31:45] scripts/slurm/phase_a4_hparam_sweep.sbatch +[2026-06-23 09:31:45] scripts/slurm/phase_a5_horizon_sweep.sbatch +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] = = = = = = = = = = = = = = = = = = +[2026-06-23 09:31:45] PHASE A: COMPLETE +[2026-06-23 09:31:45] = = = = = = = = = = = = = = = = = = +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] Next: Analyze Phase A results and proceed to Phase B +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] Analyzing Phase A results... +[2026-06-23 09:31:45] +[2026-06-23 09:31:45] = = = = = = = = = = = = = = = = = = +[2026-06-23 09:31:45] PHASE B: SECOND BENCHMARK diff --git a/workspace/manifests/baselines_full.yaml b/workspace/manifests/baselines_full.yaml new file mode 100644 index 0000000000000000000000000000000000000000..42851411ec872de335c38b2cfce1c11dbf146665 --- /dev/null +++ b/workspace/manifests/baselines_full.yaml @@ -0,0 +1,73 @@ +name: baselines_full +description: Full baseline comparison manifest. +run_dir: ${DOVLA_RUN_ROOT:-runs}/baselines_full + +dataset_generation: + backend: toy + simulator_params: {} + task_source: builtins + num_tasks: 20 + num_states_per_task: 256 + k: 16 + shard_size: 1000 + output_path: ${DOVLA_DATA_ROOT:-data}/cil_baselines_source + seed: ${DOVLA_SEED:-0} + +vlm_annotation: + enabled: false + cache_path: ${DOVLA_CACHE_ROOT:-.cache/dovla_cil}/vlm_annotations_baselines.json + model_env_var: OPENCLAUDE_MODEL + +training: + model_size: small + hidden_dim: 256 + batch_groups: 8 + records_per_group: 8 + learning_rate: 0.001 + loss_weights: + bc: 1.0 + effect: 1.0 + success: 1.0 + progress: 1.0 + rank: 1.0 + regret: 0.5 + epochs: 3 + steps: null + checkpoint_path: ${DOVLA_RUN_ROOT:-runs}/baselines_full/train/best.pt + +evaluation: + causalstress: + enabled: true + backend: toy + num_tasks: 100 + k: 16 + output_path: ${DOVLA_RUN_ROOT:-runs}/baselines_full/eval/causalstress.json + libero: + enabled: false + placeholder: true + maniskill: + enabled: false + placeholder: true + simpler: + enabled: false + placeholder: true + +baselines: + enabled: true + output_root: ${DOVLA_RUN_ROOT:-runs}/baselines_full/baselines + names: + - expert_only_bc + - more_independent_demos + - random_negatives + - cross_state_negatives + - label_only_counterfactual + - world_model_auxiliary + - no_effect_head + - no_rank_regret + +scaling_sweeps: + enabled: false + output_path: ${DOVLA_RUN_ROOT:-runs}/baselines_full/scaling + total_records: 4096 + k_values: [1, 2, 4, 8, 16] + epochs: 1 diff --git a/workspace/manifests/cil_160m.yaml b/workspace/manifests/cil_160m.yaml new file mode 100644 index 0000000000000000000000000000000000000000..42b1c2f986bc4ef5cd1829aaf70d5028731194a8 --- /dev/null +++ b/workspace/manifests/cil_160m.yaml @@ -0,0 +1,87 @@ +name: cil_160m +description: 160M-record CIL generation/training/evaluation plan. +run_dir: ${DOVLA_RUN_ROOT:-runs}/cil_160m + +dataset_generation: + backend: maniskill + simulator_params: + demo_path: ${MANISKILL_DEMO_PATH:-data/demos/PickCube-v1/trajectory.h5} + env_id: ${MANISKILL_ENV_ID:-PickCube-v1} + obs_mode: state + control_mode: pd_ee_delta_pose + render_mode: rgb_array + sim_backend: physx_cuda:0 + render_backend: cpu + horizon: 4 + state_batch_size: 16 + parallel_branches: true + state_storage: archive + image_quality: 90 + task_source: ${DOVLA_TASKS_PATH:-data/tasks/maniskill_tasks.jsonl} + num_tasks: 10000 + num_states_per_task: 500 + k: 32 + shard_size: 10000 + output_path: ${DOVLA_DATA_ROOT:-data}/cil_160m + seed: ${DOVLA_SEED:-0} + +vlm_annotation: + enabled: false + cache_path: ${DOVLA_CACHE_ROOT:-.cache/dovla_cil}/vlm_annotations_cil_160m.json + model_env_var: OPENCLAUDE_MODEL + +training: + model_size: base + hidden_dim: 768 + batch_groups: 64 + records_per_group: 16 + learning_rate: 0.0001 + loss_weights: + bc: 1.0 + effect: 1.0 + success: 1.0 + progress: 1.0 + rank: 1.0 + regret: 0.5 + contrast: 0.5 + lang_pair: 0.25 + epochs: 5 + steps: null + checkpoint_path: ${DOVLA_RUN_ROOT:-runs}/cil_160m/train/best.pt + +evaluation: + causalstress: + enabled: true + backend: toy + num_tasks: 200 + k: 32 + output_path: ${DOVLA_RUN_ROOT:-runs}/cil_160m/eval/causalstress.json + libero: + enabled: false + placeholder: true + maniskill: + enabled: false + placeholder: true + simpler: + enabled: false + placeholder: true + +baselines: + enabled: true + output_root: ${DOVLA_RUN_ROOT:-runs}/cil_160m/baselines + names: + - expert_only_bc + - more_independent_demos + - random_negatives + - cross_state_negatives + - label_only_counterfactual + - world_model_auxiliary + - no_effect_head + - no_rank_regret + +scaling_sweeps: + enabled: false + output_path: ${DOVLA_RUN_ROOT:-runs}/cil_160m/scaling + total_records: 160000000 + k_values: [1, 2, 4, 8, 16, 32] + epochs: 3 diff --git a/workspace/manifests/cil_1b_template.yaml b/workspace/manifests/cil_1b_template.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ef62bfbaba54e439c24bc957b129d652a2c58b84 --- /dev/null +++ b/workspace/manifests/cil_1b_template.yaml @@ -0,0 +1,84 @@ +name: cil_1b_template +description: Template for a 1B-record CIL run. Adjust paths, scheduler, and simulator mapping. +run_dir: ${DOVLA_RUN_ROOT:-runs}/cil_1b + +dataset_generation: + backend: maniskill + simulator_params: + demo_path: ${MANISKILL_DEMO_PATH:-data/demos/PickCube-v1/trajectory.h5} + env_id: ${MANISKILL_ENV_ID:-PickCube-v1} + obs_mode: state + control_mode: pd_ee_delta_pose + render_mode: rgb_array + sim_backend: physx_cuda:0 + render_backend: cpu + horizon: 4 + state_batch_size: 32 + parallel_branches: true + state_storage: archive + image_quality: 90 + task_source: ${DOVLA_TASKS_PATH:-data/tasks/maniskill_tasks.jsonl} + num_tasks: 50000 + num_states_per_task: 625 + k: 32 + shard_size: 10000 + output_path: ${DOVLA_DATA_ROOT:-data}/cil_1b + seed: ${DOVLA_SEED:-0} + +vlm_annotation: + enabled: false + cache_path: ${DOVLA_CACHE_ROOT:-.cache/dovla_cil}/vlm_annotations_cil_1b.json + model_env_var: OPENCLAUDE_MODEL + +training: + model_size: large + hidden_dim: 1024 + batch_groups: 128 + records_per_group: 16 + learning_rate: 0.00005 + loss_weights: + bc: 1.0 + effect: 1.0 + success: 1.0 + progress: 1.0 + rank: 1.0 + regret: 0.5 + contrast: 0.5 + lang_pair: 0.25 + epochs: 3 + steps: null + checkpoint_path: ${DOVLA_RUN_ROOT:-runs}/cil_1b/train/best.pt + +evaluation: + causalstress: + enabled: true + backend: toy + num_tasks: 500 + k: 32 + output_path: ${DOVLA_RUN_ROOT:-runs}/cil_1b/eval/causalstress.json + libero: + enabled: false + placeholder: true + maniskill: + enabled: false + placeholder: true + simpler: + enabled: false + placeholder: true + +baselines: + enabled: true + output_root: ${DOVLA_RUN_ROOT:-runs}/cil_1b/baselines + names: + - expert_only_bc + - random_negatives + - cross_state_negatives + - label_only_counterfactual + - world_model_auxiliary + +scaling_sweeps: + enabled: false + output_path: ${DOVLA_RUN_ROOT:-runs}/cil_1b/scaling + total_records: 1000000000 + k_values: [1, 2, 4, 8, 16, 32, 64] + epochs: 2 diff --git a/workspace/manifests/scaling_k_sweep.yaml b/workspace/manifests/scaling_k_sweep.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f8592f05dc528fa49555bf944e4563249ec97352 --- /dev/null +++ b/workspace/manifests/scaling_k_sweep.yaml @@ -0,0 +1,74 @@ +name: scaling_k_sweep +description: Controlled K sweep with fixed total record budget. +run_dir: ${DOVLA_RUN_ROOT:-runs}/scaling_k_sweep + +dataset_generation: + backend: toy + simulator_params: {} + task_source: builtins + num_tasks: 10 + num_states_per_task: 128 + k: 8 + shard_size: 1000 + output_path: ${DOVLA_DATA_ROOT:-data}/scaling_seed_dataset + seed: ${DOVLA_SEED:-0} + +vlm_annotation: + enabled: false + cache_path: ${DOVLA_CACHE_ROOT:-.cache/dovla_cil}/vlm_annotations_scaling.json + model_env_var: OPENCLAUDE_MODEL + +training: + model_size: small + hidden_dim: 256 + batch_groups: 8 + records_per_group: 8 + learning_rate: 0.001 + loss_weights: + bc: 1.0 + effect: 1.0 + success: 1.0 + progress: 1.0 + rank: 1.0 + regret: 0.5 + epochs: 3 + steps: null + checkpoint_path: ${DOVLA_RUN_ROOT:-runs}/scaling_k_sweep/train/best.pt + +evaluation: + causalstress: + enabled: true + backend: toy + num_tasks: 100 + k: 16 + output_path: ${DOVLA_RUN_ROOT:-runs}/scaling_k_sweep/eval/causalstress.json + libero: + enabled: false + placeholder: true + maniskill: + enabled: false + placeholder: true + simpler: + enabled: false + placeholder: true + +baselines: + enabled: false + output_root: ${DOVLA_RUN_ROOT:-runs}/scaling_k_sweep/baselines + names: [] + +scaling_sweeps: + enabled: true + backend: toy + task_source: builtins + output_path: ${DOVLA_RUN_ROOT:-runs}/scaling_k_sweep/scaling + total_records: 4096 + k_values: [1, 2, 4, 8, 16, 32] + epochs: 3 + seed: ${DOVLA_SEED:-0} + shard_size: 1000 + batch_groups: 8 + records_per_group: 8 + hidden_dim: 256 + learning_rate: 0.001 + eval_num_tasks: 50 diff --git a/workspace/outputs/audit_venv/bin/Activate.ps1 b/workspace/outputs/audit_venv/bin/Activate.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..b49d77ba44b24fe6d69f6bbe75139b3b5dc23075 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/workspace/outputs/audit_venv/bin/activate b/workspace/outputs/audit_venv/bin/activate new file mode 100644 index 0000000000000000000000000000000000000000..44fdf874f80bfd2da56437a390ec981f0c129343 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/activate @@ -0,0 +1,69 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null + fi + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV="/lustre09/project/6037638/knguy52/vla/outputs/audit_venv" +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/bin:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1="(audit_venv) ${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT="(audit_venv) " + export VIRTUAL_ENV_PROMPT +fi + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null +fi diff --git a/workspace/outputs/audit_venv/bin/activate.csh b/workspace/outputs/audit_venv/bin/activate.csh new file mode 100644 index 0000000000000000000000000000000000000000..13ff9586082747b14d34687f3bb8c77608377d04 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/activate.csh @@ -0,0 +1,26 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV "/lustre09/project/6037638/knguy52/vla/outputs/audit_venv" + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/bin:$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = "(audit_venv) $prompt" + setenv VIRTUAL_ENV_PROMPT "(audit_venv) " +endif + +alias pydoc python -m pydoc + +rehash diff --git a/workspace/outputs/audit_venv/bin/activate.fish b/workspace/outputs/audit_venv/bin/activate.fish new file mode 100644 index 0000000000000000000000000000000000000000..ee408bc8b6f59f5702a532bbe45fdbfe1aed75d9 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/activate.fish @@ -0,0 +1,69 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/); you cannot run it directly. + +function deactivate -d "Exit virtual environment and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + set -e _OLD_FISH_PROMPT_OVERRIDE + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV "/lustre09/project/6037638/knguy52/vla/outputs/audit_venv" + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/bin" $PATH + +# Unset PYTHONHOME if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # Save the current fish_prompt function as the function _old_fish_prompt. + functions -c fish_prompt _old_fish_prompt + + # With the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command. + set -l old_status $status + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) "(audit_venv) " (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT "(audit_venv) " +end diff --git a/workspace/outputs/audit_venv/bin/distro b/workspace/outputs/audit_venv/bin/distro new file mode 100644 index 0000000000000000000000000000000000000000..73e5afd5ddf31c21dcaada4734690c9df0ad2683 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/distro @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from distro.distro import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/f2py b/workspace/outputs/audit_venv/bin/f2py new file mode 100644 index 0000000000000000000000000000000000000000..6debaf29b9d75981c59be25b769fbecb72f89e68 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/f2py @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from numpy.f2py.f2py2e import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/fonttools b/workspace/outputs/audit_venv/bin/fonttools new file mode 100644 index 0000000000000000000000000000000000000000..70681c81435c005e1850d9bdf68941bb583d6ef1 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/fonttools @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from fontTools.__main__ import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/httpx b/workspace/outputs/audit_venv/bin/httpx new file mode 100644 index 0000000000000000000000000000000000000000..96216c37779d36d59dfb04e27e7e59d2bcad026a --- /dev/null +++ b/workspace/outputs/audit_venv/bin/httpx @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from httpx import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/idna b/workspace/outputs/audit_venv/bin/idna new file mode 100644 index 0000000000000000000000000000000000000000..80c81f2518d3c65a2315c93828ba17b27a50939d --- /dev/null +++ b/workspace/outputs/audit_venv/bin/idna @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from idna.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/isympy b/workspace/outputs/audit_venv/bin/isympy new file mode 100644 index 0000000000000000000000000000000000000000..99ef2cc3bf82f7ea7708fb7497bcd35c6ec5b45c --- /dev/null +++ b/workspace/outputs/audit_venv/bin/isympy @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from isympy import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/markdown-it b/workspace/outputs/audit_venv/bin/markdown-it new file mode 100644 index 0000000000000000000000000000000000000000..eee8f26b8aa16209834f1633fa672f7da5c90aea --- /dev/null +++ b/workspace/outputs/audit_venv/bin/markdown-it @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from markdown_it.cli.parse import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/numpy-config b/workspace/outputs/audit_venv/bin/numpy-config new file mode 100644 index 0000000000000000000000000000000000000000..2988648f7a60a14f79ac9393816a8b212473b81a --- /dev/null +++ b/workspace/outputs/audit_venv/bin/numpy-config @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from numpy._configtool import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/pip b/workspace/outputs/audit_venv/bin/pip new file mode 100644 index 0000000000000000000000000000000000000000..4c0a8b5692241a28c9feebe823f23ae73e112b2c --- /dev/null +++ b/workspace/outputs/audit_venv/bin/pip @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/pip3 b/workspace/outputs/audit_venv/bin/pip3 new file mode 100644 index 0000000000000000000000000000000000000000..4c0a8b5692241a28c9feebe823f23ae73e112b2c --- /dev/null +++ b/workspace/outputs/audit_venv/bin/pip3 @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/pip3.11 b/workspace/outputs/audit_venv/bin/pip3.11 new file mode 100644 index 0000000000000000000000000000000000000000..4c0a8b5692241a28c9feebe823f23ae73e112b2c --- /dev/null +++ b/workspace/outputs/audit_venv/bin/pip3.11 @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/py.test b/workspace/outputs/audit_venv/bin/py.test new file mode 100644 index 0000000000000000000000000000000000000000..38afca586ac3d9c68b65433e6b854875e9d28580 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/py.test @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from _pytest.config import _console_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(_console_main()) diff --git a/workspace/outputs/audit_venv/bin/pyftmerge b/workspace/outputs/audit_venv/bin/pyftmerge new file mode 100644 index 0000000000000000000000000000000000000000..19b32271239a31396521d996f95225d7307c84b6 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/pyftmerge @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from fontTools.merge import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/pyftsubset b/workspace/outputs/audit_venv/bin/pyftsubset new file mode 100644 index 0000000000000000000000000000000000000000..1b76c6d6384b8d85f00cb3d002ea62ee736989cc --- /dev/null +++ b/workspace/outputs/audit_venv/bin/pyftsubset @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from fontTools.subset import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/pygmentize b/workspace/outputs/audit_venv/bin/pygmentize new file mode 100644 index 0000000000000000000000000000000000000000..e1189a4af0e8fd5653fba2cbd3920a8ea07f924d --- /dev/null +++ b/workspace/outputs/audit_venv/bin/pygmentize @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from pygments.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/pytest b/workspace/outputs/audit_venv/bin/pytest new file mode 100644 index 0000000000000000000000000000000000000000..38afca586ac3d9c68b65433e6b854875e9d28580 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/pytest @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from _pytest.config import _console_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(_console_main()) diff --git a/workspace/outputs/audit_venv/bin/python b/workspace/outputs/audit_venv/bin/python new file mode 100644 index 0000000000000000000000000000000000000000..21c665a624b6206d2fcb66c8c35e5c3fdc4ac8c4 Binary files /dev/null and b/workspace/outputs/audit_venv/bin/python differ diff --git a/workspace/outputs/audit_venv/bin/python3 b/workspace/outputs/audit_venv/bin/python3 new file mode 100644 index 0000000000000000000000000000000000000000..21c665a624b6206d2fcb66c8c35e5c3fdc4ac8c4 Binary files /dev/null and b/workspace/outputs/audit_venv/bin/python3 differ diff --git a/workspace/outputs/audit_venv/bin/python3.11 b/workspace/outputs/audit_venv/bin/python3.11 new file mode 100644 index 0000000000000000000000000000000000000000..21c665a624b6206d2fcb66c8c35e5c3fdc4ac8c4 Binary files /dev/null and b/workspace/outputs/audit_venv/bin/python3.11 differ diff --git a/workspace/outputs/audit_venv/bin/torchrun b/workspace/outputs/audit_venv/bin/torchrun new file mode 100644 index 0000000000000000000000000000000000000000..71d1ba6d1be06db1faab7f5b5b070bfb279a6910 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/torchrun @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from torch.distributed.run import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/tqdm b/workspace/outputs/audit_venv/bin/tqdm new file mode 100644 index 0000000000000000000000000000000000000000..1919a50e4425631e0033b3ec1553c63937ded7b7 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/tqdm @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from tqdm.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/bin/ttx b/workspace/outputs/audit_venv/bin/ttx new file mode 100644 index 0000000000000000000000000000000000000000..2b42c8e44e53f8d3c04292f53deac266f2f98299 --- /dev/null +++ b/workspace/outputs/audit_venv/bin/ttx @@ -0,0 +1,8 @@ +#!/lustre09/project/6037638/knguy52/vla/outputs/audit_venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from fontTools.ttx import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/AvifImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/AvifImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..43c39a9fbe78b17bfd4a5440bad787bc8070be1e --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/AvifImagePlugin.py @@ -0,0 +1,293 @@ +from __future__ import annotations + +import os +from io import BytesIO +from typing import IO + +from . import ExifTags, Image, ImageFile + +try: + from . import _avif + + SUPPORTED = True +except ImportError: + SUPPORTED = False + +# Decoder options as module globals, until there is a way to pass parameters +# to Image.open (see https://github.com/python-pillow/Pillow/issues/569) +DECODE_CODEC_CHOICE = "auto" +DEFAULT_MAX_THREADS = 0 + + +def get_codec_version(codec_name: str) -> str | None: + versions = _avif.codec_versions() + for version in versions.split(", "): + if version.split(" [")[0] == codec_name: + return version.split(":")[-1].split(" ")[0] + return None + + +def _accept(prefix: bytes) -> bool | str: + if prefix[4:8] != b"ftyp": + return False + major_brand = prefix[8:12] + if major_brand in ( + # coding brands + b"avif", + b"avis", + # We accept files with AVIF container brands; we can't yet know if + # the ftyp box has the correct compatible brands, but if it doesn't + # then the plugin will raise a SyntaxError which Pillow will catch + # before moving on to the next plugin that accepts the file. + # + # Also, because this file might not actually be an AVIF file, we + # don't raise an error if AVIF support isn't properly compiled. + b"mif1", + b"msf1", + ): + if not SUPPORTED: + return ( + "image file could not be identified because AVIF support not installed" + ) + return True + return False + + +def _get_default_max_threads() -> int: + if DEFAULT_MAX_THREADS: + return DEFAULT_MAX_THREADS + if hasattr(os, "sched_getaffinity"): + return len(os.sched_getaffinity(0)) + else: + return os.cpu_count() or 1 + + +class AvifImageFile(ImageFile.ImageFile): + format = "AVIF" + format_description = "AVIF image" + __frame = -1 + + def _open(self) -> None: + if not SUPPORTED: + msg = "image file could not be opened because AVIF support not installed" + raise SyntaxError(msg) + + if DECODE_CODEC_CHOICE != "auto" and not _avif.decoder_codec_available( + DECODE_CODEC_CHOICE + ): + msg = "Invalid opening codec" + raise ValueError(msg) + + assert self.fp is not None + self._decoder = _avif.AvifDecoder( + self.fp.read(), + DECODE_CODEC_CHOICE, + _get_default_max_threads(), + ) + + # Get info from decoder + self._size, self.n_frames, self._mode, icc, exif, exif_orientation, xmp = ( + self._decoder.get_info() + ) + self.is_animated = self.n_frames > 1 + + if icc: + self.info["icc_profile"] = icc + if xmp: + self.info["xmp"] = xmp + + if exif_orientation != 1 or exif: + exif_data = Image.Exif() + if exif: + exif_data.load(exif) + original_orientation = exif_data.get(ExifTags.Base.Orientation, 1) + else: + original_orientation = 1 + if exif_orientation != original_orientation: + exif_data[ExifTags.Base.Orientation] = exif_orientation + exif = exif_data.tobytes() + if exif: + self.info["exif"] = exif + self.seek(0) + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + + # Set tile + self.__frame = frame + self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, self.mode)] + + def load(self) -> Image.core.PixelAccess | None: + if self.tile: + # We need to load the image data for this frame + data, timescale, pts_in_timescales, duration_in_timescales = ( + self._decoder.get_frame(self.__frame) + ) + self.info["timestamp"] = round(1000 * (pts_in_timescales / timescale)) + self.info["duration"] = round(1000 * (duration_in_timescales / timescale)) + + if self.fp and self._exclusive_fp: + self.fp.close() + self.fp = BytesIO(data) + + return super().load() + + def load_seek(self, pos: int) -> None: + pass + + def tell(self) -> int: + return self.__frame + + +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + _save(im, fp, filename, save_all=True) + + +def _save( + im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False +) -> None: + info = im.encoderinfo.copy() + if save_all: + append_images = list(info.get("append_images", [])) + else: + append_images = [] + + total = 0 + for ims in [im] + append_images: + total += getattr(ims, "n_frames", 1) + + quality = info.get("quality", 75) + if not isinstance(quality, int) or quality < 0 or quality > 100: + msg = "Invalid quality setting" + raise ValueError(msg) + + duration = info.get("duration", 0) + subsampling = info.get("subsampling", "4:2:0") + speed = info.get("speed", 6) + max_threads = info.get("max_threads", _get_default_max_threads()) + codec = info.get("codec", "auto") + if codec != "auto" and not _avif.encoder_codec_available(codec): + msg = "Invalid saving codec" + raise ValueError(msg) + range_ = info.get("range", "full") + tile_rows_log2 = info.get("tile_rows", 0) + tile_cols_log2 = info.get("tile_cols", 0) + alpha_premultiplied = bool(info.get("alpha_premultiplied", False)) + autotiling = bool(info.get("autotiling", tile_rows_log2 == tile_cols_log2 == 0)) + + icc_profile = info.get("icc_profile", im.info.get("icc_profile")) + exif_orientation = 1 + if exif := info.get("exif"): + if isinstance(exif, Image.Exif): + exif_data = exif + else: + exif_data = Image.Exif() + exif_data.load(exif) + if ExifTags.Base.Orientation in exif_data: + exif_orientation = exif_data.pop(ExifTags.Base.Orientation) + exif = exif_data.tobytes() if exif_data else b"" + elif isinstance(exif, Image.Exif): + exif = exif_data.tobytes() + + xmp = info.get("xmp") + + if isinstance(xmp, str): + xmp = xmp.encode("utf-8") + + advanced = info.get("advanced") + if advanced is not None: + if isinstance(advanced, dict): + advanced = advanced.items() + try: + advanced = tuple(advanced) + except TypeError: + invalid = True + else: + invalid = any(not isinstance(v, tuple) or len(v) != 2 for v in advanced) + if invalid: + msg = ( + "advanced codec options must be a dict of key-value string " + "pairs or a series of key-value two-tuples" + ) + raise ValueError(msg) + + # Setup the AVIF encoder + enc = _avif.AvifEncoder( + im.size, + subsampling, + quality, + speed, + max_threads, + codec, + range_, + tile_rows_log2, + tile_cols_log2, + alpha_premultiplied, + autotiling, + icc_profile or b"", + exif or b"", + exif_orientation, + xmp or b"", + advanced, + ) + + # Add each frame + frame_idx = 0 + frame_duration = 0 + cur_idx = im.tell() + is_single_frame = total == 1 + try: + for ims in [im] + append_images: + # Get number of frames in this image + nfr = getattr(ims, "n_frames", 1) + + for idx in range(nfr): + ims.seek(idx) + + # Make sure image mode is supported + frame = ims + rawmode = ims.mode + if ims.mode not in {"RGB", "RGBA"}: + rawmode = "RGBA" if ims.has_transparency_data else "RGB" + frame = ims.convert(rawmode) + + # Update frame duration + if isinstance(duration, (list, tuple)): + frame_duration = duration[frame_idx] + else: + frame_duration = duration + + # Append the frame to the animation encoder + enc.add( + frame.tobytes("raw", rawmode), + frame_duration, + frame.size, + rawmode, + is_single_frame, + ) + + # Update frame index + frame_idx += 1 + + if not save_all: + break + + finally: + im.seek(cur_idx) + + # Get the final output from the encoder + data = enc.finish() + if data is None: + msg = "cannot write file as AVIF (encoder returned None)" + raise OSError(msg) + + fp.write(data) + + +Image.register_open(AvifImageFile.format, AvifImageFile, _accept) +if SUPPORTED: + Image.register_save(AvifImageFile.format, _save) + Image.register_save_all(AvifImageFile.format, _save_all) + Image.register_extensions(AvifImageFile.format, [".avif", ".avifs"]) + Image.register_mime(AvifImageFile.format, "image/avif") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/BdfFontFile.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/BdfFontFile.py new file mode 100644 index 0000000000000000000000000000000000000000..f175e2f4f80b1b232d79f15a6db0667296917c97 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/BdfFontFile.py @@ -0,0 +1,122 @@ +# +# The Python Imaging Library +# $Id$ +# +# bitmap distribution font (bdf) file parser +# +# history: +# 1996-05-16 fl created (as bdf2pil) +# 1997-08-25 fl converted to FontFile driver +# 2001-05-25 fl removed bogus __init__ call +# 2002-11-20 fl robustification (from Kevin Cazabon, Dmitry Vasiliev) +# 2003-04-22 fl more robustification (from Graham Dumpleton) +# +# Copyright (c) 1997-2003 by Secret Labs AB. +# Copyright (c) 1997-2003 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# + +""" +Parse X Bitmap Distribution Format (BDF) +""" +from __future__ import annotations + +from typing import BinaryIO + +from . import FontFile, Image + + +def bdf_char( + f: BinaryIO, +) -> ( + tuple[ + str, + int, + tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]], + Image.Image, + ] + | None +): + # skip to STARTCHAR + while True: + s = f.readline() + if not s: + return None + if s.startswith(b"STARTCHAR"): + break + id = s[9:].strip().decode("ascii") + + # load symbol properties + props = {} + while True: + s = f.readline() + if not s or s.startswith(b"BITMAP"): + break + i = s.find(b" ") + props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii") + + # load bitmap + bitmap = bytearray() + while True: + s = f.readline() + if not s or s.startswith(b"ENDCHAR"): + break + bitmap += s[:-1] + + # The word BBX + # followed by the width in x (BBw), height in y (BBh), + # and x and y displacement (BBxoff0, BByoff0) + # of the lower left corner from the origin of the character. + width, height, x_disp, y_disp = (int(p) for p in props["BBX"].split()) + + # The word DWIDTH + # followed by the width in x and y of the character in device pixels. + dwx, dwy = (int(p) for p in props["DWIDTH"].split()) + + bbox = ( + (dwx, dwy), + (x_disp, -y_disp - height, width + x_disp, -y_disp), + (0, 0, width, height), + ) + + try: + im = Image.frombytes("1", (width, height), bitmap, "hex", "1") + except ValueError: + # deal with zero-width characters + im = Image.new("1", (width, height)) + + return id, int(props["ENCODING"]), bbox, im + + +class BdfFontFile(FontFile.FontFile): + """Font file plugin for the X11 BDF format.""" + + def __init__(self, fp: BinaryIO) -> None: + super().__init__() + + s = fp.readline() + if not s.startswith(b"STARTFONT 2.1"): + msg = "not a valid BDF file" + raise SyntaxError(msg) + + props = {} + comments = [] + + while True: + s = fp.readline() + if not s or s.startswith(b"ENDPROPERTIES"): + break + i = s.find(b" ") + props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii") + if s[:i] in [b"COMMENT", b"COPYRIGHT"]: + if s.find(b"LogicalFontDescription") < 0: + comments.append(s[i + 1 : -1].decode("ascii")) + + while True: + c = bdf_char(fp) + if not c: + break + id, ch, (xy, dst, src), im = c + if 0 <= ch < len(self.glyph): + self.glyph[ch] = xy, dst, src, im diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/BlpImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/BlpImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..6bb92edf8911ab9afa18e3b3dc49b4134792b794 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/BlpImagePlugin.py @@ -0,0 +1,498 @@ +""" +Blizzard Mipmap Format (.blp) +Jerome Leclanche + +The contents of this file are hereby released in the public domain (CC0) +Full text of the CC0 license: + https://creativecommons.org/publicdomain/zero/1.0/ + +BLP1 files, used mostly in Warcraft III, are not fully supported. +All types of BLP2 files used in World of Warcraft are supported. + +The BLP file structure consists of a header, up to 16 mipmaps of the +texture + +Texture sizes must be powers of two, though the two dimensions do +not have to be equal; 512x256 is valid, but 512x200 is not. +The first mipmap (mipmap #0) is the full size image; each subsequent +mipmap halves both dimensions. The final mipmap should be 1x1. + +BLP files come in many different flavours: +* JPEG-compressed (type == 0) - only supported for BLP1. +* RAW images (type == 1, encoding == 1). Each mipmap is stored as an + array of 8-bit values, one per pixel, left to right, top to bottom. + Each value is an index to the palette. +* DXT-compressed (type == 1, encoding == 2): +- DXT1 compression is used if alpha_encoding == 0. + - An additional alpha bit is used if alpha_depth == 1. + - DXT3 compression is used if alpha_encoding == 1. + - DXT5 compression is used if alpha_encoding == 7. +""" + +from __future__ import annotations + +import abc +import os +import struct +from enum import IntEnum +from io import BytesIO +from typing import IO + +from . import Image, ImageFile + + +class Format(IntEnum): + JPEG = 0 + + +class Encoding(IntEnum): + UNCOMPRESSED = 1 + DXT = 2 + UNCOMPRESSED_RAW_BGRA = 3 + + +class AlphaEncoding(IntEnum): + DXT1 = 0 + DXT3 = 1 + DXT5 = 7 + + +def unpack_565(i: int) -> tuple[int, int, int]: + return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3 + + +def decode_dxt1( + data: bytes, alpha: bool = False +) -> tuple[bytearray, bytearray, bytearray, bytearray]: + """ + input: one "row" of data (i.e. will produce 4*width pixels) + """ + + blocks = len(data) // 8 # number of blocks in row + ret = (bytearray(), bytearray(), bytearray(), bytearray()) + + for block_index in range(blocks): + # Decode next 8-byte block. + idx = block_index * 8 + color0, color1, bits = struct.unpack_from("> 2 + + a = 0xFF + if control == 0: + r, g, b = r0, g0, b0 + elif control == 1: + r, g, b = r1, g1, b1 + elif control == 2: + if color0 > color1: + r = (2 * r0 + r1) // 3 + g = (2 * g0 + g1) // 3 + b = (2 * b0 + b1) // 3 + else: + r = (r0 + r1) // 2 + g = (g0 + g1) // 2 + b = (b0 + b1) // 2 + elif control == 3: + if color0 > color1: + r = (2 * r1 + r0) // 3 + g = (2 * g1 + g0) // 3 + b = (2 * b1 + b0) // 3 + else: + r, g, b, a = 0, 0, 0, 0 + + if alpha: + ret[j].extend([r, g, b, a]) + else: + ret[j].extend([r, g, b]) + + return ret + + +def decode_dxt3(data: bytes) -> tuple[bytearray, bytearray, bytearray, bytearray]: + """ + input: one "row" of data (i.e. will produce 4*width pixels) + """ + + blocks = len(data) // 16 # number of blocks in row + ret = (bytearray(), bytearray(), bytearray(), bytearray()) + + for block_index in range(blocks): + idx = block_index * 16 + block = data[idx : idx + 16] + # Decode next 16-byte block. + bits = struct.unpack_from("<8B", block) + color0, color1 = struct.unpack_from(">= 4 + else: + high = True + a &= 0xF + a *= 17 # We get a value between 0 and 15 + + color_code = (code >> 2 * (4 * j + i)) & 0x03 + + if color_code == 0: + r, g, b = r0, g0, b0 + elif color_code == 1: + r, g, b = r1, g1, b1 + elif color_code == 2: + r = (2 * r0 + r1) // 3 + g = (2 * g0 + g1) // 3 + b = (2 * b0 + b1) // 3 + elif color_code == 3: + r = (2 * r1 + r0) // 3 + g = (2 * g1 + g0) // 3 + b = (2 * b1 + b0) // 3 + + ret[j].extend([r, g, b, a]) + + return ret + + +def decode_dxt5(data: bytes) -> tuple[bytearray, bytearray, bytearray, bytearray]: + """ + input: one "row" of data (i.e. will produce 4 * width pixels) + """ + + blocks = len(data) // 16 # number of blocks in row + ret = (bytearray(), bytearray(), bytearray(), bytearray()) + + for block_index in range(blocks): + idx = block_index * 16 + block = data[idx : idx + 16] + # Decode next 16-byte block. + a0, a1 = struct.unpack_from("> alphacode_index) & 0x07 + elif alphacode_index == 15: + alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06) + else: # alphacode_index >= 18 and alphacode_index <= 45 + alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07 + + if alphacode == 0: + a = a0 + elif alphacode == 1: + a = a1 + elif a0 > a1: + a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7 + elif alphacode == 6: + a = 0 + elif alphacode == 7: + a = 255 + else: + a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5 + + color_code = (code >> 2 * (4 * j + i)) & 0x03 + + if color_code == 0: + r, g, b = r0, g0, b0 + elif color_code == 1: + r, g, b = r1, g1, b1 + elif color_code == 2: + r = (2 * r0 + r1) // 3 + g = (2 * g0 + g1) // 3 + b = (2 * b0 + b1) // 3 + elif color_code == 3: + r = (2 * r1 + r0) // 3 + g = (2 * g1 + g0) // 3 + b = (2 * b1 + b0) // 3 + + ret[j].extend([r, g, b, a]) + + return ret + + +class BLPFormatError(NotImplementedError): + pass + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith((b"BLP1", b"BLP2")) + + +class BlpImageFile(ImageFile.ImageFile): + """ + Blizzard Mipmap Format + """ + + format = "BLP" + format_description = "Blizzard Mipmap Format" + + def _open(self) -> None: + assert self.fp is not None + self.magic = self.fp.read(4) + if not _accept(self.magic): + msg = f"Bad BLP magic {repr(self.magic)}" + raise BLPFormatError(msg) + + compression = struct.unpack(" tuple[int, int]: + try: + self._read_header() + self._load() + except struct.error as e: + msg = "Truncated BLP file" + raise OSError(msg) from e + return -1, 0 + + @abc.abstractmethod + def _load(self) -> None: + pass + + def _read_header(self) -> None: + self._offsets = struct.unpack("<16I", self._safe_read(16 * 4)) + self._lengths = struct.unpack("<16I", self._safe_read(16 * 4)) + + def _safe_read(self, length: int) -> bytes: + assert self.fd is not None + return ImageFile._safe_read(self.fd, length) + + def _read_palette(self) -> list[tuple[int, int, int, int]]: + ret = [] + for i in range(256): + try: + b, g, r, a = struct.unpack("<4B", self._safe_read(4)) + except struct.error: + break + ret.append((b, g, r, a)) + return ret + + def _read_bgra( + self, palette: list[tuple[int, int, int, int]], alpha: bool + ) -> bytearray: + data = bytearray() + _data = BytesIO(self._safe_read(self._lengths[0])) + while True: + try: + (offset,) = struct.unpack(" None: + self._compression, self._encoding, alpha = self.args + + if self._compression == Format.JPEG: + self._decode_jpeg_stream() + + elif self._compression == 1: + if self._encoding in (4, 5): + palette = self._read_palette() + data = self._read_bgra(palette, alpha) + self.set_as_raw(data) + else: + msg = f"Unsupported BLP encoding {repr(self._encoding)}" + raise BLPFormatError(msg) + else: + msg = f"Unsupported BLP compression {repr(self._encoding)}" + raise BLPFormatError(msg) + + def _decode_jpeg_stream(self) -> None: + from .JpegImagePlugin import JpegImageFile + + (jpeg_header_size,) = struct.unpack(" None: + self._compression, self._encoding, alpha, self._alpha_encoding = self.args + + palette = self._read_palette() + + assert self.fd is not None + self.fd.seek(self._offsets[0]) + + if self._compression == 1: + # Uncompressed or DirectX compression + + if self._encoding == Encoding.UNCOMPRESSED: + data = self._read_bgra(palette, alpha) + + elif self._encoding == Encoding.DXT: + data = bytearray() + if self._alpha_encoding == AlphaEncoding.DXT1: + linesize = (self.state.xsize + 3) // 4 * 8 + for yb in range((self.state.ysize + 3) // 4): + for d in decode_dxt1(self._safe_read(linesize), alpha): + data += d + + elif self._alpha_encoding == AlphaEncoding.DXT3: + linesize = (self.state.xsize + 3) // 4 * 16 + for yb in range((self.state.ysize + 3) // 4): + for d in decode_dxt3(self._safe_read(linesize)): + data += d + + elif self._alpha_encoding == AlphaEncoding.DXT5: + linesize = (self.state.xsize + 3) // 4 * 16 + for yb in range((self.state.ysize + 3) // 4): + for d in decode_dxt5(self._safe_read(linesize)): + data += d + else: + msg = f"Unsupported alpha encoding {repr(self._alpha_encoding)}" + raise BLPFormatError(msg) + else: + msg = f"Unknown BLP encoding {repr(self._encoding)}" + raise BLPFormatError(msg) + + else: + msg = f"Unknown BLP compression {repr(self._compression)}" + raise BLPFormatError(msg) + + self.set_as_raw(data) + + +class BLPEncoder(ImageFile.PyEncoder): + _pushes_fd = True + + def _write_palette(self) -> bytes: + data = b"" + assert self.im is not None + palette = self.im.getpalette("RGBA", "RGBA") + for i in range(len(palette) // 4): + r, g, b, a = palette[i * 4 : (i + 1) * 4] + data += struct.pack("<4B", b, g, r, a) + while len(data) < 256 * 4: + data += b"\x00" * 4 + return data + + def encode(self, bufsize: int) -> tuple[int, int, bytes]: + palette_data = self._write_palette() + + offset = 20 + 16 * 4 * 2 + len(palette_data) + data = struct.pack("<16I", offset, *((0,) * 15)) + + assert self.im is not None + w, h = self.im.size + data += struct.pack("<16I", w * h, *((0,) * 15)) + + data += palette_data + + for y in range(h): + for x in range(w): + data += struct.pack(" None: + if im.mode != "P": + msg = "Unsupported BLP image mode" + raise ValueError(msg) + + magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2" + fp.write(magic) + + assert im.palette is not None + fp.write(struct.pack(" mode, rawmode + 1: ("P", "P;1"), + 4: ("P", "P;4"), + 8: ("P", "P"), + 16: ("RGB", "BGR;15"), + 24: ("RGB", "BGR"), + 32: ("RGB", "BGRX"), +} + +USE_RAW_ALPHA = False + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"BM") + + +def _dib_accept(prefix: bytes) -> bool: + return i32(prefix) in [12, 40, 52, 56, 64, 108, 124] + + +# ============================================================================= +# Image plugin for the Windows BMP format. +# ============================================================================= +class BmpImageFile(ImageFile.ImageFile): + """Image plugin for the Windows Bitmap format (BMP)""" + + # ------------------------------------------------------------- Description + format_description = "Windows Bitmap" + format = "BMP" + + # -------------------------------------------------- BMP Compression values + COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5} + for k, v in COMPRESSIONS.items(): + vars()[k] = v + + def _bitmap(self, header: int = 0, offset: int = 0) -> None: + """Read relevant info about the BMP""" + assert self.fp is not None + read, seek = self.fp.read, self.fp.seek + if header: + seek(header) + # read bmp header size @offset 14 (this is part of the header size) + file_info: dict[str, bool | int | tuple[int, ...]] = { + "header_size": i32(read(4)), + "direction": -1, + } + + # -------------------- If requested, read header at a specific position + # read the rest of the bmp header, without its size + assert isinstance(file_info["header_size"], int) + header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4) + + # ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1 + # ----- This format has different offsets because of width/height types + # 12: BITMAPCOREHEADER/OS21XBITMAPHEADER + if file_info["header_size"] == 12: + file_info["width"] = i16(header_data, 0) + file_info["height"] = i16(header_data, 2) + file_info["planes"] = i16(header_data, 4) + file_info["bits"] = i16(header_data, 6) + file_info["compression"] = self.COMPRESSIONS["RAW"] + file_info["palette_padding"] = 3 + + # --------------------------------------------- Windows Bitmap v3 to v5 + # 40: BITMAPINFOHEADER + # 52: BITMAPV2HEADER + # 56: BITMAPV3HEADER + # 64: BITMAPCOREHEADER2/OS22XBITMAPHEADER + # 108: BITMAPV4HEADER + # 124: BITMAPV5HEADER + elif file_info["header_size"] in (40, 52, 56, 64, 108, 124): + file_info["y_flip"] = header_data[7] == 0xFF + file_info["direction"] = 1 if file_info["y_flip"] else -1 + file_info["width"] = i32(header_data, 0) + file_info["height"] = ( + i32(header_data, 4) + if not file_info["y_flip"] + else 2**32 - i32(header_data, 4) + ) + file_info["planes"] = i16(header_data, 8) + file_info["bits"] = i16(header_data, 10) + file_info["compression"] = i32(header_data, 12) + # byte size of pixel data + file_info["data_size"] = i32(header_data, 16) + file_info["pixels_per_meter"] = ( + i32(header_data, 20), + i32(header_data, 24), + ) + file_info["colors"] = i32(header_data, 28) + file_info["palette_padding"] = 4 + assert isinstance(file_info["pixels_per_meter"], tuple) + self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"]) + if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]: + masks = ["r_mask", "g_mask", "b_mask"] + if len(header_data) >= 48: + if len(header_data) >= 52: + masks.append("a_mask") + else: + file_info["a_mask"] = 0x0 + for idx, mask in enumerate(masks): + file_info[mask] = i32(header_data, 36 + idx * 4) + else: + # 40 byte headers only have the three components in the + # bitfields masks, ref: + # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx + # See also + # https://github.com/python-pillow/Pillow/issues/1293 + # There is a 4th component in the RGBQuad, in the alpha + # location, but it is listed as a reserved component, + # and it is not generally an alpha channel + file_info["a_mask"] = 0x0 + for mask in masks: + file_info[mask] = i32(read(4)) + assert isinstance(file_info["r_mask"], int) + assert isinstance(file_info["g_mask"], int) + assert isinstance(file_info["b_mask"], int) + assert isinstance(file_info["a_mask"], int) + file_info["rgb_mask"] = ( + file_info["r_mask"], + file_info["g_mask"], + file_info["b_mask"], + ) + file_info["rgba_mask"] = ( + file_info["r_mask"], + file_info["g_mask"], + file_info["b_mask"], + file_info["a_mask"], + ) + else: + msg = f"Unsupported BMP header type ({file_info['header_size']})" + raise OSError(msg) + + # ------------------ Special case : header is reported 40, which + # ---------------------- is shorter than real size for bpp >= 16 + assert isinstance(file_info["width"], int) + assert isinstance(file_info["height"], int) + self._size = file_info["width"], file_info["height"] + + # ------- If color count was not found in the header, compute from bits + assert isinstance(file_info["bits"], int) + file_info["colors"] = ( + file_info["colors"] + if file_info.get("colors", 0) + else (1 << file_info["bits"]) + ) + assert isinstance(file_info["colors"], int) + if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8: + offset += 4 * file_info["colors"] + + # ---------------------- Check bit depth for unusual unsupported values + self._mode, raw_mode = BIT2MODE.get(file_info["bits"], ("", "")) + if not self.mode: + msg = f"Unsupported BMP pixel depth ({file_info['bits']})" + raise OSError(msg) + + # ---------------- Process BMP with Bitfields compression (not palette) + decoder_name = "raw" + if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]: + SUPPORTED: dict[int, list[tuple[int, ...]]] = { + 32: [ + (0xFF0000, 0xFF00, 0xFF, 0x0), + (0xFF000000, 0xFF0000, 0xFF00, 0x0), + (0xFF000000, 0xFF00, 0xFF, 0x0), + (0xFF000000, 0xFF0000, 0xFF00, 0xFF), + (0xFF, 0xFF00, 0xFF0000, 0xFF000000), + (0xFF0000, 0xFF00, 0xFF, 0xFF000000), + (0xFF000000, 0xFF00, 0xFF, 0xFF0000), + (0x0, 0x0, 0x0, 0x0), + ], + 24: [(0xFF0000, 0xFF00, 0xFF)], + 16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)], + } + MASK_MODES = { + (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX", + (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR", + (32, (0xFF000000, 0xFF00, 0xFF, 0x0)): "BGXR", + (32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR", + (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA", + (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA", + (32, (0xFF000000, 0xFF00, 0xFF, 0xFF0000)): "BGAR", + (32, (0x0, 0x0, 0x0, 0x0)): "BGRA", + (24, (0xFF0000, 0xFF00, 0xFF)): "BGR", + (16, (0xF800, 0x7E0, 0x1F)): "BGR;16", + (16, (0x7C00, 0x3E0, 0x1F)): "BGR;15", + } + if file_info["bits"] in SUPPORTED: + if ( + file_info["bits"] == 32 + and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]] + ): + assert isinstance(file_info["rgba_mask"], tuple) + raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])] + self._mode = "RGBA" if "A" in raw_mode else self.mode + elif ( + file_info["bits"] in (24, 16) + and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]] + ): + assert isinstance(file_info["rgb_mask"], tuple) + raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])] + else: + msg = "Unsupported BMP bitfields layout" + raise OSError(msg) + else: + msg = "Unsupported BMP bitfields layout" + raise OSError(msg) + elif file_info["compression"] == self.COMPRESSIONS["RAW"]: + if file_info["bits"] == 32 and ( + header == 22 or USE_RAW_ALPHA # 32-bit .cur offset + ): + raw_mode, self._mode = "BGRA", "RGBA" + elif file_info["compression"] in ( + self.COMPRESSIONS["RLE8"], + self.COMPRESSIONS["RLE4"], + ): + decoder_name = "bmp_rle" + else: + msg = f"Unsupported BMP compression ({file_info['compression']})" + raise OSError(msg) + + # --------------- Once the header is processed, process the palette/LUT + if self.mode == "P": # Paletted for 1, 4 and 8 bit images + # ---------------------------------------------------- 1-bit images + if not (0 < file_info["colors"] <= 65536): + msg = f"Unsupported BMP Palette size ({file_info['colors']})" + raise OSError(msg) + else: + assert isinstance(file_info["palette_padding"], int) + padding = file_info["palette_padding"] + palette = read(padding * file_info["colors"]) + grayscale = True + indices = ( + (0, 255) + if file_info["colors"] == 2 + else list(range(file_info["colors"])) + ) + + # ----------------- Check if grayscale and ignore palette if so + for ind, val in enumerate(indices): + rgb = palette[ind * padding : ind * padding + 3] + if rgb != o8(val) * 3: + grayscale = False + + # ------- If all colors are gray, white or black, ditch palette + if grayscale: + self._mode = "1" if file_info["colors"] == 2 else "L" + raw_mode = self.mode + else: + self._mode = "P" + self.palette = ImagePalette.raw( + "BGRX" if padding == 4 else "BGR", palette + ) + + # ---------------------------- Finally set the tile data for the plugin + self.info["compression"] = file_info["compression"] + args: list[Any] = [raw_mode] + if decoder_name == "bmp_rle": + args.append(file_info["compression"] == self.COMPRESSIONS["RLE4"]) + else: + assert isinstance(file_info["width"], int) + args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3)) + args.append(file_info["direction"]) + self.tile = [ + ImageFile._Tile( + decoder_name, + (0, 0, file_info["width"], file_info["height"]), + offset or self.fp.tell(), + tuple(args), + ) + ] + + def _open(self) -> None: + """Open file, check magic number and read header""" + # read 14 bytes: magic number, filesize, reserved, header final offset + assert self.fp is not None + head_data = self.fp.read(14) + # choke if the file does not have the required magic bytes + if not _accept(head_data): + msg = "Not a BMP file" + raise SyntaxError(msg) + # read the start position of the BMP image data (u32) + offset = i32(head_data, 10) + # load bitmap information (offset=raster info) + self._bitmap(offset=offset) + + +class BmpRleDecoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + assert self.fd is not None + rle4 = self.args[1] + data = bytearray() + x = 0 + dest_length = self.state.xsize * self.state.ysize + while len(data) < dest_length: + pixels = self.fd.read(1) + byte = self.fd.read(1) + if not pixels or not byte: + break + num_pixels = pixels[0] + if num_pixels: + # encoded mode + if x + num_pixels > self.state.xsize: + # Too much data for row + num_pixels = max(0, self.state.xsize - x) + if rle4: + first_pixel = o8(byte[0] >> 4) + second_pixel = o8(byte[0] & 0x0F) + for index in range(num_pixels): + if index % 2 == 0: + data += first_pixel + else: + data += second_pixel + else: + data += byte * num_pixels + x += num_pixels + else: + if byte[0] == 0: + # end of line + while len(data) % self.state.xsize != 0: + data += b"\x00" + x = 0 + elif byte[0] == 1: + # end of bitmap + break + elif byte[0] == 2: + # delta + bytes_read = self.fd.read(2) + if len(bytes_read) < 2: + break + right, up = self.fd.read(2) + data += b"\x00" * (right + up * self.state.xsize) + x = len(data) % self.state.xsize + else: + # absolute mode + if rle4: + # 2 pixels per byte + byte_count = byte[0] // 2 + bytes_read = self.fd.read(byte_count) + for byte_read in bytes_read: + data += o8(byte_read >> 4) + data += o8(byte_read & 0x0F) + else: + byte_count = byte[0] + bytes_read = self.fd.read(byte_count) + data += bytes_read + if len(bytes_read) < byte_count: + break + x += byte[0] + + # align to 16-bit word boundary + if self.fd.tell() % 2 != 0: + self.fd.seek(1, os.SEEK_CUR) + rawmode = "L" if self.mode == "L" else "P" + self.set_as_raw(bytes(data), rawmode, (0, self.args[-1])) + return -1, 0 + + +# ============================================================================= +# Image plugin for the DIB format (BMP alias) +# ============================================================================= +class DibImageFile(BmpImageFile): + format = "DIB" + format_description = "Windows Bitmap" + + def _open(self) -> None: + self._bitmap() + + +# +# -------------------------------------------------------------------- +# Write BMP file + + +SAVE = { + "1": ("1", 1, 2), + "L": ("L", 8, 256), + "P": ("P", 8, 256), + "RGB": ("BGR", 24, 0), + "RGBA": ("BGRA", 32, 0), +} + + +def _dib_save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + _save(im, fp, filename, False) + + +def _save( + im: Image.Image, fp: IO[bytes], filename: str | bytes, bitmap_header: bool = True +) -> None: + try: + rawmode, bits, colors = SAVE[im.mode] + except KeyError as e: + msg = f"cannot write mode {im.mode} as BMP" + raise OSError(msg) from e + + info = im.encoderinfo + + dpi = info.get("dpi", (96, 96)) + + # 1 meter == 39.3701 inches + ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi) + + stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3) + header = 40 # or 64 for OS/2 version 2 + image = stride * im.size[1] + + if im.mode == "1": + palette = b"".join(o8(i) * 3 + b"\x00" for i in (0, 255)) + elif im.mode == "L": + palette = b"".join(o8(i) * 3 + b"\x00" for i in range(256)) + elif im.mode == "P": + palette = im.im.getpalette("RGB", "BGRX") + colors = len(palette) // 4 + else: + palette = None + + # bitmap header + if bitmap_header: + offset = 14 + header + colors * 4 + file_size = offset + image + if file_size > 2**32 - 1: + msg = "File size is too large for the BMP format" + raise ValueError(msg) + fp.write( + b"BM" # file type (magic) + + o32(file_size) # file size + + o32(0) # reserved + + o32(offset) # image data offset + ) + + # bitmap info header + fp.write( + o32(header) # info header size + + o32(im.size[0]) # width + + o32(im.size[1]) # height + + o16(1) # planes + + o16(bits) # depth + + o32(0) # compression (0=uncompressed) + + o32(image) # size of bitmap + + o32(ppm[0]) # resolution + + o32(ppm[1]) # resolution + + o32(colors) # colors used + + o32(colors) # colors important + ) + + fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) + + if palette: + fp.write(palette) + + ImageFile._save( + im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))] + ) + + +# +# -------------------------------------------------------------------- +# Registry + + +Image.register_open(BmpImageFile.format, BmpImageFile, _accept) +Image.register_save(BmpImageFile.format, _save) + +Image.register_extension(BmpImageFile.format, ".bmp") + +Image.register_mime(BmpImageFile.format, "image/bmp") + +Image.register_decoder("bmp_rle", BmpRleDecoder) + +Image.register_open(DibImageFile.format, DibImageFile, _dib_accept) +Image.register_save(DibImageFile.format, _dib_save) + +Image.register_extension(DibImageFile.format, ".dib") + +Image.register_mime(DibImageFile.format, "image/bmp") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/BufrStubImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/BufrStubImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..264564d2bbbcdafa369d938ddda715a216ce8ab7 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/BufrStubImagePlugin.py @@ -0,0 +1,76 @@ +# +# The Python Imaging Library +# $Id$ +# +# BUFR stub adapter +# +# Copyright (c) 1996-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +from typing import IO + +from . import Image, ImageFile + +_handler = None + + +def register_handler(handler: ImageFile.StubHandler | None) -> None: + """ + Install application-specific BUFR image handler. + + :param handler: Handler object. + """ + global _handler + _handler = handler + + +# -------------------------------------------------------------------- +# Image adapter + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith((b"BUFR", b"ZCZC")) + + +class BufrStubImageFile(ImageFile.StubImageFile): + format = "BUFR" + format_description = "BUFR" + + def _open(self) -> None: + assert self.fp is not None + if not _accept(self.fp.read(4)): + msg = "Not a BUFR file" + raise SyntaxError(msg) + + self.fp.seek(-4, os.SEEK_CUR) + + # make something up + self._mode = "F" + self._size = 1, 1 + + loader = self._load() + if loader: + loader.open(self) + + def _load(self) -> ImageFile.StubHandler | None: + return _handler + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if _handler is None or not hasattr(_handler, "save"): + msg = "BUFR save handler not installed" + raise OSError(msg) + _handler.save(im, fp, filename) + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(BufrStubImageFile.format, BufrStubImageFile, _accept) +Image.register_save(BufrStubImageFile.format, _save) + +Image.register_extension(BufrStubImageFile.format, ".bufr") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ContainerIO.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ContainerIO.py new file mode 100644 index 0000000000000000000000000000000000000000..ec9e66c714fbbfec8c597f6127e5d932b0da521f --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ContainerIO.py @@ -0,0 +1,173 @@ +# +# The Python Imaging Library. +# $Id$ +# +# a class to read from a container file +# +# History: +# 1995-06-18 fl Created +# 1995-09-07 fl Added readline(), readlines() +# +# Copyright (c) 1997-2001 by Secret Labs AB +# Copyright (c) 1995 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +from collections.abc import Iterable +from typing import IO, AnyStr, NoReturn + + +class ContainerIO(IO[AnyStr]): + """ + A file object that provides read access to a part of an existing + file (for example a TAR file). + """ + + def __init__(self, file: IO[AnyStr], offset: int, length: int) -> None: + """ + Create file object. + + :param file: Existing file. + :param offset: Start of region, in bytes. + :param length: Size of region, in bytes. + """ + self.fh: IO[AnyStr] = file + self.pos = 0 + self.offset = offset + self.length = length + self.fh.seek(offset) + + ## + # Always false. + + def isatty(self) -> bool: + return False + + def seekable(self) -> bool: + return True + + def seek(self, offset: int, mode: int = io.SEEK_SET) -> int: + """ + Move file pointer. + + :param offset: Offset in bytes. + :param mode: Starting position. Use 0 for beginning of region, 1 + for current offset, and 2 for end of region. You cannot move + the pointer outside the defined region. + :returns: Offset from start of region, in bytes. + """ + if mode == 1: + self.pos = self.pos + offset + elif mode == 2: + self.pos = self.length + offset + else: + self.pos = offset + # clamp + self.pos = max(0, min(self.pos, self.length)) + self.fh.seek(self.offset + self.pos) + return self.pos + + def tell(self) -> int: + """ + Get current file pointer. + + :returns: Offset from start of region, in bytes. + """ + return self.pos + + def readable(self) -> bool: + return True + + def read(self, n: int = -1) -> AnyStr: + """ + Read data. + + :param n: Number of bytes to read. If omitted, zero or negative, + read until end of region. + :returns: An 8-bit string. + """ + if n > 0: + n = min(n, self.length - self.pos) + else: + n = self.length - self.pos + if n <= 0: # EOF + return b"" if "b" in self.fh.mode else "" # type: ignore[return-value] + self.pos = self.pos + n + return self.fh.read(n) + + def readline(self, n: int = -1) -> AnyStr: + """ + Read a line of text. + + :param n: Number of bytes to read. If omitted, zero or negative, + read until end of line. + :returns: An 8-bit string. + """ + s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment] + newline_character = b"\n" if "b" in self.fh.mode else "\n" + while True: + c = self.read(1) + if not c: + break + s = s + c + if c == newline_character or len(s) == n: + break + return s + + def readlines(self, n: int | None = -1) -> list[AnyStr]: + """ + Read multiple lines of text. + + :param n: Number of lines to read. If omitted, zero, negative or None, + read until end of region. + :returns: A list of 8-bit strings. + """ + lines = [] + while True: + s = self.readline() + if not s: + break + lines.append(s) + if len(lines) == n: + break + return lines + + def writable(self) -> bool: + return False + + def write(self, b: AnyStr) -> NoReturn: + raise NotImplementedError() + + def writelines(self, lines: Iterable[AnyStr]) -> NoReturn: + raise NotImplementedError() + + def truncate(self, size: int | None = None) -> int: + raise NotImplementedError() + + def __enter__(self) -> ContainerIO[AnyStr]: + return self + + def __exit__(self, *args: object) -> None: + self.close() + + def __iter__(self) -> ContainerIO[AnyStr]: + return self + + def __next__(self) -> AnyStr: + line = self.readline() + if not line: + msg = "end of region" + raise StopIteration(msg) + return line + + def fileno(self) -> int: + return self.fh.fileno() + + def flush(self) -> None: + self.fh.flush() + + def close(self) -> None: + self.fh.close() diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/CurImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/CurImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..9c188e084463dd0607934196ff9ad0dd1e51b210 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/CurImagePlugin.py @@ -0,0 +1,75 @@ +# +# The Python Imaging Library. +# $Id$ +# +# Windows Cursor support for PIL +# +# notes: +# uses BmpImagePlugin.py to read the bitmap data. +# +# history: +# 96-05-27 fl Created +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1996. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import BmpImagePlugin, Image +from ._binary import i16le as i16 +from ._binary import i32le as i32 + +# +# -------------------------------------------------------------------- + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"\0\0\2\0") + + +## +# Image plugin for Windows Cursor files. + + +class CurImageFile(BmpImagePlugin.BmpImageFile): + format = "CUR" + format_description = "Windows Cursor" + + def _open(self) -> None: + assert self.fp is not None + offset = self.fp.tell() + + # check magic + s = self.fp.read(6) + if not _accept(s): + msg = "not a CUR file" + raise SyntaxError(msg) + + # pick the largest cursor in the file + m = b"" + for i in range(i16(s, 4)): + s = self.fp.read(16) + if not m: + m = s + elif s[0] > m[0] and s[1] > m[1]: + m = s + if not m: + msg = "No cursors were found" + raise TypeError(msg) + + # load as bitmap + self._bitmap(i32(m, 12) + offset) + + # patch up the bitmap height + self._size = self.size[0], self.size[1] // 2 + self.tile = [self.tile[0]._replace(extents=(0, 0) + self.size)] + + +# +# -------------------------------------------------------------------- + +Image.register_open(CurImageFile.format, CurImageFile, _accept) + +Image.register_extension(CurImageFile.format, ".cur") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/DcxImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/DcxImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..d3f456ddcc4aabac81d53bfb3972b398bb2f86b1 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/DcxImagePlugin.py @@ -0,0 +1,84 @@ +# +# The Python Imaging Library. +# $Id$ +# +# DCX file handling +# +# DCX is a container file format defined by Intel, commonly used +# for fax applications. Each DCX file consists of a directory +# (a list of file offsets) followed by a set of (usually 1-bit) +# PCX files. +# +# History: +# 1995-09-09 fl Created +# 1996-03-20 fl Properly derived from PcxImageFile. +# 1998-07-15 fl Renamed offset attribute to avoid name clash +# 2002-07-30 fl Fixed file handling +# +# Copyright (c) 1997-98 by Secret Labs AB. +# Copyright (c) 1995-96 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import Image +from ._binary import i32le as i32 +from ._util import DeferredError +from .PcxImagePlugin import PcxImageFile + +MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then? + + +def _accept(prefix: bytes) -> bool: + return len(prefix) >= 4 and i32(prefix) == MAGIC + + +## +# Image plugin for the Intel DCX format. + + +class DcxImageFile(PcxImageFile): + format = "DCX" + format_description = "Intel DCX" + _close_exclusive_fp_after_loading = False + + def _open(self) -> None: + # Header + assert self.fp is not None + s = self.fp.read(4) + if not _accept(s): + msg = "not a DCX file" + raise SyntaxError(msg) + + # Component directory + self._offset = [] + for i in range(1024): + offset = i32(self.fp.read(4)) + if not offset: + break + self._offset.append(offset) + + self._fp = self.fp + self.frame = -1 + self.n_frames = len(self._offset) + self.is_animated = self.n_frames > 1 + self.seek(0) + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + if isinstance(self._fp, DeferredError): + raise self._fp.ex + self.frame = frame + self.fp = self._fp + self.fp.seek(self._offset[frame]) + PcxImageFile._open(self) + + def tell(self) -> int: + return self.frame + + +Image.register_open(DcxImageFile.format, DcxImageFile, _accept) + +Image.register_extension(DcxImageFile.format, ".dcx") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/DdsImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/DdsImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..312f602a6b18edfcd0826e44be7ef668b5e28804 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/DdsImagePlugin.py @@ -0,0 +1,625 @@ +""" +A Pillow plugin for .dds files (S3TC-compressed aka DXTC) +Jerome Leclanche + +Documentation: +https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt + +The contents of this file are hereby released in the public domain (CC0) +Full text of the CC0 license: +https://creativecommons.org/publicdomain/zero/1.0/ +""" + +from __future__ import annotations + +import struct +import sys +from enum import IntEnum, IntFlag +from typing import IO + +from . import Image, ImageFile, ImagePalette +from ._binary import i32le as i32 +from ._binary import o8 +from ._binary import o32le as o32 + +# Magic ("DDS ") +DDS_MAGIC = 0x20534444 + + +# DDS flags +class DDSD(IntFlag): + CAPS = 0x1 + HEIGHT = 0x2 + WIDTH = 0x4 + PITCH = 0x8 + PIXELFORMAT = 0x1000 + MIPMAPCOUNT = 0x20000 + LINEARSIZE = 0x80000 + DEPTH = 0x800000 + + +# DDS caps +class DDSCAPS(IntFlag): + COMPLEX = 0x8 + TEXTURE = 0x1000 + MIPMAP = 0x400000 + + +class DDSCAPS2(IntFlag): + CUBEMAP = 0x200 + CUBEMAP_POSITIVEX = 0x400 + CUBEMAP_NEGATIVEX = 0x800 + CUBEMAP_POSITIVEY = 0x1000 + CUBEMAP_NEGATIVEY = 0x2000 + CUBEMAP_POSITIVEZ = 0x4000 + CUBEMAP_NEGATIVEZ = 0x8000 + VOLUME = 0x200000 + + +# Pixel Format +class DDPF(IntFlag): + ALPHAPIXELS = 0x1 + ALPHA = 0x2 + FOURCC = 0x4 + PALETTEINDEXED8 = 0x20 + RGB = 0x40 + LUMINANCE = 0x20000 + + +# dxgiformat.h +class DXGI_FORMAT(IntEnum): + UNKNOWN = 0 + R32G32B32A32_TYPELESS = 1 + R32G32B32A32_FLOAT = 2 + R32G32B32A32_UINT = 3 + R32G32B32A32_SINT = 4 + R32G32B32_TYPELESS = 5 + R32G32B32_FLOAT = 6 + R32G32B32_UINT = 7 + R32G32B32_SINT = 8 + R16G16B16A16_TYPELESS = 9 + R16G16B16A16_FLOAT = 10 + R16G16B16A16_UNORM = 11 + R16G16B16A16_UINT = 12 + R16G16B16A16_SNORM = 13 + R16G16B16A16_SINT = 14 + R32G32_TYPELESS = 15 + R32G32_FLOAT = 16 + R32G32_UINT = 17 + R32G32_SINT = 18 + R32G8X24_TYPELESS = 19 + D32_FLOAT_S8X24_UINT = 20 + R32_FLOAT_X8X24_TYPELESS = 21 + X32_TYPELESS_G8X24_UINT = 22 + R10G10B10A2_TYPELESS = 23 + R10G10B10A2_UNORM = 24 + R10G10B10A2_UINT = 25 + R11G11B10_FLOAT = 26 + R8G8B8A8_TYPELESS = 27 + R8G8B8A8_UNORM = 28 + R8G8B8A8_UNORM_SRGB = 29 + R8G8B8A8_UINT = 30 + R8G8B8A8_SNORM = 31 + R8G8B8A8_SINT = 32 + R16G16_TYPELESS = 33 + R16G16_FLOAT = 34 + R16G16_UNORM = 35 + R16G16_UINT = 36 + R16G16_SNORM = 37 + R16G16_SINT = 38 + R32_TYPELESS = 39 + D32_FLOAT = 40 + R32_FLOAT = 41 + R32_UINT = 42 + R32_SINT = 43 + R24G8_TYPELESS = 44 + D24_UNORM_S8_UINT = 45 + R24_UNORM_X8_TYPELESS = 46 + X24_TYPELESS_G8_UINT = 47 + R8G8_TYPELESS = 48 + R8G8_UNORM = 49 + R8G8_UINT = 50 + R8G8_SNORM = 51 + R8G8_SINT = 52 + R16_TYPELESS = 53 + R16_FLOAT = 54 + D16_UNORM = 55 + R16_UNORM = 56 + R16_UINT = 57 + R16_SNORM = 58 + R16_SINT = 59 + R8_TYPELESS = 60 + R8_UNORM = 61 + R8_UINT = 62 + R8_SNORM = 63 + R8_SINT = 64 + A8_UNORM = 65 + R1_UNORM = 66 + R9G9B9E5_SHAREDEXP = 67 + R8G8_B8G8_UNORM = 68 + G8R8_G8B8_UNORM = 69 + BC1_TYPELESS = 70 + BC1_UNORM = 71 + BC1_UNORM_SRGB = 72 + BC2_TYPELESS = 73 + BC2_UNORM = 74 + BC2_UNORM_SRGB = 75 + BC3_TYPELESS = 76 + BC3_UNORM = 77 + BC3_UNORM_SRGB = 78 + BC4_TYPELESS = 79 + BC4_UNORM = 80 + BC4_SNORM = 81 + BC5_TYPELESS = 82 + BC5_UNORM = 83 + BC5_SNORM = 84 + B5G6R5_UNORM = 85 + B5G5R5A1_UNORM = 86 + B8G8R8A8_UNORM = 87 + B8G8R8X8_UNORM = 88 + R10G10B10_XR_BIAS_A2_UNORM = 89 + B8G8R8A8_TYPELESS = 90 + B8G8R8A8_UNORM_SRGB = 91 + B8G8R8X8_TYPELESS = 92 + B8G8R8X8_UNORM_SRGB = 93 + BC6H_TYPELESS = 94 + BC6H_UF16 = 95 + BC6H_SF16 = 96 + BC7_TYPELESS = 97 + BC7_UNORM = 98 + BC7_UNORM_SRGB = 99 + AYUV = 100 + Y410 = 101 + Y416 = 102 + NV12 = 103 + P010 = 104 + P016 = 105 + OPAQUE_420 = 106 + YUY2 = 107 + Y210 = 108 + Y216 = 109 + NV11 = 110 + AI44 = 111 + IA44 = 112 + P8 = 113 + A8P8 = 114 + B4G4R4A4_UNORM = 115 + P208 = 130 + V208 = 131 + V408 = 132 + SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189 + SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190 + + +class D3DFMT(IntEnum): + UNKNOWN = 0 + R8G8B8 = 20 + A8R8G8B8 = 21 + X8R8G8B8 = 22 + R5G6B5 = 23 + X1R5G5B5 = 24 + A1R5G5B5 = 25 + A4R4G4B4 = 26 + R3G3B2 = 27 + A8 = 28 + A8R3G3B2 = 29 + X4R4G4B4 = 30 + A2B10G10R10 = 31 + A8B8G8R8 = 32 + X8B8G8R8 = 33 + G16R16 = 34 + A2R10G10B10 = 35 + A16B16G16R16 = 36 + A8P8 = 40 + P8 = 41 + L8 = 50 + A8L8 = 51 + A4L4 = 52 + V8U8 = 60 + L6V5U5 = 61 + X8L8V8U8 = 62 + Q8W8V8U8 = 63 + V16U16 = 64 + A2W10V10U10 = 67 + D16_LOCKABLE = 70 + D32 = 71 + D15S1 = 73 + D24S8 = 75 + D24X8 = 77 + D24X4S4 = 79 + D16 = 80 + D32F_LOCKABLE = 82 + D24FS8 = 83 + D32_LOCKABLE = 84 + S8_LOCKABLE = 85 + L16 = 81 + VERTEXDATA = 100 + INDEX16 = 101 + INDEX32 = 102 + Q16W16V16U16 = 110 + R16F = 111 + G16R16F = 112 + A16B16G16R16F = 113 + R32F = 114 + G32R32F = 115 + A32B32G32R32F = 116 + CxV8U8 = 117 + A1 = 118 + A2B10G10R10_XR_BIAS = 119 + BINARYBUFFER = 199 + + UYVY = i32(b"UYVY") + R8G8_B8G8 = i32(b"RGBG") + YUY2 = i32(b"YUY2") + G8R8_G8B8 = i32(b"GRGB") + DXT1 = i32(b"DXT1") + DXT2 = i32(b"DXT2") + DXT3 = i32(b"DXT3") + DXT4 = i32(b"DXT4") + DXT5 = i32(b"DXT5") + DX10 = i32(b"DX10") + BC4S = i32(b"BC4S") + BC4U = i32(b"BC4U") + BC5S = i32(b"BC5S") + BC5U = i32(b"BC5U") + ATI1 = i32(b"ATI1") + ATI2 = i32(b"ATI2") + MULTI2_ARGB8 = i32(b"MET1") + + +# Backward compatibility layer +module = sys.modules[__name__] +for item in DDSD: + assert item.name is not None + setattr(module, f"DDSD_{item.name}", item.value) +for item1 in DDSCAPS: + assert item1.name is not None + setattr(module, f"DDSCAPS_{item1.name}", item1.value) +for item2 in DDSCAPS2: + assert item2.name is not None + setattr(module, f"DDSCAPS2_{item2.name}", item2.value) +for item3 in DDPF: + assert item3.name is not None + setattr(module, f"DDPF_{item3.name}", item3.value) + +DDS_FOURCC = DDPF.FOURCC +DDS_RGB = DDPF.RGB +DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS +DDS_LUMINANCE = DDPF.LUMINANCE +DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS +DDS_ALPHA = DDPF.ALPHA +DDS_PAL8 = DDPF.PALETTEINDEXED8 + +DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT +DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT +DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH +DDS_HEADER_FLAGS_PITCH = DDSD.PITCH +DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE + +DDS_HEIGHT = DDSD.HEIGHT +DDS_WIDTH = DDSD.WIDTH + +DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE +DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP +DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX + +DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX +DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX +DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY +DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY +DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ +DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ + +DXT1_FOURCC = D3DFMT.DXT1 +DXT3_FOURCC = D3DFMT.DXT3 +DXT5_FOURCC = D3DFMT.DXT5 + +DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS +DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM +DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB +DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS +DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM +DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM +DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16 +DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16 +DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS +DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM +DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB + + +class DdsImageFile(ImageFile.ImageFile): + format = "DDS" + format_description = "DirectDraw Surface" + + def _open(self) -> None: + assert self.fp is not None + if not _accept(self.fp.read(4)): + msg = "not a DDS file" + raise SyntaxError(msg) + (header_size,) = struct.unpack(" None: + pass + + +class DdsRgbDecoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + assert self.fd is not None + bitcount, masks = self.args + + # Some masks will be padded with zeros, e.g. R 0b11 G 0b1100 + # Calculate how many zeros each mask is padded with + mask_offsets = [] + # And the maximum value of each channel without the padding + mask_totals = [] + for mask in masks: + offset = 0 + if mask != 0: + while mask >> (offset + 1) << (offset + 1) == mask: + offset += 1 + mask_offsets.append(offset) + mask_totals.append(mask >> offset) + + data = bytearray() + bytecount = bitcount // 8 + dest_length = self.state.xsize * self.state.ysize * len(masks) + while len(data) < dest_length: + value = int.from_bytes(self.fd.read(bytecount), "little") + for i, mask in enumerate(masks): + masked_value = value & mask + # Remove the zero padding, and scale it to 8 bits + data += o8( + int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255) + if mask_totals[i] + else 0 + ) + self.set_as_raw(data) + return -1, 0 + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if im.mode not in ("RGB", "RGBA", "L", "LA"): + msg = f"cannot write mode {im.mode} as DDS" + raise OSError(msg) + + flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT + bitcount = len(im.getbands()) * 8 + pixel_format = im.encoderinfo.get("pixel_format") + args: tuple[int] | str + if pixel_format: + codec_name = "bcn" + flags |= DDSD.LINEARSIZE + pitch = (im.width + 3) * 4 + rgba_mask = [0, 0, 0, 0] + pixel_flags = DDPF.FOURCC + if pixel_format == "DXT1": + fourcc = D3DFMT.DXT1 + args = (1,) + elif pixel_format == "DXT3": + fourcc = D3DFMT.DXT3 + args = (2,) + elif pixel_format == "DXT5": + fourcc = D3DFMT.DXT5 + args = (3,) + else: + fourcc = D3DFMT.DX10 + if pixel_format == "BC2": + args = (2,) + dxgi_format = DXGI_FORMAT.BC2_TYPELESS + elif pixel_format == "BC3": + args = (3,) + dxgi_format = DXGI_FORMAT.BC3_TYPELESS + elif pixel_format == "BC5": + args = (5,) + dxgi_format = DXGI_FORMAT.BC5_TYPELESS + if im.mode != "RGB": + msg = "only RGB mode can be written as BC5" + raise OSError(msg) + else: + msg = f"cannot write pixel format {pixel_format}" + raise OSError(msg) + else: + codec_name = "raw" + flags |= DDSD.PITCH + pitch = (im.width * bitcount + 7) // 8 + + alpha = im.mode[-1] == "A" + if im.mode[0] == "L": + pixel_flags = DDPF.LUMINANCE + args = im.mode + if alpha: + rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF] + else: + rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000] + else: + pixel_flags = DDPF.RGB + args = im.mode[::-1] + rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF] + + if alpha: + r, g, b, a = im.split() + im = Image.merge("RGBA", (a, r, g, b)) + if alpha: + pixel_flags |= DDPF.ALPHAPIXELS + rgba_mask.append(0xFF000000 if alpha else 0) + + fourcc = D3DFMT.UNKNOWN + fp.write( + o32(DDS_MAGIC) + + struct.pack( + "<7I", + 124, # header size + flags, # flags + im.height, + im.width, + pitch, + 0, # depth + 0, # mipmaps + ) + + struct.pack("11I", *((0,) * 11)) # reserved + # pfsize, pfflags, fourcc, bitcount + + struct.pack("<4I", 32, pixel_flags, fourcc, bitcount) + + struct.pack("<4I", *rgba_mask) # dwRGBABitMask + + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) + ) + if fourcc == D3DFMT.DX10: + fp.write( + # dxgi_format, 2D resource, misc, array size, straight alpha + struct.pack("<5I", dxgi_format, 3, 0, 0, 1) + ) + ImageFile._save(im, fp, [ImageFile._Tile(codec_name, (0, 0) + im.size, 0, args)]) + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"DDS ") + + +Image.register_open(DdsImageFile.format, DdsImageFile, _accept) +Image.register_decoder("dds_rgb", DdsRgbDecoder) +Image.register_save(DdsImageFile.format, _save) +Image.register_extension(DdsImageFile.format, ".dds") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/EpsImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/EpsImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..2effb816cfb0ab4b26c08fa082cea4fadab607c7 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/EpsImagePlugin.py @@ -0,0 +1,481 @@ +# +# The Python Imaging Library. +# $Id$ +# +# EPS file handling +# +# History: +# 1995-09-01 fl Created (0.1) +# 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2) +# 1996-08-22 fl Don't choke on floating point BoundingBox values +# 1996-08-23 fl Handle files from Macintosh (0.3) +# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4) +# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5) +# 2014-05-07 e Handling of EPS with binary preview and fixed resolution +# resizing +# +# Copyright (c) 1997-2003 by Secret Labs AB. +# Copyright (c) 1995-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +import os +import re +import subprocess +import sys +import tempfile +from typing import IO + +from . import Image, ImageFile +from ._binary import i32le as i32 + +# -------------------------------------------------------------------- + + +split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$") +field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$") + +gs_binary: str | bool | None = None +gs_windows_binary = None + + +def has_ghostscript() -> bool: + global gs_binary, gs_windows_binary + if gs_binary is None: + if sys.platform.startswith("win"): + if gs_windows_binary is None: + import shutil + + for binary in ("gswin32c", "gswin64c", "gs"): + if shutil.which(binary) is not None: + gs_windows_binary = binary + break + else: + gs_windows_binary = False + gs_binary = gs_windows_binary + else: + try: + subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL) + gs_binary = "gs" + except OSError: + gs_binary = False + return gs_binary is not False + + +def Ghostscript( + tile: list[ImageFile._Tile], + size: tuple[int, int], + fp: IO[bytes], + scale: int = 1, + transparency: bool = False, +) -> Image.core.ImagingCore: + """Render an image using Ghostscript""" + global gs_binary + if not has_ghostscript(): + msg = "Unable to locate Ghostscript on paths" + raise OSError(msg) + assert isinstance(gs_binary, str) + + # Unpack decoder tile + args = tile[0].args + assert isinstance(args, tuple) + length, bbox = args + + # Hack to support hi-res rendering + scale = int(scale) or 1 + width = size[0] * scale + height = size[1] * scale + # resolution is dependent on bbox and size + res_x = 72.0 * width / (bbox[2] - bbox[0]) + res_y = 72.0 * height / (bbox[3] - bbox[1]) + + out_fd, outfile = tempfile.mkstemp() + os.close(out_fd) + + infile_temp = None + if hasattr(fp, "name") and os.path.exists(fp.name): + infile = fp.name + else: + in_fd, infile_temp = tempfile.mkstemp() + os.close(in_fd) + infile = infile_temp + + # Ignore length and offset! + # Ghostscript can read it + # Copy whole file to read in Ghostscript + with open(infile_temp, "wb") as f: + # fetch length of fp + fp.seek(0, io.SEEK_END) + fsize = fp.tell() + # ensure start position + # go back + fp.seek(0) + lengthfile = fsize + while lengthfile > 0: + s = fp.read(min(lengthfile, 100 * 1024)) + if not s: + break + lengthfile -= len(s) + f.write(s) + + if transparency: + # "RGBA" + device = "pngalpha" + else: + # "pnmraw" automatically chooses between + # PBM ("1"), PGM ("L"), and PPM ("RGB"). + device = "pnmraw" + + # Build Ghostscript command + command = [ + gs_binary, + "-q", # quiet mode + f"-g{width:d}x{height:d}", # set output geometry (pixels) + f"-r{res_x:f}x{res_y:f}", # set input DPI (dots per inch) + "-dBATCH", # exit after processing + "-dNOPAUSE", # don't pause between pages + "-dSAFER", # safe mode + f"-sDEVICE={device}", + f"-sOutputFile={outfile}", # output file + # adjust for image origin + "-c", + f"{-bbox[0]} {-bbox[1]} translate", + "-f", + infile, # input file + # showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272) + "-c", + "showpage", + ] + + # push data through Ghostscript + try: + startupinfo = None + if sys.platform.startswith("win"): + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + subprocess.check_call(command, startupinfo=startupinfo) + with Image.open(outfile) as out_im: + out_im.load() + return out_im.im.copy() + finally: + try: + os.unlink(outfile) + if infile_temp: + os.unlink(infile_temp) + except OSError: + pass + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"%!PS") or ( + len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5 + ) + + +## +# Image plugin for Encapsulated PostScript. This plugin supports only +# a few variants of this format. + + +class EpsImageFile(ImageFile.ImageFile): + """EPS File Parser for the Python Imaging Library""" + + format = "EPS" + format_description = "Encapsulated Postscript" + + mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"} + + def _open(self) -> None: + assert self.fp is not None + (length, offset) = self._find_offset(self.fp) + + # go to offset - start of "%!PS" + self.fp.seek(offset) + + self._mode = "RGB" + + # When reading header comments, the first comment is used. + # When reading trailer comments, the last comment is used. + bounding_box: list[int] | None = None + imagedata_size: tuple[int, int] | None = None + + byte_arr = bytearray(255) + bytes_mv = memoryview(byte_arr) + bytes_read = 0 + reading_header_comments = True + reading_trailer_comments = False + trailer_reached = False + + def check_required_header_comments() -> None: + """ + The EPS specification requires that some headers exist. + This should be checked when the header comments formally end, + when image data starts, or when the file ends, whichever comes first. + """ + if "PS-Adobe" not in self.info: + msg = 'EPS header missing "%!PS-Adobe" comment' + raise SyntaxError(msg) + if "BoundingBox" not in self.info: + msg = 'EPS header missing "%%BoundingBox" comment' + raise SyntaxError(msg) + + def read_comment(s: str) -> bool: + nonlocal bounding_box, reading_trailer_comments + try: + m = split.match(s) + except re.error as e: + msg = "not an EPS file" + raise SyntaxError(msg) from e + + if not m: + return False + + k, v = m.group(1, 2) + self.info[k] = v + if k == "BoundingBox": + if v == "(atend)": + reading_trailer_comments = True + elif not bounding_box or (trailer_reached and reading_trailer_comments): + try: + # Note: The DSC spec says that BoundingBox + # fields should be integers, but some drivers + # put floating point values there anyway. + bounding_box = [int(float(i)) for i in v.split()] + except Exception: + pass + return True + + while True: + byte = self.fp.read(1) + if byte == b"": + # if we didn't read a byte we must be at the end of the file + if bytes_read == 0: + if reading_header_comments: + check_required_header_comments() + break + elif byte in b"\r\n": + # if we read a line ending character, ignore it and parse what + # we have already read. if we haven't read any other characters, + # continue reading + if bytes_read == 0: + continue + else: + # ASCII/hexadecimal lines in an EPS file must not exceed + # 255 characters, not including line ending characters + if bytes_read >= 255: + # only enforce this for lines starting with a "%", + # otherwise assume it's binary data + if byte_arr[0] == ord("%"): + msg = "not an EPS file" + raise SyntaxError(msg) + else: + if reading_header_comments: + check_required_header_comments() + reading_header_comments = False + # reset bytes_read so we can keep reading + # data until the end of the line + bytes_read = 0 + byte_arr[bytes_read] = byte[0] + bytes_read += 1 + continue + + if reading_header_comments: + # Load EPS header + + # if this line doesn't start with a "%", + # or does start with "%%EndComments", + # then we've reached the end of the header/comments + if byte_arr[0] != ord("%") or bytes_mv[:13] == b"%%EndComments": + check_required_header_comments() + reading_header_comments = False + continue + + s = str(bytes_mv[:bytes_read], "latin-1") + if not read_comment(s): + m = field.match(s) + if m: + k = m.group(1) + if k.startswith("PS-Adobe"): + self.info["PS-Adobe"] = k[9:] + else: + self.info[k] = "" + elif s[0] == "%": + # handle non-DSC PostScript comments that some + # tools mistakenly put in the Comments section + pass + else: + msg = "bad EPS header" + raise OSError(msg) + elif bytes_mv[:11] == b"%ImageData:": + # Check for an "ImageData" descriptor + # https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577413_pgfId-1035096 + + # If we've already read an "ImageData" descriptor, + # don't read another one. + if imagedata_size: + bytes_read = 0 + continue + + # Values: + # columns + # rows + # bit depth (1 or 8) + # mode (1: L, 2: LAB, 3: RGB, 4: CMYK) + # number of padding channels + # block size (number of bytes per row per channel) + # binary/ascii (1: binary, 2: ascii) + # data start identifier (the image data follows after a single line + # consisting only of this quoted value) + image_data_values = byte_arr[11:bytes_read].split(None, 7) + columns, rows, bit_depth, mode_id = ( + int(value) for value in image_data_values[:4] + ) + + if bit_depth == 1: + self._mode = "1" + elif bit_depth == 8: + try: + self._mode = self.mode_map[mode_id] + except ValueError: + break + else: + break + + # Parse the columns and rows after checking the bit depth and mode + # in case the bit depth and/or mode are invalid. + imagedata_size = columns, rows + elif bytes_mv[:5] == b"%%EOF": + break + elif trailer_reached and reading_trailer_comments: + # Load EPS trailer + s = str(bytes_mv[:bytes_read], "latin-1") + read_comment(s) + elif bytes_mv[:9] == b"%%Trailer": + trailer_reached = True + elif bytes_mv[:14] == b"%%BeginBinary:": + bytecount = int(byte_arr[14:bytes_read]) + self.fp.seek(bytecount, os.SEEK_CUR) + bytes_read = 0 + + # A "BoundingBox" is always required, + # even if an "ImageData" descriptor size exists. + if not bounding_box: + msg = "cannot determine EPS bounding box" + raise OSError(msg) + + # An "ImageData" size takes precedence over the "BoundingBox". + self._size = imagedata_size or ( + bounding_box[2] - bounding_box[0], + bounding_box[3] - bounding_box[1], + ) + + self.tile = [ + ImageFile._Tile("eps", (0, 0) + self.size, offset, (length, bounding_box)) + ] + + def _find_offset(self, fp: IO[bytes]) -> tuple[int, int]: + s = fp.read(4) + + if s == b"%!PS": + # for HEAD without binary preview + fp.seek(0, io.SEEK_END) + length = fp.tell() + offset = 0 + elif i32(s) == 0xC6D3D0C5: + # FIX for: Some EPS file not handled correctly / issue #302 + # EPS can contain binary data + # or start directly with latin coding + # more info see: + # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf + s = fp.read(8) + offset = i32(s) + length = i32(s, 4) + else: + msg = "not an EPS file" + raise SyntaxError(msg) + + return length, offset + + def load( + self, scale: int = 1, transparency: bool = False + ) -> Image.core.PixelAccess | None: + # Load EPS via Ghostscript + if self.tile: + assert self.fp is not None + self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency) + self._mode = self.im.mode + self._size = self.im.size + self.tile = [] + return Image.Image.load(self) + + def load_seek(self, pos: int) -> None: + # we can't incrementally load, so force ImageFile.parser to + # use our custom load method by defining this method. + pass + + +# -------------------------------------------------------------------- + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes, eps: int = 1) -> None: + """EPS Writer for the Python Imaging Library.""" + + # make sure image data is available + im.load() + + # determine PostScript image mode + if im.mode == "L": + operator = (8, 1, b"image") + elif im.mode == "RGB": + operator = (8, 3, b"false 3 colorimage") + elif im.mode == "CMYK": + operator = (8, 4, b"false 4 colorimage") + else: + msg = "image mode is not supported" + raise ValueError(msg) + + if eps: + # write EPS header + fp.write(b"%!PS-Adobe-3.0 EPSF-3.0\n") + fp.write(b"%%Creator: PIL 0.1 EpsEncode\n") + # fp.write("%%CreationDate: %s"...) + fp.write(b"%%%%BoundingBox: 0 0 %d %d\n" % im.size) + fp.write(b"%%Pages: 1\n") + fp.write(b"%%EndComments\n") + fp.write(b"%%Page: 1 1\n") + fp.write(b"%%ImageData: %d %d " % im.size) + fp.write(b'%d %d 0 1 1 "%s"\n' % operator) + + # image header + fp.write(b"gsave\n") + fp.write(b"10 dict begin\n") + fp.write(b"/buf %d string def\n" % (im.size[0] * operator[1])) + fp.write(b"%d %d scale\n" % im.size) + fp.write(b"%d %d 8\n" % im.size) # <= bits + fp.write(b"[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1])) + fp.write(b"{ currentfile buf readhexstring pop } bind\n") + fp.write(operator[2] + b"\n") + if hasattr(fp, "flush"): + fp.flush() + + ImageFile._save(im, fp, [ImageFile._Tile("eps", (0, 0) + im.size)]) + + fp.write(b"\n%%%%EndBinary\n") + fp.write(b"grestore end\n") + if hasattr(fp, "flush"): + fp.flush() + + +# -------------------------------------------------------------------- + + +Image.register_open(EpsImageFile.format, EpsImageFile, _accept) + +Image.register_save(EpsImageFile.format, _save) + +Image.register_extensions(EpsImageFile.format, [".ps", ".eps"]) + +Image.register_mime(EpsImageFile.format, "application/postscript") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ExifTags.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ExifTags.py new file mode 100644 index 0000000000000000000000000000000000000000..2280d5ce84b9badabe16cfb0db37f739d50d51c6 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ExifTags.py @@ -0,0 +1,382 @@ +# +# The Python Imaging Library. +# $Id$ +# +# EXIF tags +# +# Copyright (c) 2003 by Secret Labs AB +# +# See the README file for information on usage and redistribution. +# + +""" +This module provides constants and clear-text names for various +well-known EXIF tags. +""" +from __future__ import annotations + +from enum import IntEnum + + +class Base(IntEnum): + # possibly incomplete + InteropIndex = 0x0001 + ProcessingSoftware = 0x000B + NewSubfileType = 0x00FE + SubfileType = 0x00FF + ImageWidth = 0x0100 + ImageLength = 0x0101 + BitsPerSample = 0x0102 + Compression = 0x0103 + PhotometricInterpretation = 0x0106 + Thresholding = 0x0107 + CellWidth = 0x0108 + CellLength = 0x0109 + FillOrder = 0x010A + DocumentName = 0x010D + ImageDescription = 0x010E + Make = 0x010F + Model = 0x0110 + StripOffsets = 0x0111 + Orientation = 0x0112 + SamplesPerPixel = 0x0115 + RowsPerStrip = 0x0116 + StripByteCounts = 0x0117 + MinSampleValue = 0x0118 + MaxSampleValue = 0x0119 + XResolution = 0x011A + YResolution = 0x011B + PlanarConfiguration = 0x011C + PageName = 0x011D + FreeOffsets = 0x0120 + FreeByteCounts = 0x0121 + GrayResponseUnit = 0x0122 + GrayResponseCurve = 0x0123 + T4Options = 0x0124 + T6Options = 0x0125 + ResolutionUnit = 0x0128 + PageNumber = 0x0129 + TransferFunction = 0x012D + Software = 0x0131 + DateTime = 0x0132 + Artist = 0x013B + HostComputer = 0x013C + Predictor = 0x013D + WhitePoint = 0x013E + PrimaryChromaticities = 0x013F + ColorMap = 0x0140 + HalftoneHints = 0x0141 + TileWidth = 0x0142 + TileLength = 0x0143 + TileOffsets = 0x0144 + TileByteCounts = 0x0145 + SubIFDs = 0x014A + InkSet = 0x014C + InkNames = 0x014D + NumberOfInks = 0x014E + DotRange = 0x0150 + TargetPrinter = 0x0151 + ExtraSamples = 0x0152 + SampleFormat = 0x0153 + SMinSampleValue = 0x0154 + SMaxSampleValue = 0x0155 + TransferRange = 0x0156 + ClipPath = 0x0157 + XClipPathUnits = 0x0158 + YClipPathUnits = 0x0159 + Indexed = 0x015A + JPEGTables = 0x015B + OPIProxy = 0x015F + JPEGProc = 0x0200 + JpegIFOffset = 0x0201 + JpegIFByteCount = 0x0202 + JpegRestartInterval = 0x0203 + JpegLosslessPredictors = 0x0205 + JpegPointTransforms = 0x0206 + JpegQTables = 0x0207 + JpegDCTables = 0x0208 + JpegACTables = 0x0209 + YCbCrCoefficients = 0x0211 + YCbCrSubSampling = 0x0212 + YCbCrPositioning = 0x0213 + ReferenceBlackWhite = 0x0214 + XMLPacket = 0x02BC + RelatedImageFileFormat = 0x1000 + RelatedImageWidth = 0x1001 + RelatedImageLength = 0x1002 + Rating = 0x4746 + RatingPercent = 0x4749 + ImageID = 0x800D + CFARepeatPatternDim = 0x828D + BatteryLevel = 0x828F + Copyright = 0x8298 + ExposureTime = 0x829A + FNumber = 0x829D + IPTCNAA = 0x83BB + ImageResources = 0x8649 + ExifOffset = 0x8769 + InterColorProfile = 0x8773 + ExposureProgram = 0x8822 + SpectralSensitivity = 0x8824 + GPSInfo = 0x8825 + ISOSpeedRatings = 0x8827 + OECF = 0x8828 + Interlace = 0x8829 + TimeZoneOffset = 0x882A + SelfTimerMode = 0x882B + SensitivityType = 0x8830 + StandardOutputSensitivity = 0x8831 + RecommendedExposureIndex = 0x8832 + ISOSpeed = 0x8833 + ISOSpeedLatitudeyyy = 0x8834 + ISOSpeedLatitudezzz = 0x8835 + ExifVersion = 0x9000 + DateTimeOriginal = 0x9003 + DateTimeDigitized = 0x9004 + OffsetTime = 0x9010 + OffsetTimeOriginal = 0x9011 + OffsetTimeDigitized = 0x9012 + ComponentsConfiguration = 0x9101 + CompressedBitsPerPixel = 0x9102 + ShutterSpeedValue = 0x9201 + ApertureValue = 0x9202 + BrightnessValue = 0x9203 + ExposureBiasValue = 0x9204 + MaxApertureValue = 0x9205 + SubjectDistance = 0x9206 + MeteringMode = 0x9207 + LightSource = 0x9208 + Flash = 0x9209 + FocalLength = 0x920A + Noise = 0x920D + ImageNumber = 0x9211 + SecurityClassification = 0x9212 + ImageHistory = 0x9213 + TIFFEPStandardID = 0x9216 + MakerNote = 0x927C + UserComment = 0x9286 + SubsecTime = 0x9290 + SubsecTimeOriginal = 0x9291 + SubsecTimeDigitized = 0x9292 + AmbientTemperature = 0x9400 + Humidity = 0x9401 + Pressure = 0x9402 + WaterDepth = 0x9403 + Acceleration = 0x9404 + CameraElevationAngle = 0x9405 + XPTitle = 0x9C9B + XPComment = 0x9C9C + XPAuthor = 0x9C9D + XPKeywords = 0x9C9E + XPSubject = 0x9C9F + FlashPixVersion = 0xA000 + ColorSpace = 0xA001 + ExifImageWidth = 0xA002 + ExifImageHeight = 0xA003 + RelatedSoundFile = 0xA004 + ExifInteroperabilityOffset = 0xA005 + FlashEnergy = 0xA20B + SpatialFrequencyResponse = 0xA20C + FocalPlaneXResolution = 0xA20E + FocalPlaneYResolution = 0xA20F + FocalPlaneResolutionUnit = 0xA210 + SubjectLocation = 0xA214 + ExposureIndex = 0xA215 + SensingMethod = 0xA217 + FileSource = 0xA300 + SceneType = 0xA301 + CFAPattern = 0xA302 + CustomRendered = 0xA401 + ExposureMode = 0xA402 + WhiteBalance = 0xA403 + DigitalZoomRatio = 0xA404 + FocalLengthIn35mmFilm = 0xA405 + SceneCaptureType = 0xA406 + GainControl = 0xA407 + Contrast = 0xA408 + Saturation = 0xA409 + Sharpness = 0xA40A + DeviceSettingDescription = 0xA40B + SubjectDistanceRange = 0xA40C + ImageUniqueID = 0xA420 + CameraOwnerName = 0xA430 + BodySerialNumber = 0xA431 + LensSpecification = 0xA432 + LensMake = 0xA433 + LensModel = 0xA434 + LensSerialNumber = 0xA435 + CompositeImage = 0xA460 + CompositeImageCount = 0xA461 + CompositeImageExposureTimes = 0xA462 + Gamma = 0xA500 + PrintImageMatching = 0xC4A5 + DNGVersion = 0xC612 + DNGBackwardVersion = 0xC613 + UniqueCameraModel = 0xC614 + LocalizedCameraModel = 0xC615 + CFAPlaneColor = 0xC616 + CFALayout = 0xC617 + LinearizationTable = 0xC618 + BlackLevelRepeatDim = 0xC619 + BlackLevel = 0xC61A + BlackLevelDeltaH = 0xC61B + BlackLevelDeltaV = 0xC61C + WhiteLevel = 0xC61D + DefaultScale = 0xC61E + DefaultCropOrigin = 0xC61F + DefaultCropSize = 0xC620 + ColorMatrix1 = 0xC621 + ColorMatrix2 = 0xC622 + CameraCalibration1 = 0xC623 + CameraCalibration2 = 0xC624 + ReductionMatrix1 = 0xC625 + ReductionMatrix2 = 0xC626 + AnalogBalance = 0xC627 + AsShotNeutral = 0xC628 + AsShotWhiteXY = 0xC629 + BaselineExposure = 0xC62A + BaselineNoise = 0xC62B + BaselineSharpness = 0xC62C + BayerGreenSplit = 0xC62D + LinearResponseLimit = 0xC62E + CameraSerialNumber = 0xC62F + LensInfo = 0xC630 + ChromaBlurRadius = 0xC631 + AntiAliasStrength = 0xC632 + ShadowScale = 0xC633 + DNGPrivateData = 0xC634 + MakerNoteSafety = 0xC635 + CalibrationIlluminant1 = 0xC65A + CalibrationIlluminant2 = 0xC65B + BestQualityScale = 0xC65C + RawDataUniqueID = 0xC65D + OriginalRawFileName = 0xC68B + OriginalRawFileData = 0xC68C + ActiveArea = 0xC68D + MaskedAreas = 0xC68E + AsShotICCProfile = 0xC68F + AsShotPreProfileMatrix = 0xC690 + CurrentICCProfile = 0xC691 + CurrentPreProfileMatrix = 0xC692 + ColorimetricReference = 0xC6BF + CameraCalibrationSignature = 0xC6F3 + ProfileCalibrationSignature = 0xC6F4 + AsShotProfileName = 0xC6F6 + NoiseReductionApplied = 0xC6F7 + ProfileName = 0xC6F8 + ProfileHueSatMapDims = 0xC6F9 + ProfileHueSatMapData1 = 0xC6FA + ProfileHueSatMapData2 = 0xC6FB + ProfileToneCurve = 0xC6FC + ProfileEmbedPolicy = 0xC6FD + ProfileCopyright = 0xC6FE + ForwardMatrix1 = 0xC714 + ForwardMatrix2 = 0xC715 + PreviewApplicationName = 0xC716 + PreviewApplicationVersion = 0xC717 + PreviewSettingsName = 0xC718 + PreviewSettingsDigest = 0xC719 + PreviewColorSpace = 0xC71A + PreviewDateTime = 0xC71B + RawImageDigest = 0xC71C + OriginalRawFileDigest = 0xC71D + SubTileBlockSize = 0xC71E + RowInterleaveFactor = 0xC71F + ProfileLookTableDims = 0xC725 + ProfileLookTableData = 0xC726 + OpcodeList1 = 0xC740 + OpcodeList2 = 0xC741 + OpcodeList3 = 0xC74E + NoiseProfile = 0xC761 + + +"""Maps EXIF tags to tag names.""" +TAGS = { + **{i.value: i.name for i in Base}, + 0x920C: "SpatialFrequencyResponse", + 0x9214: "SubjectLocation", + 0x9215: "ExposureIndex", + 0x828E: "CFAPattern", + 0x920B: "FlashEnergy", + 0x9216: "TIFF/EPStandardID", +} + + +class GPS(IntEnum): + GPSVersionID = 0x00 + GPSLatitudeRef = 0x01 + GPSLatitude = 0x02 + GPSLongitudeRef = 0x03 + GPSLongitude = 0x04 + GPSAltitudeRef = 0x05 + GPSAltitude = 0x06 + GPSTimeStamp = 0x07 + GPSSatellites = 0x08 + GPSStatus = 0x09 + GPSMeasureMode = 0x0A + GPSDOP = 0x0B + GPSSpeedRef = 0x0C + GPSSpeed = 0x0D + GPSTrackRef = 0x0E + GPSTrack = 0x0F + GPSImgDirectionRef = 0x10 + GPSImgDirection = 0x11 + GPSMapDatum = 0x12 + GPSDestLatitudeRef = 0x13 + GPSDestLatitude = 0x14 + GPSDestLongitudeRef = 0x15 + GPSDestLongitude = 0x16 + GPSDestBearingRef = 0x17 + GPSDestBearing = 0x18 + GPSDestDistanceRef = 0x19 + GPSDestDistance = 0x1A + GPSProcessingMethod = 0x1B + GPSAreaInformation = 0x1C + GPSDateStamp = 0x1D + GPSDifferential = 0x1E + GPSHPositioningError = 0x1F + + +"""Maps EXIF GPS tags to tag names.""" +GPSTAGS = {i.value: i.name for i in GPS} + + +class Interop(IntEnum): + InteropIndex = 0x0001 + InteropVersion = 0x0002 + RelatedImageFileFormat = 0x1000 + RelatedImageWidth = 0x1001 + RelatedImageHeight = 0x1002 + + +class IFD(IntEnum): + Exif = 0x8769 + GPSInfo = 0x8825 + MakerNote = 0x927C + Makernote = 0x927C # Deprecated + Interop = 0xA005 + IFD1 = -1 + + +class LightSource(IntEnum): + Unknown = 0x00 + Daylight = 0x01 + Fluorescent = 0x02 + Tungsten = 0x03 + Flash = 0x04 + Fine = 0x09 + Cloudy = 0x0A + Shade = 0x0B + DaylightFluorescent = 0x0C + DayWhiteFluorescent = 0x0D + CoolWhiteFluorescent = 0x0E + WhiteFluorescent = 0x0F + StandardLightA = 0x11 + StandardLightB = 0x12 + StandardLightC = 0x13 + D55 = 0x14 + D65 = 0x15 + D75 = 0x16 + D50 = 0x17 + ISO = 0x18 + Other = 0xFF diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FitsImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FitsImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..a3fdc0efeec6f7ec195112ded41d8ff1e248a6a0 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FitsImagePlugin.py @@ -0,0 +1,152 @@ +# +# The Python Imaging Library +# $Id$ +# +# FITS file handling +# +# Copyright (c) 1998-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import gzip +import math + +from . import Image, ImageFile + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"SIMPLE") + + +class FitsImageFile(ImageFile.ImageFile): + format = "FITS" + format_description = "FITS" + + def _open(self) -> None: + assert self.fp is not None + + headers: dict[bytes, bytes] = {} + header_in_progress = False + decoder_name = "" + while True: + header = self.fp.read(80) + if not header: + msg = "Truncated FITS file" + raise OSError(msg) + keyword = header[:8].strip() + if keyword in (b"SIMPLE", b"XTENSION"): + header_in_progress = True + elif headers and not header_in_progress: + # This is now a data unit + break + elif keyword == b"END": + # Seek to the end of the header unit + self.fp.seek(math.ceil(self.fp.tell() / 2880) * 2880) + if not decoder_name: + decoder_name, offset, args = self._parse_headers(headers) + + header_in_progress = False + continue + + if decoder_name: + # Keep going to read past the headers + continue + + value = header[8:].split(b"/")[0].strip() + if value.startswith(b"="): + value = value[1:].strip() + if not headers and (not _accept(keyword) or value != b"T"): + msg = "Not a FITS file" + raise SyntaxError(msg) + headers[keyword] = value + + if not decoder_name: + msg = "No image data" + raise ValueError(msg) + + offset += self.fp.tell() - 80 + self.tile = [ImageFile._Tile(decoder_name, (0, 0) + self.size, offset, args)] + + def _get_size( + self, headers: dict[bytes, bytes], prefix: bytes + ) -> tuple[int, int] | None: + naxis = int(headers[prefix + b"NAXIS"]) + if naxis == 0: + return None + + if naxis == 1: + return 1, int(headers[prefix + b"NAXIS1"]) + else: + return int(headers[prefix + b"NAXIS1"]), int(headers[prefix + b"NAXIS2"]) + + def _parse_headers( + self, headers: dict[bytes, bytes] + ) -> tuple[str, int, tuple[str | int, ...]]: + prefix = b"" + decoder_name = "raw" + offset = 0 + if ( + headers.get(b"XTENSION") == b"'BINTABLE'" + and headers.get(b"ZIMAGE") == b"T" + and headers[b"ZCMPTYPE"] == b"'GZIP_1 '" + ): + no_prefix_size = self._get_size(headers, prefix) or (0, 0) + number_of_bits = int(headers[b"BITPIX"]) + offset = no_prefix_size[0] * no_prefix_size[1] * (number_of_bits // 8) + + prefix = b"Z" + decoder_name = "fits_gzip" + + size = self._get_size(headers, prefix) + if not size: + return "", 0, () + + self._size = size + + number_of_bits = int(headers[prefix + b"BITPIX"]) + if number_of_bits == 8: + self._mode = "L" + elif number_of_bits == 16: + self._mode = "I;16" + elif number_of_bits == 32: + self._mode = "I" + elif number_of_bits in (-32, -64): + self._mode = "F" + + args: tuple[str | int, ...] + if decoder_name == "raw": + args = (self.mode, 0, -1) + else: + args = (number_of_bits,) + return decoder_name, offset, args + + +class FitsGzipDecoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + assert self.fd is not None + value = gzip.decompress(self.fd.read()) + + rows = [] + offset = 0 + number_of_bits = min(self.args[0] // 8, 4) + for y in range(self.state.ysize): + row = bytearray() + for x in range(self.state.xsize): + row += value[offset + (4 - number_of_bits) : offset + 4] + offset += 4 + rows.append(row) + self.set_as_raw(bytes([pixel for row in rows[::-1] for pixel in row])) + return -1, 0 + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(FitsImageFile.format, FitsImageFile, _accept) +Image.register_decoder("fits_gzip", FitsGzipDecoder) + +Image.register_extensions(FitsImageFile.format, [".fit", ".fits"]) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FliImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FliImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..da1e8e95cf3263bb2e22ba45d6d2934fc4f2b54f --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FliImagePlugin.py @@ -0,0 +1,184 @@ +# +# The Python Imaging Library. +# $Id$ +# +# FLI/FLC file handling. +# +# History: +# 95-09-01 fl Created +# 97-01-03 fl Fixed parser, setup decoder tile +# 98-07-15 fl Renamed offset attribute to avoid name clash +# +# Copyright (c) Secret Labs AB 1997-98. +# Copyright (c) Fredrik Lundh 1995-97. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os + +from . import Image, ImageFile, ImagePalette +from ._binary import i16le as i16 +from ._binary import i32le as i32 +from ._binary import o8 +from ._util import DeferredError + +# +# decoder + + +def _accept(prefix: bytes) -> bool: + return ( + len(prefix) >= 16 + and i16(prefix, 4) in [0xAF11, 0xAF12] + and i16(prefix, 14) in [0, 3] # flags + ) + + +## +# Image plugin for the FLI/FLC animation format. Use the seek +# method to load individual frames. + + +class FliImageFile(ImageFile.ImageFile): + format = "FLI" + format_description = "Autodesk FLI/FLC Animation" + _close_exclusive_fp_after_loading = False + + def _open(self) -> None: + # HEAD + assert self.fp is not None + s = self.fp.read(128) + if not ( + _accept(s) + and s[20:22] == b"\x00" * 2 + and s[42:80] == b"\x00" * 38 + and s[88:] == b"\x00" * 40 + ): + msg = "not an FLI/FLC file" + raise SyntaxError(msg) + + # frames + self.n_frames = i16(s, 6) + self.is_animated = self.n_frames > 1 + + # image characteristics + self._mode = "P" + self._size = i16(s, 8), i16(s, 10) + + # animation speed + duration = i32(s, 16) + magic = i16(s, 4) + if magic == 0xAF11: + duration = (duration * 1000) // 70 + self.info["duration"] = duration + + # look for palette + palette = [(a, a, a) for a in range(256)] + + s = self.fp.read(16) + + self.__offset = 128 + + if i16(s, 4) == 0xF100: + # prefix chunk; ignore it + self.fp.seek(self.__offset + i32(s)) + s = self.fp.read(16) + + if i16(s, 4) == 0xF1FA: + # look for palette chunk + number_of_subchunks = i16(s, 6) + chunk_size: int | None = None + for _ in range(number_of_subchunks): + if chunk_size is not None: + self.fp.seek(chunk_size - 6, os.SEEK_CUR) + s = self.fp.read(6) + chunk_type = i16(s, 4) + if chunk_type in (4, 11): + self._palette(palette, 2 if chunk_type == 11 else 0) + break + chunk_size = i32(s) + if not chunk_size: + break + + self.palette = ImagePalette.raw( + "RGB", b"".join(o8(r) + o8(g) + o8(b) for (r, g, b) in palette) + ) + + # set things up to decode first frame + self.__frame = -1 + self._fp = self.fp + self.__rewind = self.fp.tell() + self.seek(0) + + def _palette(self, palette: list[tuple[int, int, int]], shift: int) -> None: + # load palette + + i = 0 + assert self.fp is not None + for e in range(i16(self.fp.read(2))): + s = self.fp.read(2) + i = i + s[0] + n = s[1] + if n == 0: + n = 256 + s = self.fp.read(n * 3) + for n in range(0, len(s), 3): + r = s[n] << shift + g = s[n + 1] << shift + b = s[n + 2] << shift + palette[i] = (r, g, b) + i += 1 + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + if frame < self.__frame: + self._seek(0) + + for f in range(self.__frame + 1, frame + 1): + self._seek(f) + + def _seek(self, frame: int) -> None: + if isinstance(self._fp, DeferredError): + raise self._fp.ex + if frame == 0: + self.__frame = -1 + self._fp.seek(self.__rewind) + self.__offset = 128 + else: + # ensure that the previous frame was loaded + self.load() + + if frame != self.__frame + 1: + msg = f"cannot seek to frame {frame}" + raise ValueError(msg) + self.__frame = frame + + # move to next frame + self.fp = self._fp + self.fp.seek(self.__offset) + + s = self.fp.read(4) + if not s: + msg = "missing frame size" + raise EOFError(msg) + + framesize = i32(s) + + self.decodermaxblock = framesize + self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset)] + + self.__offset += framesize + + def tell(self) -> int: + return self.__frame + + +# +# registry + +Image.register_open(FliImageFile.format, FliImageFile, _accept) + +Image.register_extensions(FliImageFile.format, [".fli", ".flc"]) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FontFile.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FontFile.py new file mode 100644 index 0000000000000000000000000000000000000000..1e0c1c166b5932a7621e510eba047586465e03d8 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FontFile.py @@ -0,0 +1,134 @@ +# +# The Python Imaging Library +# $Id$ +# +# base class for raster font file parsers +# +# history: +# 1997-06-05 fl created +# 1997-08-19 fl restrict image width +# +# Copyright (c) 1997-1998 by Secret Labs AB +# Copyright (c) 1997-1998 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +from typing import BinaryIO + +from . import Image, _binary + +WIDTH = 800 + + +def puti16( + fp: BinaryIO, values: tuple[int, int, int, int, int, int, int, int, int, int] +) -> None: + """Write network order (big-endian) 16-bit sequence""" + for v in values: + if v < 0: + v += 65536 + fp.write(_binary.o16be(v)) + + +class FontFile: + """Base class for raster font file handlers.""" + + bitmap: Image.Image | None = None + + def __init__(self) -> None: + self.info: dict[bytes, bytes | int] = {} + self.glyph: list[ + tuple[ + tuple[int, int], + tuple[int, int, int, int], + tuple[int, int, int, int], + Image.Image, + ] + | None + ] = [None] * 256 + + def __getitem__(self, ix: int) -> ( + tuple[ + tuple[int, int], + tuple[int, int, int, int], + tuple[int, int, int, int], + Image.Image, + ] + | None + ): + return self.glyph[ix] + + def compile(self) -> None: + """Create metrics and bitmap""" + + if self.bitmap: + return + + # create bitmap large enough to hold all data + h = w = maxwidth = 0 + lines = 1 + for glyph in self.glyph: + if glyph: + d, dst, src, im = glyph + h = max(h, src[3] - src[1]) + w = w + (src[2] - src[0]) + if w > WIDTH: + lines += 1 + w = src[2] - src[0] + maxwidth = max(maxwidth, w) + + xsize = maxwidth + ysize = lines * h + + if xsize == 0 and ysize == 0: + return + + self.ysize = h + + # paste glyphs into bitmap + self.bitmap = Image.new("1", (xsize, ysize)) + self.metrics: list[ + tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]] + | None + ] = [None] * 256 + x = y = 0 + for i in range(256): + glyph = self[i] + if glyph: + d, dst, src, im = glyph + xx = src[2] - src[0] + x0, y0 = x, y + x = x + xx + if x > WIDTH: + x, y = 0, y + h + x0, y0 = x, y + x = xx + s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0 + self.bitmap.paste(im.crop(src), s) + self.metrics[i] = d, dst, s + + def save(self, filename: str) -> None: + """Save font""" + + self.compile() + + # font data + if not self.bitmap: + msg = "No bitmap created" + raise ValueError(msg) + self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG") + + # font metrics + with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp: + fp.write(b"PILfont\n") + fp.write(f";;;;;;{self.ysize};\n".encode("ascii")) # HACK!!! + fp.write(b"DATA\n") + for id in range(256): + m = self.metrics[id] + if not m: + puti16(fp, (0,) * 10) + else: + puti16(fp, m[0] + m[1] + m[2]) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FpxImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FpxImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..297971234d86a4900b91e801d5985b6ce0a3a23e --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FpxImagePlugin.py @@ -0,0 +1,259 @@ +# +# THIS IS WORK IN PROGRESS +# +# The Python Imaging Library. +# $Id$ +# +# FlashPix support for PIL +# +# History: +# 97-01-25 fl Created (reads uncompressed RGB images only) +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1997. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import olefile + +from . import Image, ImageFile +from ._binary import i32le as i32 + +# we map from colour field tuples to (mode, rawmode) descriptors +MODES = { + # opacity + (0x00007FFE,): ("A", "L"), + # monochrome + (0x00010000,): ("L", "L"), + (0x00018000, 0x00017FFE): ("RGBA", "LA"), + # photo YCC + (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"), + (0x00028000, 0x00028001, 0x00028002, 0x00027FFE): ("RGBA", "YCCA;P"), + # standard RGB (NIFRGB) + (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"), + (0x00038000, 0x00038001, 0x00038002, 0x00037FFE): ("RGBA", "RGBA"), +} + + +# +# -------------------------------------------------------------------- + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(olefile.MAGIC) + + +## +# Image plugin for the FlashPix images. + + +class FpxImageFile(ImageFile.ImageFile): + format = "FPX" + format_description = "FlashPix" + + def _open(self) -> None: + # + # read the OLE directory and see if this is a likely + # to be a FlashPix file + + assert self.fp is not None + try: + self.ole = olefile.OleFileIO(self.fp) + except OSError as e: + msg = "not an FPX file; invalid OLE file" + raise SyntaxError(msg) from e + + root = self.ole.root + if not root or root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B": + msg = "not an FPX file; bad root CLSID" + raise SyntaxError(msg) + + self._open_index(1) + + def _open_index(self, index: int = 1) -> None: + # + # get the Image Contents Property Set + + prop = self.ole.getproperties( + [f"Data Object Store {index:06d}", "\005Image Contents"] + ) + + # size (highest resolution) + + assert isinstance(prop[0x1000002], int) + assert isinstance(prop[0x1000003], int) + self._size = prop[0x1000002], prop[0x1000003] + + size = max(self.size) + i = 1 + while size > 64: + size = size // 2 + i += 1 + self.maxid = i - 1 + + # mode. instead of using a single field for this, flashpix + # requires you to specify the mode for each channel in each + # resolution subimage, and leaves it to the decoder to make + # sure that they all match. for now, we'll cheat and assume + # that this is always the case. + + id = self.maxid << 16 + + s = prop[0x2000002 | id] + + if not isinstance(s, bytes) or (bands := i32(s, 4)) > 4: + msg = "Invalid number of bands" + raise OSError(msg) + + # note: for now, we ignore the "uncalibrated" flag + colors = tuple(i32(s, 8 + i * 4) & 0x7FFFFFFF for i in range(bands)) + + self._mode, self.rawmode = MODES[colors] + + # load JPEG tables, if any + self.jpeg = {} + for i in range(256): + id = 0x3000001 | (i << 16) + if id in prop: + self.jpeg[i] = prop[id] + + self._open_subimage(1, self.maxid) + + def _open_subimage(self, index: int = 1, subimage: int = 0) -> None: + # + # setup tile descriptors for a given subimage + + stream = [ + f"Data Object Store {index:06d}", + f"Resolution {subimage:04d}", + "Subimage 0000 Header", + ] + + fp = self.ole.openstream(stream) + + # skip prefix + fp.read(28) + + # header stream + s = fp.read(36) + + size = i32(s, 4), i32(s, 8) + # tilecount = i32(s, 12) + tilesize = i32(s, 16), i32(s, 20) + # channels = i32(s, 24) + offset = i32(s, 28) + length = i32(s, 32) + + if size != self.size: + msg = "subimage mismatch" + raise OSError(msg) + + # get tile descriptors + fp.seek(28 + offset) + s = fp.read(i32(s, 12) * length) + + x = y = 0 + xsize, ysize = size + xtile, ytile = tilesize + self.tile = [] + + for i in range(0, len(s), length): + x1 = min(xsize, x + xtile) + y1 = min(ysize, y + ytile) + + compression = i32(s, i + 8) + + if compression == 0: + self.tile.append( + ImageFile._Tile( + "raw", + (x, y, x1, y1), + i32(s, i) + 28, + self.rawmode, + ) + ) + + elif compression == 1: + # FIXME: the fill decoder is not implemented + self.tile.append( + ImageFile._Tile( + "fill", + (x, y, x1, y1), + i32(s, i) + 28, + (self.rawmode, s[12:16]), + ) + ) + + elif compression == 2: + internal_color_conversion = s[14] + jpeg_tables = s[15] + rawmode = self.rawmode + + if internal_color_conversion: + # The image is stored as usual (usually YCbCr). + if rawmode == "RGBA": + # For "RGBA", data is stored as YCbCrA based on + # negative RGB. The following trick works around + # this problem : + jpegmode, rawmode = "YCbCrK", "CMYK" + else: + jpegmode = None # let the decoder decide + + else: + # The image is stored as defined by rawmode + jpegmode = rawmode + + self.tile.append( + ImageFile._Tile( + "jpeg", + (x, y, x1, y1), + i32(s, i) + 28, + (rawmode, jpegmode), + ) + ) + + # FIXME: jpeg tables are tile dependent; the prefix + # data must be placed in the tile descriptor itself! + + if jpeg_tables: + self.tile_prefix = self.jpeg[jpeg_tables] + + else: + msg = "unknown/invalid compression" + raise OSError(msg) + + x = x + xtile + if x >= xsize: + x, y = 0, y + ytile + if y >= ysize: + break # isn't really required + + assert self.fp is not None + self.stream = stream + self._fp = self.fp + self.fp = None + + def load(self) -> Image.core.PixelAccess | None: + if not self.fp: + self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"]) + + return ImageFile.ImageFile.load(self) + + def close(self) -> None: + self.ole.close() + super().close() + + def __exit__(self, *args: object) -> None: + self.ole.close() + super().__exit__() + + +# +# -------------------------------------------------------------------- + + +Image.register_open(FpxImageFile.format, FpxImageFile, _accept) + +Image.register_extension(FpxImageFile.format, ".fpx") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FtexImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FtexImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..e4d836cbdb27cc891f2cf62659eabcfaa37ffb70 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/FtexImagePlugin.py @@ -0,0 +1,115 @@ +""" +A Pillow loader for .ftc and .ftu files (FTEX) +Jerome Leclanche + +The contents of this file are hereby released in the public domain (CC0) +Full text of the CC0 license: + https://creativecommons.org/publicdomain/zero/1.0/ + +Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001 + +The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a +packed custom format called FTEX. This file format uses file extensions FTC +and FTU. +* FTC files are compressed textures (using standard texture compression). +* FTU files are not compressed. +Texture File Format +The FTC and FTU texture files both use the same format. This +has the following structure: +{header} +{format_directory} +{data} +Where: +{header} = { + u32:magic, + u32:version, + u32:width, + u32:height, + u32:mipmap_count, + u32:format_count +} + +* The "magic" number is "FTEX". +* "width" and "height" are the dimensions of the texture. +* "mipmap_count" is the number of mipmaps in the texture. +* "format_count" is the number of texture formats (different versions of the +same texture) in this file. + +{format_directory} = format_count * { u32:format, u32:where } + +The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB +uncompressed textures. +The texture data for a format starts at the position "where" in the file. + +Each set of texture data in the file has the following structure: +{data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } } +* "mipmap_size" is the number of bytes in that mip level. For compressed +textures this is the size of the texture data compressed with DXT1. For 24 bit +uncompressed textures, this is 3 * width * height. Following this are the image +bytes for that mipmap level. + +Note: All data is stored in little-Endian (Intel) byte order. +""" + +from __future__ import annotations + +import struct +from enum import IntEnum +from io import BytesIO + +from . import Image, ImageFile + +MAGIC = b"FTEX" + + +class Format(IntEnum): + DXT1 = 0 + UNCOMPRESSED = 1 + + +class FtexImageFile(ImageFile.ImageFile): + format = "FTEX" + format_description = "Texture File Format (IW2:EOC)" + + def _open(self) -> None: + assert self.fp is not None + if not _accept(self.fp.read(4)): + msg = "not an FTEX file" + raise SyntaxError(msg) + struct.unpack(" None: + pass + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(MAGIC) + + +Image.register_open(FtexImageFile.format, FtexImageFile, _accept) +Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"]) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GbrImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GbrImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..ec666c81c2c6aab6d243747e844e13a0cc1d296f --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GbrImagePlugin.py @@ -0,0 +1,103 @@ +# +# The Python Imaging Library +# +# load a GIMP brush file +# +# History: +# 96-03-14 fl Created +# 16-01-08 es Version 2 +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1996. +# Copyright (c) Eric Soroos 2016. +# +# See the README file for information on usage and redistribution. +# +# +# See https://github.com/GNOME/gimp/blob/mainline/devel-docs/gbr.txt for +# format documentation. +# +# This code Interprets version 1 and 2 .gbr files. +# Version 1 files are obsolete, and should not be used for new +# brushes. +# Version 2 files are saved by GIMP v2.8 (at least) +# Version 3 files have a format specifier of 18 for 16bit floats in +# the color depth field. This is currently unsupported by Pillow. +from __future__ import annotations + +from . import Image, ImageFile +from ._binary import i32be as i32 + + +def _accept(prefix: bytes) -> bool: + return len(prefix) >= 8 and i32(prefix, 0) >= 20 and i32(prefix, 4) in (1, 2) + + +## +# Image plugin for the GIMP brush format. + + +class GbrImageFile(ImageFile.ImageFile): + format = "GBR" + format_description = "GIMP brush file" + + def _open(self) -> None: + assert self.fp is not None + header_size = i32(self.fp.read(4)) + if header_size < 20: + msg = "not a GIMP brush" + raise SyntaxError(msg) + version = i32(self.fp.read(4)) + if version not in (1, 2): + msg = f"Unsupported GIMP brush version: {version}" + raise SyntaxError(msg) + + width = i32(self.fp.read(4)) + height = i32(self.fp.read(4)) + color_depth = i32(self.fp.read(4)) + if width == 0 or height == 0: + msg = "not a GIMP brush" + raise SyntaxError(msg) + if color_depth not in (1, 4): + msg = f"Unsupported GIMP brush color depth: {color_depth}" + raise SyntaxError(msg) + + if version == 1: + comment_length = header_size - 20 + else: + comment_length = header_size - 28 + magic_number = self.fp.read(4) + if magic_number != b"GIMP": + msg = "not a GIMP brush, bad magic number" + raise SyntaxError(msg) + self.info["spacing"] = i32(self.fp.read(4)) + + self.info["comment"] = self.fp.read(comment_length)[:-1] + + if color_depth == 1: + self._mode = "L" + else: + self._mode = "RGBA" + + self._size = width, height + + # Image might not be small + Image._decompression_bomb_check(self.size) + + # Data is an uncompressed block of w * h * bytes/pixel + self._data_size = width * height * color_depth + + def load(self) -> Image.core.PixelAccess | None: + if self._im is None: + assert self.fp is not None + self.im = Image.core.new(self.mode, self.size) + self.frombytes(self.fp.read(self._data_size)) + return Image.Image.load(self) + + +# +# registry + + +Image.register_open(GbrImageFile.format, GbrImageFile, _accept) +Image.register_extension(GbrImageFile.format, ".gbr") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GdImageFile.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GdImageFile.py new file mode 100644 index 0000000000000000000000000000000000000000..891225ce2fd034a11963bb64212cfa7311190441 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GdImageFile.py @@ -0,0 +1,102 @@ +# +# The Python Imaging Library. +# $Id$ +# +# GD file handling +# +# History: +# 1996-04-12 fl Created +# +# Copyright (c) 1997 by Secret Labs AB. +# Copyright (c) 1996 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# + + +""" +.. note:: + This format cannot be automatically recognized, so the + class is not registered for use with :py:func:`PIL.Image.open()`. To open a + gd file, use the :py:func:`PIL.GdImageFile.open()` function instead. + +.. warning:: + THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This + implementation is provided for convenience and demonstrational + purposes only. +""" +from __future__ import annotations + +from typing import IO + +from . import ImageFile, ImagePalette, UnidentifiedImageError +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._typing import StrOrBytesPath + + +class GdImageFile(ImageFile.ImageFile): + """ + Image plugin for the GD uncompressed format. Note that this format + is not supported by the standard :py:func:`PIL.Image.open()` function. To use + this plugin, you have to import the :py:mod:`PIL.GdImageFile` module and + use the :py:func:`PIL.GdImageFile.open()` function. + """ + + format = "GD" + format_description = "GD uncompressed images" + + def _open(self) -> None: + # Header + assert self.fp is not None + + s = self.fp.read(1037) + + if i16(s) not in [65534, 65535]: + msg = "Not a valid GD 2.x .gd file" + raise SyntaxError(msg) + + self._mode = "P" + self._size = i16(s, 2), i16(s, 4) + + true_color = s[6] + true_color_offset = 2 if true_color else 0 + + # transparency index + tindex = i32(s, 7 + true_color_offset) + if tindex < 256: + self.info["transparency"] = tindex + + self.palette = ImagePalette.raw( + "RGBX", s[7 + true_color_offset + 6 : 7 + true_color_offset + 6 + 256 * 4] + ) + + self.tile = [ + ImageFile._Tile( + "raw", + (0, 0) + self.size, + 7 + true_color_offset + 6 + 256 * 4, + "L", + ) + ] + + +def open(fp: StrOrBytesPath | IO[bytes], mode: str = "r") -> GdImageFile: + """ + Load texture from a GD image file. + + :param fp: GD file name, or an opened file handle. + :param mode: Optional mode. In this version, if the mode argument + is given, it must be "r". + :returns: An image instance. + :raises OSError: If the image could not be read. + """ + if mode != "r": + msg = "bad mode" + raise ValueError(msg) + + try: + return GdImageFile(fp) + except SyntaxError as e: + msg = "cannot identify this image file" + raise UnidentifiedImageError(msg) from e diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GifImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GifImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..76a0d4ab99fbcfbdd5a2d4b2ed2aedc53666f962 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GifImagePlugin.py @@ -0,0 +1,1217 @@ +# +# The Python Imaging Library. +# $Id$ +# +# GIF file handling +# +# History: +# 1995-09-01 fl Created +# 1996-12-14 fl Added interlace support +# 1996-12-30 fl Added animation support +# 1997-01-05 fl Added write support, fixed local colour map bug +# 1997-02-23 fl Make sure to load raster data in getdata() +# 1997-07-05 fl Support external decoder (0.4) +# 1998-07-09 fl Handle all modes when saving (0.5) +# 1998-07-15 fl Renamed offset attribute to avoid name clash +# 2001-04-16 fl Added rewind support (seek to frame 0) (0.6) +# 2001-04-17 fl Added palette optimization (0.7) +# 2002-06-06 fl Added transparency support for save (0.8) +# 2004-02-24 fl Disable interlacing for small images +# +# Copyright (c) 1997-2004 by Secret Labs AB +# Copyright (c) 1995-2004 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import itertools +import math +import os +import subprocess +from enum import IntEnum +from functools import cached_property +from typing import Any, NamedTuple, cast + +from . import ( + Image, + ImageChops, + ImageFile, + ImageMath, + ImageOps, + ImagePalette, + ImageSequence, +) +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 +from ._util import DeferredError + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import IO, Literal + + from . import _imaging + from ._typing import Buffer + + +class LoadingStrategy(IntEnum): + """.. versionadded:: 9.1.0""" + + RGB_AFTER_FIRST = 0 + RGB_AFTER_DIFFERENT_PALETTE_ONLY = 1 + RGB_ALWAYS = 2 + + +#: .. versionadded:: 9.1.0 +LOADING_STRATEGY = LoadingStrategy.RGB_AFTER_FIRST + +# -------------------------------------------------------------------- +# Identify/read GIF files + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith((b"GIF87a", b"GIF89a")) + + +## +# Image plugin for GIF images. This plugin supports both GIF87 and +# GIF89 images. + + +class GifImageFile(ImageFile.ImageFile): + format = "GIF" + format_description = "Compuserve GIF" + _close_exclusive_fp_after_loading = False + + global_palette = None + + def data(self) -> bytes | None: + assert self.fp is not None + s = self.fp.read(1) + if s and s[0]: + return self.fp.read(s[0]) + return None + + def _is_palette_needed(self, p: bytes) -> bool: + for i in range(0, len(p), 3): + if not (i // 3 == p[i] == p[i + 1] == p[i + 2]): + return True + return False + + def _open(self) -> None: + # Screen + assert self.fp is not None + s = self.fp.read(13) + if not _accept(s): + msg = "not a GIF file" + raise SyntaxError(msg) + + self.info["version"] = s[:6] + self._size = i16(s, 6), i16(s, 8) + flags = s[10] + bits = (flags & 7) + 1 + + if flags & 128: + # get global palette + self.info["background"] = s[11] + # check if palette contains colour indices + p = self.fp.read(3 << bits) + if self._is_palette_needed(p): + palette = ImagePalette.raw("RGB", p) + self.global_palette = self.palette = palette + + self._fp = self.fp # FIXME: hack + self.__rewind = self.fp.tell() + self._n_frames: int | None = None + self._seek(0) # get ready to read first frame + + @property + def n_frames(self) -> int: + if self._n_frames is None: + current = self.tell() + try: + while True: + self._seek(self.tell() + 1, False) + except EOFError: + self._n_frames = self.tell() + 1 + self.seek(current) + return self._n_frames + + @cached_property + def is_animated(self) -> bool: + if self._n_frames is not None: + return self._n_frames != 1 + + current = self.tell() + if current: + return True + + try: + self._seek(1, False) + is_animated = True + except EOFError: + is_animated = False + + self.seek(current) + return is_animated + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + if frame < self.__frame: + self._im = None + self._seek(0) + + last_frame = self.__frame + for f in range(self.__frame + 1, frame + 1): + try: + self._seek(f) + except EOFError as e: + self.seek(last_frame) + msg = "no more images in GIF file" + raise EOFError(msg) from e + + def _seek(self, frame: int, update_image: bool = True) -> None: + if isinstance(self._fp, DeferredError): + raise self._fp.ex + if frame == 0: + # rewind + self.__offset = 0 + self.dispose: _imaging.ImagingCore | None = None + self.__frame = -1 + self._fp.seek(self.__rewind) + self.disposal_method = 0 + if "comment" in self.info: + del self.info["comment"] + else: + # ensure that the previous frame was loaded + if self.tile and update_image: + self.load() + + if frame != self.__frame + 1: + msg = f"cannot seek to frame {frame}" + raise ValueError(msg) + + self.fp = self._fp + if self.__offset: + # backup to last frame + self.fp.seek(self.__offset) + while self.data(): + pass + self.__offset = 0 + + s = self.fp.read(1) + if not s or s == b";": + msg = "no more images in GIF file" + raise EOFError(msg) + + palette: ImagePalette.ImagePalette | Literal[False] | None = None + + info: dict[str, Any] = {} + frame_transparency = None + interlace = None + frame_dispose_extent = None + while True: + if not s: + s = self.fp.read(1) + if not s or s == b";": + break + + elif s == b"!": + # + # extensions + # + s = self.fp.read(1) + block = self.data() + if s[0] == 249 and block is not None: + # + # graphic control extension + # + flags = block[0] + if flags & 1: + frame_transparency = block[3] + info["duration"] = i16(block, 1) * 10 + + # disposal method - find the value of bits 4 - 6 + dispose_bits = 0b00011100 & flags + dispose_bits = dispose_bits >> 2 + if dispose_bits: + # only set the dispose if it is not + # unspecified. I'm not sure if this is + # correct, but it seems to prevent the last + # frame from looking odd for some animations + self.disposal_method = dispose_bits + elif s[0] == 254: + # + # comment extension + # + comment = b"" + + # Read this comment block + while block: + comment += block + block = self.data() + + if "comment" in info: + # If multiple comment blocks in frame, separate with \n + info["comment"] += b"\n" + comment + else: + info["comment"] = comment + s = b"" + continue + elif s[0] == 255 and frame == 0 and block is not None: + # + # application extension + # + info["extension"] = block, self.fp.tell() + if block.startswith(b"NETSCAPE2.0"): + block = self.data() + if block and len(block) >= 3 and block[0] == 1: + self.info["loop"] = i16(block, 1) + while self.data(): + pass + + elif s == b",": + # + # local image + # + s = self.fp.read(9) + + # extent + x0, y0 = i16(s, 0), i16(s, 2) + x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6) + if (x1 > self.size[0] or y1 > self.size[1]) and update_image: + self._size = max(x1, self.size[0]), max(y1, self.size[1]) + Image._decompression_bomb_check(self._size) + frame_dispose_extent = x0, y0, x1, y1 + flags = s[8] + + interlace = (flags & 64) != 0 + + if flags & 128: + bits = (flags & 7) + 1 + p = self.fp.read(3 << bits) + if self._is_palette_needed(p): + palette = ImagePalette.raw("RGB", p) + else: + palette = False + + # image data + bits = self.fp.read(1)[0] + self.__offset = self.fp.tell() + break + s = b"" + + if interlace is None: + msg = "image not found in GIF frame" + raise EOFError(msg) + + self.__frame = frame + if not update_image: + return + + self.tile = [] + + if self.dispose: + self.im.paste(self.dispose, self.dispose_extent) + + self._frame_palette = palette if palette is not None else self.global_palette + self._frame_transparency = frame_transparency + if frame == 0: + if self._frame_palette: + if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS: + self._mode = "RGBA" if frame_transparency is not None else "RGB" + else: + self._mode = "P" + else: + self._mode = "L" + + if palette: + self.palette = palette + elif self.global_palette: + from copy import copy + + self.palette = copy(self.global_palette) + else: + self.palette = None + else: + if self.mode == "P": + if ( + LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY + or palette + ): + if "transparency" in self.info: + self.im.putpalettealpha(self.info["transparency"], 0) + self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG) + self._mode = "RGBA" + del self.info["transparency"] + else: + self._mode = "RGB" + self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG) + + def _rgb(color: int) -> tuple[int, int, int]: + if self._frame_palette: + if color * 3 + 3 > len(self._frame_palette.palette): + color = 0 + return cast( + tuple[int, int, int], + tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]), + ) + else: + return (color, color, color) + + self.dispose = None + self.dispose_extent: tuple[int, int, int, int] | None = frame_dispose_extent + if self.dispose_extent and self.disposal_method >= 2: + try: + if self.disposal_method == 2: + # replace with background colour + + # only dispose the extent in this frame + x0, y0, x1, y1 = self.dispose_extent + dispose_size = (x1 - x0, y1 - y0) + + Image._decompression_bomb_check(dispose_size) + + # by convention, attempt to use transparency first + dispose_mode = "P" + color = self.info.get("transparency", frame_transparency) + if color is not None: + if self.mode in ("RGB", "RGBA"): + dispose_mode = "RGBA" + color = _rgb(color) + (0,) + else: + color = self.info.get("background", 0) + if self.mode in ("RGB", "RGBA"): + dispose_mode = "RGB" + color = _rgb(color) + self.dispose = Image.core.fill(dispose_mode, dispose_size, color) + else: + # replace with previous contents + if self._im is not None: + # only dispose the extent in this frame + self.dispose = self._crop(self.im, self.dispose_extent) + elif frame_transparency is not None: + x0, y0, x1, y1 = self.dispose_extent + dispose_size = (x1 - x0, y1 - y0) + + Image._decompression_bomb_check(dispose_size) + dispose_mode = "P" + color = frame_transparency + if self.mode in ("RGB", "RGBA"): + dispose_mode = "RGBA" + color = _rgb(frame_transparency) + (0,) + self.dispose = Image.core.fill( + dispose_mode, dispose_size, color + ) + except AttributeError: + pass + + if interlace is not None: + transparency = -1 + if frame_transparency is not None: + if frame == 0: + if LOADING_STRATEGY != LoadingStrategy.RGB_ALWAYS: + self.info["transparency"] = frame_transparency + elif self.mode not in ("RGB", "RGBA"): + transparency = frame_transparency + self.tile = [ + ImageFile._Tile( + "gif", + (x0, y0, x1, y1), + self.__offset, + (bits, interlace, transparency), + ) + ] + + if info.get("comment"): + self.info["comment"] = info["comment"] + for k in ["duration", "extension"]: + if k in info: + self.info[k] = info[k] + elif k in self.info: + del self.info[k] + + def load_prepare(self) -> None: + temp_mode = "P" if self._frame_palette else "L" + self._prev_im = None + if self.__frame == 0: + if self._frame_transparency is not None: + self.im = Image.core.fill( + temp_mode, self.size, self._frame_transparency + ) + elif self.mode in ("RGB", "RGBA"): + self._prev_im = self.im + if self._frame_palette: + self.im = Image.core.fill("P", self.size, self._frame_transparency or 0) + self.im.putpalette("RGB", *self._frame_palette.getdata()) + else: + self._im = None + if not self._prev_im and self._im is not None and self.size != self.im.size: + expanded_im = Image.core.fill(self.im.mode, self.size) + if self._frame_palette: + expanded_im.putpalette("RGB", *self._frame_palette.getdata()) + expanded_im.paste(self.im, (0, 0) + self.im.size) + + self.im = expanded_im + self._mode = temp_mode + self._frame_palette = None + + super().load_prepare() + + def load_end(self) -> None: + if self.__frame == 0: + if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS: + if self._frame_transparency is not None: + self.im.putpalettealpha(self._frame_transparency, 0) + self._mode = "RGBA" + else: + self._mode = "RGB" + self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG) + return + if not self._prev_im: + return + if self.size != self._prev_im.size: + if self._frame_transparency is not None: + expanded_im = Image.core.fill("RGBA", self.size) + else: + expanded_im = Image.core.fill("P", self.size) + expanded_im.putpalette("RGB", "RGB", self.im.getpalette()) + expanded_im = expanded_im.convert("RGB") + expanded_im.paste(self._prev_im, (0, 0) + self._prev_im.size) + + self._prev_im = expanded_im + assert self._prev_im is not None + if self._frame_transparency is not None: + if self.mode == "L": + frame_im = self.im.convert_transparent("LA", self._frame_transparency) + else: + self.im.putpalettealpha(self._frame_transparency, 0) + frame_im = self.im.convert("RGBA") + else: + frame_im = self.im.convert("RGB") + + assert self.dispose_extent is not None + frame_im = self._crop(frame_im, self.dispose_extent) + + self.im = self._prev_im + self._mode = self.im.mode + if frame_im.mode in ("LA", "RGBA"): + self.im.paste(frame_im, self.dispose_extent, frame_im) + else: + self.im.paste(frame_im, self.dispose_extent) + + def tell(self) -> int: + return self.__frame + + +# -------------------------------------------------------------------- +# Write GIF files + + +RAWMODE = {"1": "L", "L": "L", "P": "P"} + + +def _normalize_mode(im: Image.Image) -> Image.Image: + """ + Takes an image (or frame), returns an image in a mode that is appropriate + for saving in a Gif. + + It may return the original image, or it may return an image converted to + palette or 'L' mode. + + :param im: Image object + :returns: Image object + """ + if im.mode in RAWMODE: + im.load() + return im + if Image.getmodebase(im.mode) == "RGB": + im = im.convert("P", palette=Image.Palette.ADAPTIVE) + assert im.palette is not None + if im.palette.mode == "RGBA": + for rgba in im.palette.colors: + if rgba[3] == 0: + im.info["transparency"] = im.palette.colors[rgba] + break + return im + return im.convert("L") + + +_Palette = bytes | bytearray | list[int] | ImagePalette.ImagePalette + + +def _normalize_palette( + im: Image.Image, palette: _Palette | None, info: dict[str, Any] +) -> Image.Image: + """ + Normalizes the palette for image. + - Sets the palette to the incoming palette, if provided. + - Ensures that there's a palette for L mode images + - Optimizes the palette if necessary/desired. + + :param im: Image object + :param palette: bytes object containing the source palette, or .... + :param info: encoderinfo + :returns: Image object + """ + source_palette = None + if palette: + # a bytes palette + if isinstance(palette, (bytes, bytearray, list)): + source_palette = bytearray(palette[:768]) + if isinstance(palette, ImagePalette.ImagePalette): + source_palette = bytearray(palette.palette) + + if im.mode == "P": + if not source_palette: + im_palette = im.getpalette(None) + assert im_palette is not None + source_palette = bytearray(im_palette) + else: # L-mode + if not source_palette: + source_palette = bytearray(i // 3 for i in range(768)) + im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette) + assert source_palette is not None + + if palette: + used_palette_colors: list[int | None] = [] + assert im.palette is not None + for i in range(0, len(source_palette), 3): + source_color = tuple(source_palette[i : i + 3]) + index = im.palette.colors.get(source_color) + if index in used_palette_colors: + index = None + used_palette_colors.append(index) + for i, index in enumerate(used_palette_colors): + if index is None: + for j in range(len(used_palette_colors)): + if j not in used_palette_colors: + used_palette_colors[i] = j + break + dest_map: list[int] = [] + for index in used_palette_colors: + assert index is not None + dest_map.append(index) + im = im.remap_palette(dest_map) + else: + optimized_palette_colors = _get_optimize(im, info) + if optimized_palette_colors is not None: + im = im.remap_palette(optimized_palette_colors, source_palette) + if "transparency" in info: + try: + info["transparency"] = optimized_palette_colors.index( + info["transparency"] + ) + except ValueError: + del info["transparency"] + return im + + assert im.palette is not None + im.palette.palette = source_palette + return im + + +def _write_single_frame( + im: Image.Image, + fp: IO[bytes], + palette: _Palette | None, +) -> None: + im_out = _normalize_mode(im) + for k, v in im_out.info.items(): + if isinstance(k, str): + im.encoderinfo.setdefault(k, v) + im_out = _normalize_palette(im_out, palette, im.encoderinfo) + + for s in _get_global_header(im_out, im.encoderinfo): + fp.write(s) + + # local image header + flags = 0 + if get_interlace(im): + flags = flags | 64 + _write_local_header(fp, im, (0, 0), flags) + + im_out.encoderconfig = (8, get_interlace(im)) + ImageFile._save( + im_out, fp, [ImageFile._Tile("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])] + ) + + fp.write(b"\0") # end of image data + + +def _getbbox( + base_im: Image.Image, im_frame: Image.Image +) -> tuple[Image.Image, tuple[int, int, int, int] | None]: + palette_bytes = [ + bytes(im.palette.palette) if im.palette else b"" for im in (base_im, im_frame) + ] + if palette_bytes[0] != palette_bytes[1]: + im_frame = im_frame.convert("RGBA") + base_im = base_im.convert("RGBA") + delta = ImageChops.subtract_modulo(im_frame, base_im) + return delta, delta.getbbox(alpha_only=False) + + +class _Frame(NamedTuple): + im: Image.Image + bbox: tuple[int, int, int, int] | None + encoderinfo: dict[str, Any] + + +def _write_multiple_frames( + im: Image.Image, fp: IO[bytes], palette: _Palette | None +) -> bool: + duration = im.encoderinfo.get("duration") + disposal = im.encoderinfo.get("disposal", im.info.get("disposal")) + + im_frames: list[_Frame] = [] + previous_im: Image.Image | None = None + frame_count = 0 + background_im = None + for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])): + for im_frame in ImageSequence.Iterator(imSequence): + # a copy is required here since seek can still mutate the image + im_frame = _normalize_mode(im_frame.copy()) + if frame_count == 0: + for k, v in im_frame.info.items(): + if k == "transparency": + continue + if isinstance(k, str): + im.encoderinfo.setdefault(k, v) + + encoderinfo = im.encoderinfo.copy() + if "transparency" in im_frame.info: + encoderinfo.setdefault("transparency", im_frame.info["transparency"]) + im_frame = _normalize_palette(im_frame, palette, encoderinfo) + if isinstance(duration, (list, tuple)): + encoderinfo["duration"] = duration[frame_count] + elif duration is None and "duration" in im_frame.info: + encoderinfo["duration"] = im_frame.info["duration"] + if isinstance(disposal, (list, tuple)): + encoderinfo["disposal"] = disposal[frame_count] + frame_count += 1 + + diff_frame = None + if im_frames and previous_im: + # delta frame + delta, bbox = _getbbox(previous_im, im_frame) + if not bbox: + # This frame is identical to the previous frame + if encoderinfo.get("duration"): + im_frames[-1].encoderinfo["duration"] += encoderinfo["duration"] + continue + if im_frames[-1].encoderinfo.get("disposal") == 2: + # To appear correctly in viewers using a convention, + # only consider transparency, and not background color + color = im.encoderinfo.get( + "transparency", im.info.get("transparency") + ) + if color is not None: + if background_im is None: + background = _get_background(im_frame, color) + background_im = Image.new("P", im_frame.size, background) + first_palette = im_frames[0].im.palette + assert first_palette is not None + background_im.putpalette(first_palette, first_palette.mode) + bbox = _getbbox(background_im, im_frame)[1] + else: + bbox = (0, 0) + im_frame.size + elif encoderinfo.get("optimize") and im_frame.mode != "1": + if "transparency" not in encoderinfo: + assert im_frame.palette is not None + try: + encoderinfo["transparency"] = ( + im_frame.palette._new_color_index(im_frame) + ) + except ValueError: + pass + if "transparency" in encoderinfo: + # When the delta is zero, fill the image with transparency + diff_frame = im_frame.copy() + fill = Image.new("P", delta.size, encoderinfo["transparency"]) + if delta.mode == "RGBA": + r, g, b, a = delta.split() + mask = ImageMath.lambda_eval( + lambda args: args["convert"]( + args["max"]( + args["max"]( + args["max"](args["r"], args["g"]), args["b"] + ), + args["a"], + ) + * 255, + "1", + ), + r=r, + g=g, + b=b, + a=a, + ) + else: + if delta.mode == "P": + # Convert to L without considering palette + delta_l = Image.new("L", delta.size) + delta_l.putdata(delta.get_flattened_data()) + delta = delta_l + mask = ImageMath.lambda_eval( + lambda args: args["convert"](args["im"] * 255, "1"), + im=delta, + ) + diff_frame.paste(fill, mask=ImageOps.invert(mask)) + else: + bbox = None + previous_im = im_frame + im_frames.append(_Frame(diff_frame or im_frame, bbox, encoderinfo)) + + if len(im_frames) == 1: + if "duration" in im.encoderinfo: + # Since multiple frames will not be written, use the combined duration + im.encoderinfo["duration"] = im_frames[0].encoderinfo["duration"] + return False + + for frame_data in im_frames: + im_frame = frame_data.im + if not frame_data.bbox: + # global header + for s in _get_global_header(im_frame, frame_data.encoderinfo): + fp.write(s) + offset = (0, 0) + else: + # compress difference + if not palette: + frame_data.encoderinfo["include_color_table"] = True + + if frame_data.bbox != (0, 0) + im_frame.size: + im_frame = im_frame.crop(frame_data.bbox) + offset = frame_data.bbox[:2] + _write_frame_data(fp, im_frame, offset, frame_data.encoderinfo) + return True + + +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + _save(im, fp, filename, save_all=True) + + +def _save( + im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False +) -> None: + # header + if "palette" in im.encoderinfo or "palette" in im.info: + palette = im.encoderinfo.get("palette", im.info.get("palette")) + else: + palette = None + im.encoderinfo.setdefault("optimize", True) + + if not save_all or not _write_multiple_frames(im, fp, palette): + _write_single_frame(im, fp, palette) + + fp.write(b";") # end of file + + if hasattr(fp, "flush"): + fp.flush() + + +def get_interlace(im: Image.Image) -> int: + interlace = im.encoderinfo.get("interlace", 1) + + # workaround for @PIL153 + if min(im.size) < 16: + interlace = 0 + + return interlace + + +def _write_local_header( + fp: IO[bytes], im: Image.Image, offset: tuple[int, int], flags: int +) -> None: + try: + transparency = im.encoderinfo["transparency"] + except KeyError: + transparency = None + + if "duration" in im.encoderinfo: + duration = int(im.encoderinfo["duration"] / 10) + else: + duration = 0 + + disposal = int(im.encoderinfo.get("disposal", 0)) + + if transparency is not None or duration != 0 or disposal: + packed_flag = 1 if transparency is not None else 0 + packed_flag |= disposal << 2 + + fp.write( + b"!" + + o8(249) # extension intro + + o8(4) # length + + o8(packed_flag) # packed fields + + o16(duration) # duration + + o8(transparency or 0) # transparency index + + o8(0) + ) + + include_color_table = im.encoderinfo.get("include_color_table") + if include_color_table: + palette_bytes = _get_palette_bytes(im) + color_table_size = _get_color_table_size(palette_bytes) + if color_table_size: + flags = flags | 128 # local color table flag + flags = flags | color_table_size + + fp.write( + b"," + + o16(offset[0]) # offset + + o16(offset[1]) + + o16(im.size[0]) # size + + o16(im.size[1]) + + o8(flags) # flags + ) + if include_color_table and color_table_size: + fp.write(_get_header_palette(palette_bytes)) + fp.write(o8(8)) # bits + + +def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + # Unused by default. + # To use, uncomment the register_save call at the end of the file. + # + # If you need real GIF compression and/or RGB quantization, you + # can use the external NETPBM/PBMPLUS utilities. See comments + # below for information on how to enable this. + tempfile = im._dump() + + try: + with open(filename, "wb") as f: + if im.mode != "RGB": + subprocess.check_call( + ["ppmtogif", tempfile], stdout=f, stderr=subprocess.DEVNULL + ) + else: + # Pipe ppmquant output into ppmtogif + # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename) + quant_cmd = ["ppmquant", "256", tempfile] + togif_cmd = ["ppmtogif"] + quant_proc = subprocess.Popen( + quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + ) + togif_proc = subprocess.Popen( + togif_cmd, + stdin=quant_proc.stdout, + stdout=f, + stderr=subprocess.DEVNULL, + ) + + # Allow ppmquant to receive SIGPIPE if ppmtogif exits + assert quant_proc.stdout is not None + quant_proc.stdout.close() + + retcode = quant_proc.wait() + if retcode: + raise subprocess.CalledProcessError(retcode, quant_cmd) + + retcode = togif_proc.wait() + if retcode: + raise subprocess.CalledProcessError(retcode, togif_cmd) + finally: + try: + os.unlink(tempfile) + except OSError: + pass + + +# Force optimization so that we can test performance against +# cases where it took lots of memory and time previously. +_FORCE_OPTIMIZE = False + + +def _get_optimize(im: Image.Image, info: dict[str, Any]) -> list[int] | None: + """ + Palette optimization is a potentially expensive operation. + + This function determines if the palette should be optimized using + some heuristics, then returns the list of palette entries in use. + + :param im: Image object + :param info: encoderinfo + :returns: list of indexes of palette entries in use, or None + """ + if im.mode in ("P", "L") and info and info.get("optimize"): + # Potentially expensive operation. + + # The palette saves 3 bytes per color not used, but palette + # lengths are restricted to 3*(2**N) bytes. Max saving would + # be 768 -> 6 bytes if we went all the way down to 2 colors. + # * If we're over 128 colors, we can't save any space. + # * If there aren't any holes, it's not worth collapsing. + # * If we have a 'large' image, the palette is in the noise. + + # create the new palette if not every color is used + optimise = _FORCE_OPTIMIZE or im.mode == "L" + if optimise or im.width * im.height < 512 * 512: + # check which colors are used + used_palette_colors = [] + for i, count in enumerate(im.histogram()): + if count: + used_palette_colors.append(i) + + if optimise or max(used_palette_colors) >= len(used_palette_colors): + return used_palette_colors + + assert im.palette is not None + num_palette_colors = len(im.palette.palette) // Image.getmodebands( + im.palette.mode + ) + current_palette_size = 1 << (num_palette_colors - 1).bit_length() + if ( + # check that the palette would become smaller when saved + len(used_palette_colors) <= current_palette_size // 2 + # check that the palette is not already the smallest possible size + and current_palette_size > 2 + ): + return used_palette_colors + return None + + +def _get_color_table_size(palette_bytes: bytes) -> int: + # calculate the palette size for the header + if not palette_bytes: + return 0 + elif len(palette_bytes) < 9: + return 1 + else: + return math.ceil(math.log(len(palette_bytes) // 3, 2)) - 1 + + +def _get_header_palette(palette_bytes: bytes) -> bytes: + """ + Returns the palette, null padded to the next power of 2 (*3) bytes + suitable for direct inclusion in the GIF header + + :param palette_bytes: Unpadded palette bytes, in RGBRGB form + :returns: Null padded palette + """ + color_table_size = _get_color_table_size(palette_bytes) + + # add the missing amount of bytes + # the palette has to be 2< 0: + palette_bytes += o8(0) * 3 * actual_target_size_diff + return palette_bytes + + +def _get_palette_bytes(im: Image.Image) -> bytes: + """ + Gets the palette for inclusion in the gif header + + :param im: Image object + :returns: Bytes, len<=768 suitable for inclusion in gif header + """ + if not im.palette: + return b"" + + palette = bytes(im.palette.palette) + if im.palette.mode == "RGBA": + palette = b"".join(palette[i * 4 : i * 4 + 3] for i in range(len(palette) // 3)) + return palette + + +def _get_background( + im: Image.Image, + info_background: int | tuple[int, int, int] | tuple[int, int, int, int] | None, +) -> int: + background = 0 + if info_background: + if isinstance(info_background, tuple): + # WebPImagePlugin stores an RGBA value in info["background"] + # So it must be converted to the same format as GifImagePlugin's + # info["background"] - a global color table index + assert im.palette is not None + try: + background = im.palette.getcolor(info_background, im) + except ValueError as e: + if str(e) not in ( + # If all 256 colors are in use, + # then there is no need for the background color + "cannot allocate more than 256 colors", + # Ignore non-opaque WebP background + "cannot add non-opaque RGBA color to RGB palette", + ): + raise + else: + background = info_background + return background + + +def _get_global_header(im: Image.Image, info: dict[str, Any]) -> list[bytes]: + """Return a list of strings representing a GIF header""" + + # Header Block + # https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp + + version = b"87a" + if im.info.get("version") == b"89a" or ( + info + and ( + "transparency" in info + or info.get("loop") is not None + or info.get("duration") + or info.get("comment") + ) + ): + version = b"89a" + + background = _get_background(im, info.get("background")) + + palette_bytes = _get_palette_bytes(im) + color_table_size = _get_color_table_size(palette_bytes) + + header = [ + b"GIF" # signature + + version # version + + o16(im.size[0]) # canvas width + + o16(im.size[1]), # canvas height + # Logical Screen Descriptor + # size of global color table + global color table flag + o8(color_table_size + 128), # packed fields + # background + reserved/aspect + o8(background) + o8(0), + # Global Color Table + _get_header_palette(palette_bytes), + ] + if info.get("loop") is not None: + header.append( + b"!" + + o8(255) # extension intro + + o8(11) + + b"NETSCAPE2.0" + + o8(3) + + o8(1) + + o16(info["loop"]) # number of loops + + o8(0) + ) + if info.get("comment"): + comment_block = b"!" + o8(254) # extension intro + + comment = info["comment"] + if isinstance(comment, str): + comment = comment.encode() + for i in range(0, len(comment), 255): + subblock = comment[i : i + 255] + comment_block += o8(len(subblock)) + subblock + + comment_block += o8(0) + header.append(comment_block) + return header + + +def _write_frame_data( + fp: IO[bytes], + im_frame: Image.Image, + offset: tuple[int, int], + params: dict[str, Any], +) -> None: + try: + im_frame.encoderinfo = params + + # local image header + _write_local_header(fp, im_frame, offset, 0) + + ImageFile._save( + im_frame, + fp, + [ImageFile._Tile("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])], + ) + + fp.write(b"\0") # end of image data + finally: + del im_frame.encoderinfo + + +# -------------------------------------------------------------------- +# Legacy GIF utilities + + +def getheader( + im: Image.Image, palette: _Palette | None = None, info: dict[str, Any] | None = None +) -> tuple[list[bytes], list[int] | None]: + """ + Legacy Method to get Gif data from image. + + Warning:: May modify image data. + + :param im: Image object + :param palette: bytes object containing the source palette, or .... + :param info: encoderinfo + :returns: tuple of(list of header items, optimized palette) + + """ + if info is None: + info = {} + + used_palette_colors = _get_optimize(im, info) + + if "background" not in info and "background" in im.info: + info["background"] = im.info["background"] + + im_mod = _normalize_palette(im, palette, info) + im.palette = im_mod.palette + im.im = im_mod.im + header = _get_global_header(im, info) + + return header, used_palette_colors + + +def getdata( + im: Image.Image, offset: tuple[int, int] = (0, 0), **params: Any +) -> list[bytes]: + """ + Legacy Method + + Return a list of strings representing this image. + The first string is a local image header, the rest contains + encoded image data. + + To specify duration, add the time in milliseconds, + e.g. ``getdata(im_frame, duration=1000)`` + + :param im: Image object + :param offset: Tuple of (x, y) pixels. Defaults to (0, 0) + :param \\**params: e.g. duration or other encoder info parameters + :returns: List of bytes containing GIF encoded frame data + + """ + from io import BytesIO + + class Collector(BytesIO): + data = [] + + def write(self, data: Buffer) -> int: + self.data.append(data) + return len(data) + + im.load() # make sure raster data is available + + fp = Collector() + + _write_frame_data(fp, im, offset, params) + + return fp.data + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(GifImageFile.format, GifImageFile, _accept) +Image.register_save(GifImageFile.format, _save) +Image.register_save_all(GifImageFile.format, _save_all) +Image.register_extension(GifImageFile.format, ".gif") +Image.register_mime(GifImageFile.format, "image/gif") + +# +# Uncomment the following line if you wish to use NETPBM/PBMPLUS +# instead of the built-in "uncompressed" GIF encoder + +# Image.register_save(GifImageFile.format, _save_netpbm) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GimpGradientFile.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GimpGradientFile.py new file mode 100644 index 0000000000000000000000000000000000000000..5f2691882c46130fc2f83c45f01db34e6ce1efe6 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GimpGradientFile.py @@ -0,0 +1,153 @@ +# +# Python Imaging Library +# $Id$ +# +# stuff to read (and render) GIMP gradient files +# +# History: +# 97-08-23 fl Created +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1997. +# +# See the README file for information on usage and redistribution. +# + +""" +Stuff to translate curve segments to palette values (derived from +the corresponding code in GIMP, written by Federico Mena Quintero. +See the GIMP distribution for more information.) +""" +from __future__ import annotations + +from math import log, pi, sin, sqrt + +from ._binary import o8 + +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + from typing import IO + +EPSILON = 1e-10 +"""""" # Enable auto-doc for data member + + +def linear(middle: float, pos: float) -> float: + if pos <= middle: + if middle < EPSILON: + return 0.0 + else: + return 0.5 * pos / middle + else: + pos = pos - middle + middle = 1.0 - middle + if middle < EPSILON: + return 1.0 + else: + return 0.5 + 0.5 * pos / middle + + +def curved(middle: float, pos: float) -> float: + return pos ** (log(0.5) / log(max(middle, EPSILON))) + + +def sine(middle: float, pos: float) -> float: + return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0 + + +def sphere_increasing(middle: float, pos: float) -> float: + return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2) + + +def sphere_decreasing(middle: float, pos: float) -> float: + return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2) + + +SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing] +"""""" # Enable auto-doc for data member + + +class GradientFile: + gradient: ( + list[ + tuple[ + float, + float, + float, + list[float], + list[float], + Callable[[float, float], float], + ] + ] + | None + ) = None + + def getpalette(self, entries: int = 256) -> tuple[bytes, str]: + assert self.gradient is not None + palette = [] + + ix = 0 + x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix] + + for i in range(entries): + x = i / (entries - 1) + + while x1 < x: + ix += 1 + x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix] + + w = x1 - x0 + + if w < EPSILON: + scale = segment(0.5, 0.5) + else: + scale = segment((xm - x0) / w, (x - x0) / w) + + # expand to RGBA + r = o8(int(255 * ((rgb1[0] - rgb0[0]) * scale + rgb0[0]) + 0.5)) + g = o8(int(255 * ((rgb1[1] - rgb0[1]) * scale + rgb0[1]) + 0.5)) + b = o8(int(255 * ((rgb1[2] - rgb0[2]) * scale + rgb0[2]) + 0.5)) + a = o8(int(255 * ((rgb1[3] - rgb0[3]) * scale + rgb0[3]) + 0.5)) + + # add to palette + palette.append(r + g + b + a) + + return b"".join(palette), "RGBA" + + +class GimpGradientFile(GradientFile): + """File handler for GIMP's gradient format.""" + + def __init__(self, fp: IO[bytes]) -> None: + if not fp.readline().startswith(b"GIMP Gradient"): + msg = "not a GIMP gradient file" + raise SyntaxError(msg) + + line = fp.readline() + + # GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do + if line.startswith(b"Name: "): + line = fp.readline().strip() + + count = int(line) + + self.gradient = [] + + for i in range(count): + s = fp.readline().split() + w = [float(x) for x in s[:11]] + + x0, x1 = w[0], w[2] + xm = w[1] + rgb0 = w[3:7] + rgb1 = w[7:11] + + segment = SEGMENTS[int(s[11])] + cspace = int(s[12]) + + if cspace != 0: + msg = "cannot handle HSV colour space" + raise OSError(msg) + + self.gradient.append((x0, x1, xm, rgb0, rgb1, segment)) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GimpPaletteFile.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GimpPaletteFile.py new file mode 100644 index 0000000000000000000000000000000000000000..016257d3dd29e83ed8d68f89363965dc39b93811 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GimpPaletteFile.py @@ -0,0 +1,75 @@ +# +# Python Imaging Library +# $Id$ +# +# stuff to read GIMP palette files +# +# History: +# 1997-08-23 fl Created +# 2004-09-07 fl Support GIMP 2.0 palette files. +# +# Copyright (c) Secret Labs AB 1997-2004. All rights reserved. +# Copyright (c) Fredrik Lundh 1997-2004. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import re +from io import BytesIO + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import IO + + +class GimpPaletteFile: + """File handler for GIMP's palette format.""" + + rawmode = "RGB" + + def _read(self, fp: IO[bytes], limit: bool = True) -> None: + if not fp.readline().startswith(b"GIMP Palette"): + msg = "not a GIMP palette file" + raise SyntaxError(msg) + + palette: list[int] = [] + i = 0 + while True: + if limit and i == 256 + 3: + break + + i += 1 + s = fp.readline() + if not s: + break + + # skip fields and comment lines + if re.match(rb"\w+:|#", s): + continue + if limit and len(s) > 100: + msg = "bad palette file" + raise SyntaxError(msg) + + v = s.split(maxsplit=3) + if len(v) < 3: + msg = "bad palette entry" + raise ValueError(msg) + + palette += (int(v[i]) for i in range(3)) + if limit and len(palette) == 768: + break + + self.palette = bytes(palette) + + def __init__(self, fp: IO[bytes]) -> None: + self._read(fp) + + @classmethod + def frombytes(cls, data: bytes) -> GimpPaletteFile: + self = cls.__new__(cls) + self._read(BytesIO(data), False) + return self + + def getpalette(self) -> tuple[bytes, str]: + return self.palette, self.rawmode diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GribStubImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GribStubImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..146a6fa0df0d886b13e4b7ec5ae0689e89504f7e --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/GribStubImagePlugin.py @@ -0,0 +1,76 @@ +# +# The Python Imaging Library +# $Id$ +# +# GRIB stub adapter +# +# Copyright (c) 1996-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +from typing import IO + +from . import Image, ImageFile + +_handler = None + + +def register_handler(handler: ImageFile.StubHandler | None) -> None: + """ + Install application-specific GRIB image handler. + + :param handler: Handler object. + """ + global _handler + _handler = handler + + +# -------------------------------------------------------------------- +# Image adapter + + +def _accept(prefix: bytes) -> bool: + return len(prefix) >= 8 and prefix.startswith(b"GRIB") and prefix[7] == 1 + + +class GribStubImageFile(ImageFile.StubImageFile): + format = "GRIB" + format_description = "GRIB" + + def _open(self) -> None: + assert self.fp is not None + if not _accept(self.fp.read(8)): + msg = "Not a GRIB file" + raise SyntaxError(msg) + + self.fp.seek(-8, os.SEEK_CUR) + + # make something up + self._mode = "F" + self._size = 1, 1 + + loader = self._load() + if loader: + loader.open(self) + + def _load(self) -> ImageFile.StubHandler | None: + return _handler + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if _handler is None or not hasattr(_handler, "save"): + msg = "GRIB save handler not installed" + raise OSError(msg) + _handler.save(im, fp, filename) + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(GribStubImageFile.format, GribStubImageFile, _accept) +Image.register_save(GribStubImageFile.format, _save) + +Image.register_extension(GribStubImageFile.format, ".grib") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/Hdf5StubImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/Hdf5StubImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..1523e95d58c0f1fad893a2ea56312237c45e6117 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/Hdf5StubImagePlugin.py @@ -0,0 +1,76 @@ +# +# The Python Imaging Library +# $Id$ +# +# HDF5 stub adapter +# +# Copyright (c) 2000-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +from typing import IO + +from . import Image, ImageFile + +_handler = None + + +def register_handler(handler: ImageFile.StubHandler | None) -> None: + """ + Install application-specific HDF5 image handler. + + :param handler: Handler object. + """ + global _handler + _handler = handler + + +# -------------------------------------------------------------------- +# Image adapter + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"\x89HDF\r\n\x1a\n") + + +class HDF5StubImageFile(ImageFile.StubImageFile): + format = "HDF5" + format_description = "HDF5" + + def _open(self) -> None: + assert self.fp is not None + if not _accept(self.fp.read(8)): + msg = "Not an HDF file" + raise SyntaxError(msg) + + self.fp.seek(-8, os.SEEK_CUR) + + # make something up + self._mode = "F" + self._size = 1, 1 + + loader = self._load() + if loader: + loader.open(self) + + def _load(self) -> ImageFile.StubHandler | None: + return _handler + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if _handler is None or not hasattr(_handler, "save"): + msg = "HDF5 save handler not installed" + raise OSError(msg) + _handler.save(im, fp, filename) + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(HDF5StubImageFile.format, HDF5StubImageFile, _accept) +Image.register_save(HDF5StubImageFile.format, _save) + +Image.register_extensions(HDF5StubImageFile.format, [".h5", ".hdf"]) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/IcnsImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/IcnsImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..058861d67e511324de620f283d62fd5c1f2dbfeb --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/IcnsImagePlugin.py @@ -0,0 +1,402 @@ +# +# The Python Imaging Library. +# $Id$ +# +# macOS icns file decoder, based on icns.py by Bob Ippolito. +# +# history: +# 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies. +# 2020-04-04 Allow saving on all operating systems. +# +# Copyright (c) 2004 by Bob Ippolito. +# Copyright (c) 2004 by Secret Labs. +# Copyright (c) 2004 by Fredrik Lundh. +# Copyright (c) 2014 by Alastair Houghton. +# Copyright (c) 2020 by Pan Jing. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +import os +import struct +import sys +from typing import IO + +from . import Image, ImageFile, PngImagePlugin, features + +enable_jpeg2k = features.check_codec("jpg_2000") +if enable_jpeg2k: + from . import Jpeg2KImagePlugin + +MAGIC = b"icns" +HEADERSIZE = 8 + + +def nextheader(fobj: IO[bytes]) -> tuple[bytes, int]: + return struct.unpack(">4sI", fobj.read(HEADERSIZE)) + + +def read_32t( + fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int] +) -> dict[str, Image.Image]: + # The 128x128 icon seems to have an extra header for some reason. + (start, length) = start_length + fobj.seek(start) + sig = fobj.read(4) + if sig != b"\x00\x00\x00\x00": + msg = "Unknown signature, expecting 0x00000000" + raise SyntaxError(msg) + return read_32(fobj, (start + 4, length - 4), size) + + +def read_32( + fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int] +) -> dict[str, Image.Image]: + """ + Read a 32bit RGB icon resource. Seems to be either uncompressed or + an RLE packbits-like scheme. + """ + (start, length) = start_length + fobj.seek(start) + pixel_size = (size[0] * size[2], size[1] * size[2]) + sizesq = pixel_size[0] * pixel_size[1] + if length == sizesq * 3: + # uncompressed ("RGBRGBGB") + indata = fobj.read(length) + im = Image.frombuffer("RGB", pixel_size, indata, "raw", "RGB", 0, 1) + else: + # decode image + im = Image.new("RGB", pixel_size, None) + for band_ix in range(3): + data = [] + bytesleft = sizesq + while bytesleft > 0: + byte = fobj.read(1) + if not byte: + break + byte_int = byte[0] + if byte_int & 0x80: + blocksize = byte_int - 125 + byte = fobj.read(1) + for i in range(blocksize): + data.append(byte) + else: + blocksize = byte_int + 1 + data.append(fobj.read(blocksize)) + bytesleft -= blocksize + if bytesleft <= 0: + break + if bytesleft != 0: + msg = f"Error reading channel [{repr(bytesleft)} left]" + raise SyntaxError(msg) + band = Image.frombuffer("L", pixel_size, b"".join(data), "raw", "L", 0, 1) + im.im.putband(band.im, band_ix) + return {"RGB": im} + + +def read_mk( + fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int] +) -> dict[str, Image.Image]: + # Alpha masks seem to be uncompressed + start = start_length[0] + fobj.seek(start) + pixel_size = (size[0] * size[2], size[1] * size[2]) + sizesq = pixel_size[0] * pixel_size[1] + band = Image.frombuffer("L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1) + return {"A": band} + + +def read_png_or_jpeg2000( + fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int] +) -> dict[str, Image.Image]: + (start, length) = start_length + fobj.seek(start) + sig = fobj.read(12) + + im: Image.Image + if sig.startswith(b"\x89PNG\x0d\x0a\x1a\x0a"): + fobj.seek(start) + im = PngImagePlugin.PngImageFile(fobj) + Image._decompression_bomb_check(im.size) + return {"RGBA": im} + elif ( + sig.startswith((b"\xff\x4f\xff\x51", b"\x0d\x0a\x87\x0a")) + or sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a" + ): + if not enable_jpeg2k: + msg = ( + "Unsupported icon subimage format (rebuild PIL " + "with JPEG 2000 support to fix this)" + ) + raise ValueError(msg) + # j2k, jpc or j2c + fobj.seek(start) + jp2kstream = fobj.read(length) + f = io.BytesIO(jp2kstream) + im = Jpeg2KImagePlugin.Jpeg2KImageFile(f) + Image._decompression_bomb_check(im.size) + if im.mode != "RGBA": + im = im.convert("RGBA") + return {"RGBA": im} + else: + msg = "Unsupported icon subimage format" + raise ValueError(msg) + + +class IcnsFile: + SIZES = { + (512, 512, 2): [(b"ic10", read_png_or_jpeg2000)], + (512, 512, 1): [(b"ic09", read_png_or_jpeg2000)], + (256, 256, 2): [(b"ic14", read_png_or_jpeg2000)], + (256, 256, 1): [(b"ic08", read_png_or_jpeg2000)], + (128, 128, 2): [(b"ic13", read_png_or_jpeg2000)], + (128, 128, 1): [ + (b"ic07", read_png_or_jpeg2000), + (b"it32", read_32t), + (b"t8mk", read_mk), + ], + (64, 64, 1): [(b"icp6", read_png_or_jpeg2000)], + (32, 32, 2): [(b"ic12", read_png_or_jpeg2000)], + (48, 48, 1): [(b"ih32", read_32), (b"h8mk", read_mk)], + (32, 32, 1): [ + (b"icp5", read_png_or_jpeg2000), + (b"il32", read_32), + (b"l8mk", read_mk), + ], + (16, 16, 2): [(b"ic11", read_png_or_jpeg2000)], + (16, 16, 1): [ + (b"icp4", read_png_or_jpeg2000), + (b"is32", read_32), + (b"s8mk", read_mk), + ], + } + + def __init__(self, fobj: IO[bytes]) -> None: + """ + fobj is a file-like object as an icns resource + """ + # signature : (start, length) + self.dct = {} + self.fobj = fobj + sig, filesize = nextheader(fobj) + if not _accept(sig): + msg = "not an icns file" + raise SyntaxError(msg) + i = HEADERSIZE + while i < filesize: + sig, blocksize = nextheader(fobj) + if blocksize <= 0: + msg = "invalid block header" + raise SyntaxError(msg) + i += HEADERSIZE + blocksize -= HEADERSIZE + self.dct[sig] = (i, blocksize) + fobj.seek(blocksize, io.SEEK_CUR) + i += blocksize + + def itersizes(self) -> list[tuple[int, int, int]]: + sizes = [] + for size, fmts in self.SIZES.items(): + for fmt, reader in fmts: + if fmt in self.dct: + sizes.append(size) + break + return sizes + + def bestsize(self) -> tuple[int, int, int]: + sizes = self.itersizes() + if not sizes: + msg = "No 32bit icon resources found" + raise SyntaxError(msg) + return max(sizes) + + def dataforsize(self, size: tuple[int, int, int]) -> dict[str, Image.Image]: + """ + Get an icon resource as {channel: array}. Note that + the arrays are bottom-up like windows bitmaps and will likely + need to be flipped or transposed in some way. + """ + dct = {} + for code, reader in self.SIZES[size]: + desc = self.dct.get(code) + if desc is not None: + dct.update(reader(self.fobj, desc, size)) + return dct + + def getimage( + self, size: tuple[int, int] | tuple[int, int, int] | None = None + ) -> Image.Image: + if size is None: + size = self.bestsize() + elif len(size) == 2: + size = (size[0], size[1], 1) + channels = self.dataforsize(size) + + im = channels.get("RGBA") + if im: + return im + + im = channels["RGB"].copy() + try: + im.putalpha(channels["A"]) + except KeyError: + pass + return im + + +## +# Image plugin for Mac OS icons. + + +class IcnsImageFile(ImageFile.ImageFile): + """ + PIL image support for Mac OS .icns files. + Chooses the best resolution, but will possibly load + a different size image if you mutate the size attribute + before calling 'load'. + + The info dictionary has a key 'sizes' that is a list + of sizes that the icns file has. + """ + + format = "ICNS" + format_description = "Mac OS icns resource" + + def _open(self) -> None: + assert self.fp is not None + self.icns = IcnsFile(self.fp) + self._mode = "RGBA" + self.info["sizes"] = self.icns.itersizes() + self.best_size = self.icns.bestsize() + self.size = ( + self.best_size[0] * self.best_size[2], + self.best_size[1] * self.best_size[2], + ) + + @property + def size(self) -> tuple[int, int]: + return self._size + + @size.setter + def size(self, value: tuple[int, int]) -> None: + # Check that a matching size exists, + # or that there is a scale that would create a size that matches + for size in self.info["sizes"]: + simple_size = size[0] * size[2], size[1] * size[2] + scale = simple_size[0] // value[0] + if simple_size[1] / value[1] == scale: + self._size = value + return + msg = "This is not one of the allowed sizes of this image" + raise ValueError(msg) + + def load(self, scale: int | None = None) -> Image.core.PixelAccess | None: + if scale is not None: + width, height = self.size[:2] + self.size = width * scale, height * scale + self.best_size = width, height, scale + + px = Image.Image.load(self) + if self._im is not None and self.im.size == self.size: + # Already loaded + return px + self.load_prepare() + # This is likely NOT the best way to do it, but whatever. + im = self.icns.getimage(self.best_size) + + # If this is a PNG or JPEG 2000, it won't be loaded yet + px = im.load() + + self.im = im.im + self._mode = im.mode + self.size = im.size + + return px + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + """ + Saves the image as a series of PNG files, + that are then combined into a .icns file. + """ + if hasattr(fp, "flush"): + fp.flush() + + sizes = { + b"ic07": 128, + b"ic08": 256, + b"ic09": 512, + b"ic10": 1024, + b"ic11": 32, + b"ic12": 64, + b"ic13": 256, + b"ic14": 512, + } + provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])} + size_streams = {} + for size in set(sizes.values()): + image = ( + provided_images[size] + if size in provided_images + else im.resize((size, size)) + ) + + temp = io.BytesIO() + image.save(temp, "png") + size_streams[size] = temp.getvalue() + + entries = [] + for type, size in sizes.items(): + stream = size_streams[size] + entries.append((type, HEADERSIZE + len(stream), stream)) + + # Header + fp.write(MAGIC) + file_length = HEADERSIZE # Header + file_length += HEADERSIZE + 8 * len(entries) # TOC + file_length += sum(entry[1] for entry in entries) + fp.write(struct.pack(">i", file_length)) + + # TOC + fp.write(b"TOC ") + fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE)) + for entry in entries: + fp.write(entry[0]) + fp.write(struct.pack(">i", entry[1])) + + # Data + for entry in entries: + fp.write(entry[0]) + fp.write(struct.pack(">i", entry[1])) + fp.write(entry[2]) + + if hasattr(fp, "flush"): + fp.flush() + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(MAGIC) + + +Image.register_open(IcnsImageFile.format, IcnsImageFile, _accept) +Image.register_extension(IcnsImageFile.format, ".icns") + +Image.register_save(IcnsImageFile.format, _save) +Image.register_mime(IcnsImageFile.format, "image/icns") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Syntax: python3 IcnsImagePlugin.py [file]") + sys.exit() + + with open(sys.argv[1], "rb") as fp: + imf = IcnsImageFile(fp) + for size in imf.info["sizes"]: + width, height, scale = imf.size = size + imf.save(f"out-{width}-{height}-{scale}.png") + with Image.open(sys.argv[1]) as im: + im.save("out.png") + if sys.platform == "windows": + os.startfile("out.png") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/IcoImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/IcoImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..8dd57ff858ab9aca62434699d9fef8ae956a4867 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/IcoImagePlugin.py @@ -0,0 +1,396 @@ +# +# The Python Imaging Library. +# $Id$ +# +# Windows Icon support for PIL +# +# History: +# 96-05-27 fl Created +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1996. +# +# See the README file for information on usage and redistribution. +# + +# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis +# . +# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki +# +# Copyright 2008 Bryan Davis +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Icon format references: +# * https://en.wikipedia.org/wiki/ICO_(file_format) +# * https://msdn.microsoft.com/en-us/library/ms997538.aspx +from __future__ import annotations + +import warnings +from io import BytesIO +from math import ceil, log +from typing import IO, NamedTuple + +from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin +from ._binary import i16le as i16 +from ._binary import i32le as i32 +from ._binary import o8 +from ._binary import o16le as o16 +from ._binary import o32le as o32 + +# +# -------------------------------------------------------------------- + +_MAGIC = b"\0\0\1\0" + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + fp.write(_MAGIC) # (2+2) + bmp = im.encoderinfo.get("bitmap_format") == "bmp" + sizes = im.encoderinfo.get( + "sizes", + [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)], + ) + frames = [] + provided_ims = [im] + im.encoderinfo.get("append_images", []) + width, height = im.size + for size in sorted(set(sizes)): + if size[0] > width or size[1] > height or size[0] > 256 or size[1] > 256: + continue + + for provided_im in provided_ims: + if provided_im.size != size: + continue + frames.append(provided_im) + if bmp: + bits = BmpImagePlugin.SAVE[provided_im.mode][1] + bits_used = [bits] + for other_im in provided_ims: + if other_im.size != size: + continue + bits = BmpImagePlugin.SAVE[other_im.mode][1] + if bits not in bits_used: + # Another image has been supplied for this size + # with a different bit depth + frames.append(other_im) + bits_used.append(bits) + break + else: + # TODO: invent a more convenient method for proportional scalings + frame = provided_im.copy() + frame.thumbnail(size, Image.Resampling.LANCZOS, reducing_gap=None) + frames.append(frame) + fp.write(o16(len(frames))) # idCount(2) + offset = fp.tell() + len(frames) * 16 + for frame in frames: + width, height = frame.size + # 0 means 256 + fp.write(o8(width if width < 256 else 0)) # bWidth(1) + fp.write(o8(height if height < 256 else 0)) # bHeight(1) + + bits, colors = BmpImagePlugin.SAVE[frame.mode][1:] if bmp else (32, 0) + fp.write(o8(colors)) # bColorCount(1) + fp.write(b"\0") # bReserved(1) + fp.write(b"\0\0") # wPlanes(2) + fp.write(o16(bits)) # wBitCount(2) + + image_io = BytesIO() + if bmp: + frame.save(image_io, "dib") + + if bits != 32: + and_mask = Image.new("1", size) + ImageFile._save( + and_mask, + image_io, + [ImageFile._Tile("raw", (0, 0) + size, 0, ("1", 0, -1))], + ) + else: + frame.save(image_io, "png") + image_io.seek(0) + image_bytes = image_io.read() + if bmp: + image_bytes = image_bytes[:8] + o32(height * 2) + image_bytes[12:] + bytes_len = len(image_bytes) + fp.write(o32(bytes_len)) # dwBytesInRes(4) + fp.write(o32(offset)) # dwImageOffset(4) + current = fp.tell() + fp.seek(offset) + fp.write(image_bytes) + offset = offset + bytes_len + fp.seek(current) + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(_MAGIC) + + +class IconHeader(NamedTuple): + width: int + height: int + nb_color: int + reserved: int + planes: int + bpp: int + size: int + offset: int + dim: tuple[int, int] + square: int + color_depth: int + + +class IcoFile: + def __init__(self, buf: IO[bytes]) -> None: + """ + Parse image from file-like object containing ico file data + """ + + # check magic + s = buf.read(6) + if not _accept(s): + msg = "not an ICO file" + raise SyntaxError(msg) + + self.buf = buf + self.entry = [] + + # Number of items in file + self.nb_items = i16(s, 4) + + # Get headers for each item + for i in range(self.nb_items): + s = buf.read(16) + + # See Wikipedia + width = s[0] or 256 + height = s[1] or 256 + + # No. of colors in image (0 if >=8bpp) + nb_color = s[2] + bpp = i16(s, 6) + icon_header = IconHeader( + width=width, + height=height, + nb_color=nb_color, + reserved=s[3], + planes=i16(s, 4), + bpp=i16(s, 6), + size=i32(s, 8), + offset=i32(s, 12), + dim=(width, height), + square=width * height, + # See Wikipedia notes about color depth. + # We need this just to differ images with equal sizes + color_depth=bpp or (nb_color != 0 and ceil(log(nb_color, 2))) or 256, + ) + + self.entry.append(icon_header) + + self.entry = sorted(self.entry, key=lambda x: x.color_depth) + # ICO images are usually squares + self.entry = sorted(self.entry, key=lambda x: x.square, reverse=True) + + def sizes(self) -> set[tuple[int, int]]: + """ + Get a set of all available icon sizes and color depths. + """ + return {(h.width, h.height) for h in self.entry} + + def getentryindex(self, size: tuple[int, int], bpp: int | bool = False) -> int: + for i, h in enumerate(self.entry): + if size == h.dim and (bpp is False or bpp == h.color_depth): + return i + return 0 + + def getimage(self, size: tuple[int, int], bpp: int | bool = False) -> Image.Image: + """ + Get an image from the icon + """ + return self.frame(self.getentryindex(size, bpp)) + + def frame(self, idx: int) -> Image.Image: + """ + Get an image from frame idx + """ + + header = self.entry[idx] + + self.buf.seek(header.offset) + data = self.buf.read(8) + self.buf.seek(header.offset) + + im: Image.Image + if data[:8] == PngImagePlugin._MAGIC: + # png frame + im = PngImagePlugin.PngImageFile(self.buf) + Image._decompression_bomb_check(im.size) + else: + # XOR + AND mask bmp frame + im = BmpImagePlugin.DibImageFile(self.buf) + Image._decompression_bomb_check(im.size) + + # change tile dimension to only encompass XOR image + im._size = (im.size[0], int(im.size[1] / 2)) + d, e, o, a = im.tile[0] + im.tile[0] = ImageFile._Tile(d, (0, 0) + im.size, o, a) + + # figure out where AND mask image starts + if header.bpp == 32: + # 32-bit color depth icon image allows semitransparent areas + # PIL's DIB format ignores transparency bits, recover them. + # The DIB is packed in BGRX byte order where X is the alpha + # channel. + + # Back up to start of bmp data + self.buf.seek(o) + # extract every 4th byte (eg. 3,7,11,15,...) + alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4] + + # convert to an 8bpp grayscale image + try: + mask = Image.frombuffer( + "L", # 8bpp + im.size, # (w, h) + alpha_bytes, # source chars + "raw", # raw decoder + ("L", 0, -1), # 8bpp inverted, unpadded, reversed + ) + except ValueError: + if ImageFile.LOAD_TRUNCATED_IMAGES: + mask = None + else: + raise + else: + # get AND image from end of bitmap + w = im.size[0] + if (w % 32) > 0: + # bitmap row data is aligned to word boundaries + w += 32 - (im.size[0] % 32) + + # the total mask data is + # padded row size * height / bits per char + + total_bytes = int((w * im.size[1]) / 8) + and_mask_offset = header.offset + header.size - total_bytes + + self.buf.seek(and_mask_offset) + mask_data = self.buf.read(total_bytes) + + # convert raw data to image + try: + mask = Image.frombuffer( + "1", # 1 bpp + im.size, # (w, h) + mask_data, # source chars + "raw", # raw decoder + ("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed + ) + except ValueError: + if ImageFile.LOAD_TRUNCATED_IMAGES: + mask = None + else: + raise + + # now we have two images, im is XOR image and mask is AND image + + # apply mask image as alpha channel + if mask: + im = im.convert("RGBA") + im.putalpha(mask) + + return im + + +## +# Image plugin for Windows Icon files. + + +class IcoImageFile(ImageFile.ImageFile): + """ + PIL read-only image support for Microsoft Windows .ico files. + + By default the largest resolution image in the file will be loaded. This + can be changed by altering the 'size' attribute before calling 'load'. + + The info dictionary has a key 'sizes' that is a list of the sizes available + in the icon file. + + Handles classic, XP and Vista icon formats. + + When saving, PNG compression is used. Support for this was only added in + Windows Vista. If you are unable to view the icon in Windows, convert the + image to "RGBA" mode before saving. + + This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis + . + https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki + """ + + format = "ICO" + format_description = "Windows Icon" + + def _open(self) -> None: + assert self.fp is not None + self.ico = IcoFile(self.fp) + self.info["sizes"] = self.ico.sizes() + self.size = self.ico.entry[0].dim + self.load() + + @property + def size(self) -> tuple[int, int]: + return self._size + + @size.setter + def size(self, value: tuple[int, int]) -> None: + if value not in self.info["sizes"]: + msg = "This is not one of the allowed sizes of this image" + raise ValueError(msg) + self._size = value + + def load(self) -> Image.core.PixelAccess | None: + if self._im is not None and self.im.size == self.size: + # Already loaded + return Image.Image.load(self) + im = self.ico.getimage(self.size) + # if tile is PNG, it won't really be loaded yet + im.load() + self.im = im.im + self._mode = im.mode + if im.palette: + self.palette = im.palette + if im.size != self.size: + warnings.warn("Image was not the expected size") + + index = self.ico.getentryindex(self.size) + sizes = list(self.info["sizes"]) + sizes[index] = im.size + self.info["sizes"] = set(sizes) + + self.size = im.size + return Image.Image.load(self) + + def load_seek(self, pos: int) -> None: + # Flag the ImageFile.Parser so that it + # just does all the decode at the end. + pass + + +# +# -------------------------------------------------------------------- + + +Image.register_open(IcoImageFile.format, IcoImageFile, _accept) +Image.register_save(IcoImageFile.format, _save) +Image.register_extension(IcoImageFile.format, ".ico") + +Image.register_mime(IcoImageFile.format, "image/x-icon") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImImagePlugin.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImImagePlugin.py new file mode 100644 index 0000000000000000000000000000000000000000..ef54f16e97e69a1410880e48ffecb1a2cbf15ea8 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImImagePlugin.py @@ -0,0 +1,390 @@ +# +# The Python Imaging Library. +# $Id$ +# +# IFUNC IM file handling for PIL +# +# history: +# 1995-09-01 fl Created. +# 1997-01-03 fl Save palette images +# 1997-01-08 fl Added sequence support +# 1997-01-23 fl Added P and RGB save support +# 1997-05-31 fl Read floating point images +# 1997-06-22 fl Save floating point images +# 1997-08-27 fl Read and save 1-bit images +# 1998-06-25 fl Added support for RGB+LUT images +# 1998-07-02 fl Added support for YCC images +# 1998-07-15 fl Renamed offset attribute to avoid name clash +# 1998-12-29 fl Added I;16 support +# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7) +# 2003-09-26 fl Added LA/PA support +# +# Copyright (c) 1997-2003 by Secret Labs AB. +# Copyright (c) 1995-2001 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +import re +from typing import IO, Any + +from . import Image, ImageFile, ImagePalette +from ._util import DeferredError + +# -------------------------------------------------------------------- +# Standard tags + +COMMENT = "Comment" +DATE = "Date" +EQUIPMENT = "Digitalization equipment" +FRAMES = "File size (no of images)" +LUT = "Lut" +NAME = "Name" +SCALE = "Scale (x,y)" +SIZE = "Image size (x*y)" +MODE = "Image type" + +TAGS = { + COMMENT: 0, + DATE: 0, + EQUIPMENT: 0, + FRAMES: 0, + LUT: 0, + NAME: 0, + SCALE: 0, + SIZE: 0, + MODE: 0, +} + +OPEN = { + # ifunc93/p3cfunc formats + "0 1 image": ("1", "1"), + "L 1 image": ("1", "1"), + "Greyscale image": ("L", "L"), + "Grayscale image": ("L", "L"), + "RGB image": ("RGB", "RGB;L"), + "RLB image": ("RGB", "RLB"), + "RYB image": ("RGB", "RLB"), + "B1 image": ("1", "1"), + "B2 image": ("P", "P;2"), + "B4 image": ("P", "P;4"), + "X 24 image": ("RGB", "RGB"), + "L 32 S image": ("I", "I;32"), + "L 32 F image": ("F", "F;32"), + # old p3cfunc formats + "RGB3 image": ("RGB", "RGB;T"), + "RYB3 image": ("RGB", "RYB;T"), + # extensions + "LA image": ("LA", "LA;L"), + "PA image": ("LA", "PA;L"), + "RGBA image": ("RGBA", "RGBA;L"), + "RGBX image": ("RGB", "RGBX;L"), + "CMYK image": ("CMYK", "CMYK;L"), + "YCC image": ("YCbCr", "YCbCr;L"), +} + +# ifunc95 extensions +for i in ["8", "8S", "16", "16S", "32", "32F"]: + OPEN[f"L {i} image"] = ("F", f"F;{i}") + OPEN[f"L*{i} image"] = ("F", f"F;{i}") +for i in ["16", "16L", "16B"]: + OPEN[f"L {i} image"] = (f"I;{i}", f"I;{i}") + OPEN[f"L*{i} image"] = (f"I;{i}", f"I;{i}") +for i in ["32S"]: + OPEN[f"L {i} image"] = ("I", f"I;{i}") + OPEN[f"L*{i} image"] = ("I", f"I;{i}") +for j in range(2, 33): + OPEN[f"L*{j} image"] = ("F", f"F;{j}") + + +# -------------------------------------------------------------------- +# Read IM directory + +split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$") + + +def number(s: Any) -> float: + try: + return int(s) + except ValueError: + return float(s) + + +## +# Image plugin for the IFUNC IM file format. + + +class ImImageFile(ImageFile.ImageFile): + format = "IM" + format_description = "IFUNC Image Memory" + _close_exclusive_fp_after_loading = False + + def _open(self) -> None: + # Quick rejection: if there's not an LF among the first + # 100 bytes, this is (probably) not a text header. + + assert self.fp is not None + if b"\n" not in self.fp.read(100): + msg = "not an IM file" + raise SyntaxError(msg) + self.fp.seek(0) + + n = 0 + + # Default values + self.info[MODE] = "L" + self.info[SIZE] = (512, 512) + self.info[FRAMES] = 1 + + self.rawmode = "L" + + while True: + s = self.fp.read(1) + + # Some versions of IFUNC uses \n\r instead of \r\n... + if s == b"\r": + continue + + if not s or s == b"\0" or s == b"\x1a": + break + + # FIXME: this may read whole file if not a text file + s = s + self.fp.readline() + + if len(s) > 100: + msg = "not an IM file" + raise SyntaxError(msg) + + if s.endswith(b"\r\n"): + s = s[:-2] + elif s.endswith(b"\n"): + s = s[:-1] + + try: + m = split.match(s) + except re.error as e: + msg = "not an IM file" + raise SyntaxError(msg) from e + + if m: + k, v = m.group(1, 2) + + # Don't know if this is the correct encoding, + # but a decent guess (I guess) + k = k.decode("latin-1", "replace") + v = v.decode("latin-1", "replace") + + # Convert value as appropriate + if k in [FRAMES, SCALE, SIZE]: + v = v.replace("*", ",") + v = tuple(map(number, v.split(","))) + if len(v) == 1: + v = v[0] + elif k == MODE and v in OPEN: + v, self.rawmode = OPEN[v] + + # Add to dictionary. Note that COMMENT tags are + # combined into a list of strings. + if k == COMMENT: + if k in self.info: + self.info[k].append(v) + else: + self.info[k] = [v] + else: + self.info[k] = v + + if k in TAGS: + n += 1 + + else: + msg = f"Syntax error in IM header: {s.decode('ascii', 'replace')}" + raise SyntaxError(msg) + + if not n: + msg = "Not an IM file" + raise SyntaxError(msg) + + # Basic attributes + self._size = self.info[SIZE] + self._mode = self.info[MODE] + + # Skip forward to start of image data + while s and not s.startswith(b"\x1a"): + s = self.fp.read(1) + if not s: + msg = "File truncated" + raise SyntaxError(msg) + + if LUT in self.info: + # convert lookup table to palette or lut attribute + palette = self.fp.read(768) + greyscale = 1 # greyscale palette + linear = 1 # linear greyscale palette + for i in range(256): + if palette[i] == palette[i + 256] == palette[i + 512]: + if palette[i] != i: + linear = 0 + else: + greyscale = 0 + if self.mode in ["L", "LA", "P", "PA"]: + if greyscale: + if not linear: + self.lut = list(palette[:256]) + else: + if self.mode in ["L", "P"]: + self._mode = self.rawmode = "P" + elif self.mode in ["LA", "PA"]: + self._mode = "PA" + self.rawmode = "PA;L" + self.palette = ImagePalette.raw("RGB;L", palette) + elif self.mode == "RGB": + if not greyscale or not linear: + self.lut = list(palette) + + self.frame = 0 + + self.__offset = offs = self.fp.tell() + + self._fp = self.fp # FIXME: hack + + if self.rawmode.startswith("F;"): + # ifunc95 formats + try: + # use bit decoder (if necessary) + bits = int(self.rawmode[2:]) + if bits not in [8, 16, 32]: + self.tile = [ + ImageFile._Tile( + "bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1) + ) + ] + return + except ValueError: + pass + + if self.rawmode in ["RGB;T", "RYB;T"]: + # Old LabEye/3PC files. Would be very surprised if anyone + # ever stumbled upon such a file ;-) + size = self.size[0] * self.size[1] + self.tile = [ + ImageFile._Tile("raw", (0, 0) + self.size, offs, ("G", 0, -1)), + ImageFile._Tile("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)), + ImageFile._Tile( + "raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1) + ), + ] + else: + # LabEye/IFUNC files + self.tile = [ + ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1)) + ] + + @property + def n_frames(self) -> int: + return self.info[FRAMES] + + @property + def is_animated(self) -> bool: + return self.info[FRAMES] > 1 + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + if isinstance(self._fp, DeferredError): + raise self._fp.ex + + self.frame = frame + + if self.mode == "1": + bits = 1 + else: + bits = 8 * len(self.mode) + + size = ((self.size[0] * bits + 7) // 8) * self.size[1] + offs = self.__offset + frame * size + + self.fp = self._fp + + self.tile = [ + ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1)) + ] + + def tell(self) -> int: + return self.frame + + +# +# -------------------------------------------------------------------- +# Save IM files + + +SAVE = { + # mode: (im type, raw mode) + "1": ("0 1", "1"), + "L": ("Greyscale", "L"), + "LA": ("LA", "LA;L"), + "P": ("Greyscale", "P"), + "PA": ("LA", "PA;L"), + "I": ("L 32S", "I;32S"), + "I;16": ("L 16", "I;16"), + "I;16L": ("L 16L", "I;16L"), + "I;16B": ("L 16B", "I;16B"), + "F": ("L 32F", "F;32F"), + "RGB": ("RGB", "RGB;L"), + "RGBA": ("RGBA", "RGBA;L"), + "RGBX": ("RGBX", "RGBX;L"), + "CMYK": ("CMYK", "CMYK;L"), + "YCbCr": ("YCC", "YCbCr;L"), +} + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + try: + image_type, rawmode = SAVE[im.mode] + except KeyError as e: + msg = f"Cannot save {im.mode} images as IM" + raise ValueError(msg) from e + + frames = im.encoderinfo.get("frames", 1) + + fp.write(f"Image type: {image_type} image\r\n".encode("ascii")) + if filename: + # Each line must be 100 characters or less, + # or: SyntaxError("not an IM file") + # 8 characters are used for "Name: " and "\r\n" + # Keep just the filename, ditch the potentially overlong path + if isinstance(filename, bytes): + filename = filename.decode("ascii") + name, ext = os.path.splitext(os.path.basename(filename)) + name = "".join([name[: 92 - len(ext)], ext]) + + fp.write(f"Name: {name}\r\n".encode("ascii")) + fp.write(f"Image size (x*y): {im.size[0]}*{im.size[1]}\r\n".encode("ascii")) + fp.write(f"File size (no of images): {frames}\r\n".encode("ascii")) + if im.mode in ["P", "PA"]: + fp.write(b"Lut: 1\r\n") + fp.write(b"\000" * (511 - fp.tell()) + b"\032") + if im.mode in ["P", "PA"]: + im_palette = im.im.getpalette("RGB", "RGB;L") + colors = len(im_palette) // 3 + palette = b"" + for i in range(3): + palette += im_palette[colors * i : colors * (i + 1)] + palette += b"\x00" * (256 - colors) + fp.write(palette) # 768 bytes + ImageFile._save( + im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))] + ) + + +# +# -------------------------------------------------------------------- +# Registry + + +Image.register_open(ImImageFile.format, ImImageFile) +Image.register_save(ImImageFile.format, _save) + +Image.register_extension(ImImageFile.format, ".im") diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/Image.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/Image.py new file mode 100644 index 0000000000000000000000000000000000000000..57ebea689642d9b307e0ec39bb08ed01eebcf4d3 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/Image.py @@ -0,0 +1,4246 @@ +# +# The Python Imaging Library. +# $Id$ +# +# the Image class wrapper +# +# partial release history: +# 1995-09-09 fl Created +# 1996-03-11 fl PIL release 0.0 (proof of concept) +# 1996-04-30 fl PIL release 0.1b1 +# 1999-07-28 fl PIL release 1.0 final +# 2000-06-07 fl PIL release 1.1 +# 2000-10-20 fl PIL release 1.1.1 +# 2001-05-07 fl PIL release 1.1.2 +# 2002-03-15 fl PIL release 1.1.3 +# 2003-05-10 fl PIL release 1.1.4 +# 2005-03-28 fl PIL release 1.1.5 +# 2006-12-02 fl PIL release 1.1.6 +# 2009-11-15 fl PIL release 1.1.7 +# +# Copyright (c) 1997-2009 by Secret Labs AB. All rights reserved. +# Copyright (c) 1995-2009 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# + +from __future__ import annotations + +import abc +import atexit +import builtins +import io +import logging +import math +import os +import re +import struct +import sys +import tempfile +import warnings +from collections.abc import MutableMapping +from enum import IntEnum +from typing import IO, Protocol, cast + +# VERSION was removed in Pillow 6.0.0. +# PILLOW_VERSION was removed in Pillow 9.0.0. +# Use __version__ instead. +from . import ( + ExifTags, + ImageMode, + TiffTags, + UnidentifiedImageError, + __version__, + _plugins, +) +from ._binary import i32le, o32be, o32le +from ._deprecate import deprecate +from ._util import DeferredError, is_path + +ElementTree: ModuleType | None +try: + from defusedxml import ElementTree +except ImportError: + ElementTree = None + +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable, Iterator, Sequence + from types import ModuleType + from typing import Any, Literal + +logger = logging.getLogger(__name__) + + +class DecompressionBombWarning(RuntimeWarning): + pass + + +class DecompressionBombError(Exception): + pass + + +WARN_POSSIBLE_FORMATS: bool = False + +# Limit to around a quarter gigabyte for a 24-bit (3 bpp) image +MAX_IMAGE_PIXELS: int | None = int(1024 * 1024 * 1024 // 4 // 3) + + +try: + # If the _imaging C module is not present, Pillow will not load. + # Note that other modules should not refer to _imaging directly; + # import Image and use the Image.core variable instead. + # Also note that Image.core is not a publicly documented interface, + # and should be considered private and subject to change. + from . import _imaging as core + + if __version__ != getattr(core, "PILLOW_VERSION", None): + msg = ( + "The _imaging extension was built for another version of Pillow or PIL:\n" + f"Core version: {getattr(core, 'PILLOW_VERSION', None)}\n" + f"Pillow version: {__version__}" + ) + raise ImportError(msg) + +except ImportError as v: + # Explanations for ways that we know we might have an import error + if str(v).startswith("Module use of python"): + # The _imaging C module is present, but not compiled for + # the right version (windows only). Print a warning, if + # possible. + warnings.warn( + "The _imaging extension was built for another version of Python.", + RuntimeWarning, + ) + elif str(v).startswith("The _imaging extension"): + warnings.warn(str(v), RuntimeWarning) + # Fail here anyway. Don't let people run with a mostly broken Pillow. + # see docs/porting.rst + raise + + +# +# Constants + + +# transpose +class Transpose(IntEnum): + FLIP_LEFT_RIGHT = 0 + FLIP_TOP_BOTTOM = 1 + ROTATE_90 = 2 + ROTATE_180 = 3 + ROTATE_270 = 4 + TRANSPOSE = 5 + TRANSVERSE = 6 + + +# transforms (also defined in Imaging.h) +class Transform(IntEnum): + AFFINE = 0 + EXTENT = 1 + PERSPECTIVE = 2 + QUAD = 3 + MESH = 4 + + +# resampling filters (also defined in Imaging.h) +class Resampling(IntEnum): + NEAREST = 0 + BOX = 4 + BILINEAR = 2 + HAMMING = 5 + BICUBIC = 3 + LANCZOS = 1 + + +_filters_support = { + Resampling.BOX: 0.5, + Resampling.BILINEAR: 1.0, + Resampling.HAMMING: 1.0, + Resampling.BICUBIC: 2.0, + Resampling.LANCZOS: 3.0, +} + + +# dithers +class Dither(IntEnum): + NONE = 0 + ORDERED = 1 # Not yet implemented + RASTERIZE = 2 # Not yet implemented + FLOYDSTEINBERG = 3 # default + + +# palettes/quantizers +class Palette(IntEnum): + WEB = 0 + ADAPTIVE = 1 + + +class Quantize(IntEnum): + MEDIANCUT = 0 + MAXCOVERAGE = 1 + FASTOCTREE = 2 + LIBIMAGEQUANT = 3 + + +module = sys.modules[__name__] +for enum in (Transpose, Transform, Resampling, Dither, Palette, Quantize): + for item in enum: + setattr(module, item.name, item.value) + + +if hasattr(core, "DEFAULT_STRATEGY"): + DEFAULT_STRATEGY = core.DEFAULT_STRATEGY + FILTERED = core.FILTERED + HUFFMAN_ONLY = core.HUFFMAN_ONLY + RLE = core.RLE + FIXED = core.FIXED + + +# -------------------------------------------------------------------- +# Registries + +TYPE_CHECKING = False +if TYPE_CHECKING: + import mmap + from xml.etree.ElementTree import Element + + from IPython.lib.pretty import PrettyPrinter + + from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin + from ._typing import CapsuleType, NumpyArray, StrOrBytesPath +ID: list[str] = [] +OPEN: dict[ + str, + tuple[ + Callable[[IO[bytes], str | bytes], ImageFile.ImageFile], + Callable[[bytes], bool | str] | None, + ], +] = {} +MIME: dict[str, str] = {} +SAVE: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {} +SAVE_ALL: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {} +EXTENSION: dict[str, str] = {} +DECODERS: dict[str, type[ImageFile.PyDecoder]] = {} +ENCODERS: dict[str, type[ImageFile.PyEncoder]] = {} + +# -------------------------------------------------------------------- +# Modes + +_ENDIAN = "<" if sys.byteorder == "little" else ">" + + +def _conv_type_shape(im: Image) -> tuple[tuple[int, ...], str]: + m = ImageMode.getmode(im.mode) + shape: tuple[int, ...] = (im.height, im.width) + extra = len(m.bands) + if extra != 1: + shape += (extra,) + return shape, m.typestr + + +MODES = [ + "1", + "CMYK", + "F", + "HSV", + "I", + "I;16", + "I;16B", + "I;16L", + "I;16N", + "L", + "LA", + "La", + "LAB", + "P", + "PA", + "RGB", + "RGBA", + "RGBa", + "RGBX", + "YCbCr", +] + +# raw modes that may be memory mapped. NOTE: if you change this, you +# may have to modify the stride calculation in map.c too! +_MAPMODES = ("L", "P", "RGBX", "RGBA", "CMYK", "I;16", "I;16L", "I;16B") + + +def getmodebase(mode: str) -> str: + """ + Gets the "base" mode for given mode. This function returns "L" for + images that contain grayscale data, and "RGB" for images that + contain color data. + + :param mode: Input mode. + :returns: "L" or "RGB". + :exception KeyError: If the input mode was not a standard mode. + """ + return ImageMode.getmode(mode).basemode + + +def getmodetype(mode: str) -> str: + """ + Gets the storage type mode. Given a mode, this function returns a + single-layer mode suitable for storing individual bands. + + :param mode: Input mode. + :returns: "L", "I", or "F". + :exception KeyError: If the input mode was not a standard mode. + """ + return ImageMode.getmode(mode).basetype + + +def getmodebandnames(mode: str) -> tuple[str, ...]: + """ + Gets a list of individual band names. Given a mode, this function returns + a tuple containing the names of individual bands (use + :py:method:`~PIL.Image.getmodetype` to get the mode used to store each + individual band. + + :param mode: Input mode. + :returns: A tuple containing band names. The length of the tuple + gives the number of bands in an image of the given mode. + :exception KeyError: If the input mode was not a standard mode. + """ + return ImageMode.getmode(mode).bands + + +def getmodebands(mode: str) -> int: + """ + Gets the number of individual bands for this mode. + + :param mode: Input mode. + :returns: The number of bands in this mode. + :exception KeyError: If the input mode was not a standard mode. + """ + return len(ImageMode.getmode(mode).bands) + + +# -------------------------------------------------------------------- +# Helpers + +_initialized = 0 + + +def preinit() -> None: + """ + Explicitly loads BMP, GIF, JPEG, PPM and PPM file format drivers. + + It is called when opening or saving images. + """ + + global _initialized + if _initialized >= 1: + return + + try: + from . import BmpImagePlugin + + assert BmpImagePlugin + except ImportError: + pass + try: + from . import GifImagePlugin + + assert GifImagePlugin + except ImportError: + pass + try: + from . import JpegImagePlugin + + assert JpegImagePlugin + except ImportError: + pass + try: + from . import PpmImagePlugin + + assert PpmImagePlugin + except ImportError: + pass + try: + from . import PngImagePlugin + + assert PngImagePlugin + except ImportError: + pass + + _initialized = 1 + + +def init() -> bool: + """ + Explicitly initializes the Python Imaging Library. This function + loads all available file format drivers. + + It is called when opening or saving images if :py:meth:`~preinit()` is + insufficient, and by :py:meth:`~PIL.features.pilinfo`. + """ + + global _initialized + if _initialized >= 2: + return False + + parent_name = __name__.rpartition(".")[0] + for plugin in _plugins: + try: + logger.debug("Importing %s", plugin) + __import__(f"{parent_name}.{plugin}", globals(), locals(), []) + except ImportError as e: + logger.debug("Image: failed to import %s: %s", plugin, e) + + if OPEN or SAVE: + _initialized = 2 + return True + return False + + +# -------------------------------------------------------------------- +# Codec factories (used by tobytes/frombytes and ImageFile.load) + + +def _getdecoder( + mode: str, decoder_name: str, args: Any, extra: tuple[Any, ...] = () +) -> core.ImagingDecoder | ImageFile.PyDecoder: + # tweak arguments + if args is None: + args = () + elif not isinstance(args, tuple): + args = (args,) + + try: + decoder = DECODERS[decoder_name] + except KeyError: + pass + else: + return decoder(mode, *args + extra) + + try: + # get decoder + decoder = getattr(core, f"{decoder_name}_decoder") + except AttributeError as e: + msg = f"decoder {decoder_name} not available" + raise OSError(msg) from e + return decoder(mode, *args + extra) + + +def _getencoder( + mode: str, encoder_name: str, args: Any, extra: tuple[Any, ...] = () +) -> core.ImagingEncoder | ImageFile.PyEncoder: + # tweak arguments + if args is None: + args = () + elif not isinstance(args, tuple): + args = (args,) + + try: + encoder = ENCODERS[encoder_name] + except KeyError: + pass + else: + return encoder(mode, *args + extra) + + try: + # get encoder + encoder = getattr(core, f"{encoder_name}_encoder") + except AttributeError as e: + msg = f"encoder {encoder_name} not available" + raise OSError(msg) from e + return encoder(mode, *args + extra) + + +# -------------------------------------------------------------------- +# Simple expression analyzer + + +class ImagePointTransform: + """ + Used with :py:meth:`~PIL.Image.Image.point` for single band images with more than + 8 bits, this represents an affine transformation, where the value is multiplied by + ``scale`` and ``offset`` is added. + """ + + def __init__(self, scale: float, offset: float) -> None: + self.scale = scale + self.offset = offset + + def __neg__(self) -> ImagePointTransform: + return ImagePointTransform(-self.scale, -self.offset) + + def __add__(self, other: ImagePointTransform | float) -> ImagePointTransform: + if isinstance(other, ImagePointTransform): + return ImagePointTransform( + self.scale + other.scale, self.offset + other.offset + ) + return ImagePointTransform(self.scale, self.offset + other) + + __radd__ = __add__ + + def __sub__(self, other: ImagePointTransform | float) -> ImagePointTransform: + return self + -other + + def __rsub__(self, other: ImagePointTransform | float) -> ImagePointTransform: + return other + -self + + def __mul__(self, other: ImagePointTransform | float) -> ImagePointTransform: + if isinstance(other, ImagePointTransform): + return NotImplemented + return ImagePointTransform(self.scale * other, self.offset * other) + + __rmul__ = __mul__ + + def __truediv__(self, other: ImagePointTransform | float) -> ImagePointTransform: + if isinstance(other, ImagePointTransform): + return NotImplemented + return ImagePointTransform(self.scale / other, self.offset / other) + + +def _getscaleoffset( + expr: Callable[[ImagePointTransform], ImagePointTransform | float], +) -> tuple[float, float]: + a = expr(ImagePointTransform(1, 0)) + return (a.scale, a.offset) if isinstance(a, ImagePointTransform) else (0, a) + + +# -------------------------------------------------------------------- +# Implementation wrapper + + +class SupportsGetData(Protocol): + def getdata( + self, + ) -> tuple[Transform, Sequence[int]]: ... + + +class Image: + """ + This class represents an image object. To create + :py:class:`~PIL.Image.Image` objects, use the appropriate factory + functions. There's hardly ever any reason to call the Image constructor + directly. + + * :py:func:`~PIL.Image.open` + * :py:func:`~PIL.Image.new` + * :py:func:`~PIL.Image.frombytes` + """ + + format: str | None = None + format_description: str | None = None + _close_exclusive_fp_after_loading = True + + def __init__(self) -> None: + # FIXME: take "new" parameters / other image? + self._im: core.ImagingCore | DeferredError | None = None + self._mode = "" + self._size = (0, 0) + self.palette: ImagePalette.ImagePalette | None = None + self.info: dict[str | tuple[int, int], Any] = {} + self.readonly = 0 + self._exif: Exif | None = None + + @property + def im(self) -> core.ImagingCore: + if isinstance(self._im, DeferredError): + raise self._im.ex + assert self._im is not None + return self._im + + @im.setter + def im(self, im: core.ImagingCore) -> None: + self._im = im + + @property + def width(self) -> int: + return self.size[0] + + @property + def height(self) -> int: + return self.size[1] + + @property + def size(self) -> tuple[int, int]: + return self._size + + @property + def mode(self) -> str: + return self._mode + + @property + def readonly(self) -> int: + return (self._im and self._im.readonly) or self._readonly + + @readonly.setter + def readonly(self, readonly: int) -> None: + self._readonly = readonly + + def _new(self, im: core.ImagingCore) -> Image: + new = Image() + new.im = im + new._mode = im.mode + new._size = im.size + if im.mode in ("P", "PA"): + if self.palette: + new.palette = self.palette.copy() + else: + from . import ImagePalette + + new.palette = ImagePalette.ImagePalette() + new.info = self.info.copy() + return new + + # Context manager support + def __enter__(self) -> Image: + return self + + def __exit__(self, *args: object) -> None: + pass + + def close(self) -> None: + """ + This operation will destroy the image core and release its memory. + The image data will be unusable afterward. + + This function is required to close images that have multiple frames or + have not had their file read and closed by the + :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for + more information. + """ + if getattr(self, "map", None): + if sys.platform == "win32" and hasattr(sys, "pypy_version_info"): + self.map.close() + self.map: mmap.mmap | None = None + + # Instead of simply setting to None, we're setting up a + # deferred error that will better explain that the core image + # object is gone. + self._im = DeferredError(ValueError("Operation on closed image")) + + def _copy(self) -> None: + self.load() + self.im = self.im.copy() + self.readonly = 0 + + def _ensure_mutable(self) -> None: + if self.readonly: + self._copy() + else: + self.load() + + def _dump( + self, file: str | None = None, format: str | None = None, **options: Any + ) -> str: + suffix = "" + if format: + suffix = f".{format}" + + if not file: + f, filename = tempfile.mkstemp(suffix) + os.close(f) + else: + filename = file + if not filename.endswith(suffix): + filename = filename + suffix + + self.load() + + if not format or format == "PPM": + self.im.save_ppm(filename) + else: + self.save(filename, format, **options) + + return filename + + def __eq__(self, other: object) -> bool: + if self.__class__ is not other.__class__: + return False + assert isinstance(other, Image) + return ( + self.mode == other.mode + and self.size == other.size + and self.info == other.info + and self.getpalette() == other.getpalette() + and self.tobytes() == other.tobytes() + ) + + def __repr__(self) -> str: + return ( + f"<{self.__class__.__module__}.{self.__class__.__name__} " + f"image mode={self.mode} size={self.size[0]}x{self.size[1]} " + f"at 0x{id(self):X}>" + ) + + def _repr_pretty_(self, p: PrettyPrinter, cycle: bool) -> None: + """IPython plain text display support""" + + # Same as __repr__ but without unpredictable id(self), + # to keep Jupyter notebook `text/plain` output stable. + p.text( + f"<{self.__class__.__module__}.{self.__class__.__name__} " + f"image mode={self.mode} size={self.size[0]}x{self.size[1]}>" + ) + + def _repr_image(self, image_format: str, **kwargs: Any) -> bytes | None: + """Helper function for iPython display hook. + + :param image_format: Image format. + :returns: image as bytes, saved into the given format. + """ + b = io.BytesIO() + try: + self.save(b, image_format, **kwargs) + except Exception: + return None + return b.getvalue() + + def _repr_png_(self) -> bytes | None: + """iPython display hook support for PNG format. + + :returns: PNG version of the image as bytes + """ + return self._repr_image("PNG", compress_level=1) + + def _repr_jpeg_(self) -> bytes | None: + """iPython display hook support for JPEG format. + + :returns: JPEG version of the image as bytes + """ + return self._repr_image("JPEG") + + @property + def __array_interface__(self) -> dict[str, str | bytes | int | tuple[int, ...]]: + # numpy array interface support + new: dict[str, str | bytes | int | tuple[int, ...]] = {"version": 3} + if self.mode == "1": + # Binary images need to be extended from bits to bytes + # See: https://github.com/python-pillow/Pillow/issues/350 + new["data"] = self.tobytes("raw", "L") + else: + new["data"] = self.tobytes() + new["shape"], new["typestr"] = _conv_type_shape(self) + return new + + def __arrow_c_schema__(self) -> object: + self.load() + return self.im.__arrow_c_schema__() + + def __arrow_c_array__( + self, requested_schema: object | None = None + ) -> tuple[object, object]: + self.load() + return (self.im.__arrow_c_schema__(), self.im.__arrow_c_array__()) + + def __getstate__(self) -> list[Any]: + im_data = self.tobytes() # load image first + return [self.info, self.mode, self.size, self.getpalette(), im_data] + + def __setstate__(self, state: list[Any]) -> None: + Image.__init__(self) + info, mode, size, palette, data = state[:5] + self.info = info + self._mode = mode + self._size = size + self.im = core.new(mode, size) + if mode in ("L", "LA", "P", "PA") and palette: + self.putpalette(palette) + self.frombytes(data) + + def tobytes(self, encoder_name: str = "raw", *args: Any) -> bytes: + """ + Return image as a bytes object. + + .. warning:: + + This method returns raw image data derived from Pillow's internal + storage. For compressed image data (e.g. PNG, JPEG) use + :meth:`~.save`, with a BytesIO parameter for in-memory data. + + :param encoder_name: What encoder to use. + + The default is to use the standard "raw" encoder. + To see how this packs pixel data into the returned + bytes, see :file:`libImaging/Pack.c`. + + A list of C encoders can be seen under codecs + section of the function array in + :file:`_imaging.c`. Python encoders are registered + within the relevant plugins. + :param args: Extra arguments to the encoder. + :returns: A :py:class:`bytes` object. + """ + + encoder_args: Any = args + if len(encoder_args) == 1 and isinstance(encoder_args[0], tuple): + # may pass tuple instead of argument list + encoder_args = encoder_args[0] + + if encoder_name == "raw" and encoder_args == (): + encoder_args = self.mode + + self.load() + + if self.width == 0 or self.height == 0: + return b"" + + # unpack data + e = _getencoder(self.mode, encoder_name, encoder_args) + e.setimage(self.im) + + from . import ImageFile + + bufsize = max(ImageFile.MAXBLOCK, self.size[0] * 4) # see RawEncode.c + + output = [] + while True: + bytes_consumed, errcode, data = e.encode(bufsize) + output.append(data) + if errcode: + break + if errcode < 0: + msg = f"encoder error {errcode} in tobytes" + raise RuntimeError(msg) + + return b"".join(output) + + def tobitmap(self, name: str = "image") -> bytes: + """ + Returns the image converted to an X11 bitmap. + + .. note:: This method only works for mode "1" images. + + :param name: The name prefix to use for the bitmap variables. + :returns: A string containing an X11 bitmap. + :raises ValueError: If the mode is not "1" + """ + + self.load() + if self.mode != "1": + msg = "not a bitmap" + raise ValueError(msg) + data = self.tobytes("xbm") + return b"".join( + [ + f"#define {name}_width {self.size[0]}\n".encode("ascii"), + f"#define {name}_height {self.size[1]}\n".encode("ascii"), + f"static char {name}_bits[] = {{\n".encode("ascii"), + data, + b"};", + ] + ) + + def frombytes( + self, + data: bytes | bytearray | SupportsArrayInterface, + decoder_name: str = "raw", + *args: Any, + ) -> None: + """ + Loads this image with pixel data from a bytes object. + + This method is similar to the :py:func:`~PIL.Image.frombytes` function, + but loads data into this image instead of creating a new image object. + """ + + if self.width == 0 or self.height == 0: + return + + decoder_args: Any = args + if len(decoder_args) == 1 and isinstance(decoder_args[0], tuple): + # may pass tuple instead of argument list + decoder_args = decoder_args[0] + + # default format + if decoder_name == "raw" and decoder_args == (): + decoder_args = self.mode + + # unpack data + d = _getdecoder(self.mode, decoder_name, decoder_args) + d.setimage(self.im) + s = d.decode(data) + + if s[0] >= 0: + msg = "not enough image data" + raise ValueError(msg) + if s[1] != 0: + msg = "cannot decode image data" + raise ValueError(msg) + + def load(self) -> core.PixelAccess | None: + """ + Allocates storage for the image and loads the pixel data. In + normal cases, you don't need to call this method, since the + Image class automatically loads an opened image when it is + accessed for the first time. + + If the file associated with the image was opened by Pillow, then this + method will close it. The exception to this is if the image has + multiple frames, in which case the file will be left open for seek + operations. See :ref:`file-handling` for more information. + + :returns: An image access object. + :rtype: :py:class:`.PixelAccess` + """ + if self._im is not None and self.palette and self.palette.dirty: + # realize palette + mode, arr = self.palette.getdata() + self.im.putpalette(self.palette.mode, mode, arr) + self.palette.dirty = 0 + self.palette.rawmode = None + if "transparency" in self.info and mode in ("LA", "PA"): + if isinstance(self.info["transparency"], int): + self.im.putpalettealpha(self.info["transparency"], 0) + else: + self.im.putpalettealphas(self.info["transparency"]) + self.palette.mode = "RGBA" + else: + self.palette.palette = self.im.getpalette( + self.palette.mode, self.palette.mode + ) + + if self._im is not None: + return self.im.pixel_access(self.readonly) + return None + + def verify(self) -> None: + """ + Verifies the contents of a file. For data read from a file, this + method attempts to determine if the file is broken, without + actually decoding the image data. If this method finds any + problems, it raises suitable exceptions. If you need to load + the image after using this method, you must reopen the image + file. + """ + pass + + def convert( + self, + mode: str | None = None, + matrix: tuple[float, ...] | None = None, + dither: Dither | None = None, + palette: Palette = Palette.WEB, + colors: int = 256, + ) -> Image: + """ + Returns a converted copy of this image. For the "P" mode, this + method translates pixels through the palette. If mode is + omitted, a mode is chosen so that all information in the image + and the palette can be represented without a palette. + + This supports all possible conversions between "L", "RGB" and "CMYK". The + ``matrix`` argument only supports "L" and "RGB". + + When translating a color image to grayscale (mode "L"), + the library uses the ITU-R 601-2 luma transform:: + + L = R * 299/1000 + G * 587/1000 + B * 114/1000 + + The default method of converting a grayscale ("L") or "RGB" + image into a bilevel (mode "1") image uses Floyd-Steinberg + dither to approximate the original image luminosity levels. If + dither is ``None``, all values larger than 127 are set to 255 (white), + all other values to 0 (black). To use other thresholds, use the + :py:meth:`~PIL.Image.Image.point` method. + + When converting from "RGBA" to "P" without a ``matrix`` argument, + this passes the operation to :py:meth:`~PIL.Image.Image.quantize`, + and ``dither`` and ``palette`` are ignored. + + When converting from "PA", if an "RGBA" palette is present, the alpha + channel from the image will be used instead of the values from the palette. + + :param mode: The requested mode. See: :ref:`concept-modes`. + :param matrix: An optional conversion matrix. If given, this + should be 4- or 12-tuple containing floating point values. + :param dither: Dithering method, used when converting from + mode "RGB" to "P" or from "RGB" or "L" to "1". + Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG` + (default). Note that this is not used when ``matrix`` is supplied. + :param palette: Palette to use when converting from mode "RGB" + to "P". Available palettes are :data:`Palette.WEB` or + :data:`Palette.ADAPTIVE`. + :param colors: Number of colors to use for the :data:`Palette.ADAPTIVE` + palette. Defaults to 256. + :rtype: :py:class:`~PIL.Image.Image` + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + self.load() + + has_transparency = "transparency" in self.info + if not mode and self.mode == "P": + # determine default mode + if self.palette: + mode = self.palette.mode + else: + mode = "RGB" + if mode == "RGB" and has_transparency: + mode = "RGBA" + if not mode or (mode == self.mode and not matrix): + return self.copy() + + if matrix: + # matrix conversion + if mode not in ("L", "RGB"): + msg = "illegal conversion" + raise ValueError(msg) + im = self.im.convert_matrix(mode, matrix) + new_im = self._new(im) + if has_transparency and self.im.bands == 3: + transparency = new_im.info["transparency"] + + def convert_transparency( + m: tuple[float, ...], v: tuple[int, int, int] + ) -> int: + value = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5 + return max(0, min(255, int(value))) + + if mode == "L": + transparency = convert_transparency(matrix, transparency) + elif len(mode) == 3: + transparency = tuple( + convert_transparency(matrix[i * 4 : i * 4 + 4], transparency) + for i in range(len(transparency)) + ) + new_im.info["transparency"] = transparency + return new_im + + if self.mode == "RGBA": + if mode == "P": + return self.quantize(colors) + elif mode == "PA": + r, g, b, a = self.split() + rgb = merge("RGB", (r, g, b)) + p = rgb.quantize(colors) + return merge("PA", (p, a)) + + trns = None + delete_trns = False + # transparency handling + if has_transparency: + if (self.mode in ("1", "L", "I", "I;16") and mode in ("LA", "RGBA")) or ( + self.mode == "RGB" and mode in ("La", "LA", "RGBa", "RGBA") + ): + # Use transparent conversion to promote from transparent + # color to an alpha channel. + new_im = self._new( + self.im.convert_transparent(mode, self.info["transparency"]) + ) + del new_im.info["transparency"] + return new_im + elif self.mode in ("L", "RGB", "P") and mode in ("L", "RGB", "P"): + t = self.info["transparency"] + if isinstance(t, bytes): + # Dragons. This can't be represented by a single color + warnings.warn( + "Palette images with Transparency expressed in bytes should be " + "converted to RGBA images" + ) + delete_trns = True + else: + # get the new transparency color. + # use existing conversions + trns_im = new(self.mode, (1, 1)) + if self.mode == "P": + assert self.palette is not None + trns_im.putpalette(self.palette, self.palette.mode) + if isinstance(t, tuple): + err = "Couldn't allocate a palette color for transparency" + assert trns_im.palette is not None + try: + t = trns_im.palette.getcolor(t, self) + except ValueError as e: + if str(e) == "cannot allocate more than 256 colors": + # If all 256 colors are in use, + # then there is no need for transparency + t = None + else: + raise ValueError(err) from e + if t is None: + trns = None + else: + trns_im.putpixel((0, 0), t) + + if mode in ("L", "RGB"): + trns_im = trns_im.convert(mode) + else: + # can't just retrieve the palette number, got to do it + # after quantization. + trns_im = trns_im.convert("RGB") + trns = trns_im.getpixel((0, 0)) + + elif self.mode == "P" and mode in ("LA", "PA", "RGBA"): + t = self.info["transparency"] + delete_trns = True + + if isinstance(t, bytes): + self.im.putpalettealphas(t) + elif isinstance(t, int): + self.im.putpalettealpha(t, 0) + else: + msg = "Transparency for P mode should be bytes or int" + raise ValueError(msg) + + if mode == "P" and palette == Palette.ADAPTIVE: + im = self.im.quantize(colors) + new_im = self._new(im) + from . import ImagePalette + + new_im.palette = ImagePalette.ImagePalette( + "RGB", new_im.im.getpalette("RGB") + ) + if delete_trns: + # This could possibly happen if we requantize to fewer colors. + # The transparency would be totally off in that case. + del new_im.info["transparency"] + if trns is not None: + try: + new_im.info["transparency"] = new_im.palette.getcolor( + cast(tuple[int, ...], trns), # trns was converted to RGB + new_im, + ) + except Exception: + # if we can't make a transparent color, don't leave the old + # transparency hanging around to mess us up. + del new_im.info["transparency"] + warnings.warn("Couldn't allocate palette entry for transparency") + return new_im + + if "LAB" in (self.mode, mode): + im = self + if mode == "LAB": + if im.mode not in ("RGB", "RGBA", "RGBX"): + im = im.convert("RGBA") + other_mode = im.mode + else: + other_mode = mode + if other_mode in ("RGB", "RGBA", "RGBX"): + from . import ImageCms + + srgb = ImageCms.createProfile("sRGB") + lab = ImageCms.createProfile("LAB") + profiles = [lab, srgb] if im.mode == "LAB" else [srgb, lab] + transform = ImageCms.buildTransform( + profiles[0], profiles[1], im.mode, mode + ) + return transform.apply(im) + + # colorspace conversion + if dither is None: + dither = Dither.FLOYDSTEINBERG + + try: + im = self.im.convert(mode, dither) + except ValueError: + try: + # normalize source image and try again + modebase = getmodebase(self.mode) + if modebase == self.mode: + raise + im = self.im.convert(modebase) + im = im.convert(mode, dither) + except KeyError as e: + msg = "illegal conversion" + raise ValueError(msg) from e + + new_im = self._new(im) + if mode in ("P", "PA") and palette != Palette.ADAPTIVE: + from . import ImagePalette + + new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB")) + if delete_trns: + # crash fail if we leave a bytes transparency in an rgb/l mode. + del new_im.info["transparency"] + if trns is not None: + if new_im.mode == "P" and new_im.palette: + try: + new_im.info["transparency"] = new_im.palette.getcolor( + cast(tuple[int, ...], trns), new_im # trns was converted to RGB + ) + except ValueError as e: + del new_im.info["transparency"] + if str(e) != "cannot allocate more than 256 colors": + # If all 256 colors are in use, + # then there is no need for transparency + warnings.warn( + "Couldn't allocate palette entry for transparency" + ) + else: + new_im.info["transparency"] = trns + return new_im + + def quantize( + self, + colors: int = 256, + method: int | None = None, + kmeans: int = 0, + palette: Image | None = None, + dither: Dither = Dither.FLOYDSTEINBERG, + ) -> Image: + """ + Convert the image to 'P' mode with the specified number + of colors. + + :param colors: The desired number of colors, <= 256 + :param method: :data:`Quantize.MEDIANCUT` (median cut), + :data:`Quantize.MAXCOVERAGE` (maximum coverage), + :data:`Quantize.FASTOCTREE` (fast octree), + :data:`Quantize.LIBIMAGEQUANT` (libimagequant; check support + using :py:func:`PIL.features.check_feature` with + ``feature="libimagequant"``). + + By default, :data:`Quantize.MEDIANCUT` will be used. + + The exception to this is RGBA images. :data:`Quantize.MEDIANCUT` + and :data:`Quantize.MAXCOVERAGE` do not support RGBA images, so + :data:`Quantize.FASTOCTREE` is used by default instead. + :param kmeans: Integer greater than or equal to zero. + :param palette: Quantize to the palette of given + :py:class:`PIL.Image.Image`. + :param dither: Dithering method, used when converting from + mode "RGB" to "P" or from "RGB" or "L" to "1". + Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG` + (default). + :returns: A new image + """ + + self.load() + + if method is None: + # defaults: + method = Quantize.MEDIANCUT + if self.mode == "RGBA": + method = Quantize.FASTOCTREE + + if self.mode == "RGBA" and method not in ( + Quantize.FASTOCTREE, + Quantize.LIBIMAGEQUANT, + ): + # Caller specified an invalid mode. + msg = ( + "Fast Octree (method == 2) and libimagequant (method == 3) " + "are the only valid methods for quantizing RGBA images" + ) + raise ValueError(msg) + + if palette: + # use palette from reference image + palette.load() + if palette.mode != "P": + msg = "bad mode for palette image" + raise ValueError(msg) + if self.mode not in {"RGB", "L"}: + msg = "only RGB or L mode images can be quantized to a palette" + raise ValueError(msg) + im = self.im.convert("P", dither, palette.im) + new_im = self._new(im) + assert palette.palette is not None + new_im.palette = palette.palette.copy() + return new_im + + if kmeans < 0: + msg = "kmeans must not be negative" + raise ValueError(msg) + + im = self._new(self.im.quantize(colors, method, kmeans)) + + from . import ImagePalette + + mode = im.im.getpalettemode() + palette_data = im.im.getpalette(mode, mode)[: colors * len(mode)] + im.palette = ImagePalette.ImagePalette(mode, palette_data) + + return im + + def copy(self) -> Image: + """ + Copies this image. Use this method if you wish to paste things + into an image, but still retain the original. + + :rtype: :py:class:`~PIL.Image.Image` + :returns: An :py:class:`~PIL.Image.Image` object. + """ + self.load() + return self._new(self.im.copy()) + + __copy__ = copy + + def crop(self, box: tuple[float, float, float, float] | None = None) -> Image: + """ + Returns a rectangular region from this image. The box is a + 4-tuple defining the left, upper, right, and lower pixel + coordinate. See :ref:`coordinate-system`. + + Note: Prior to Pillow 3.4.0, this was a lazy operation. + + :param box: The crop rectangle, as a (left, upper, right, lower)-tuple. + :rtype: :py:class:`~PIL.Image.Image` + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + if box is None: + return self.copy() + + if box[2] < box[0]: + msg = "Coordinate 'right' is less than 'left'" + raise ValueError(msg) + elif box[3] < box[1]: + msg = "Coordinate 'lower' is less than 'upper'" + raise ValueError(msg) + + self.load() + return self._new(self._crop(self.im, box)) + + def _crop( + self, im: core.ImagingCore, box: tuple[float, float, float, float] + ) -> core.ImagingCore: + """ + Returns a rectangular region from the core image object im. + + This is equivalent to calling im.crop((x0, y0, x1, y1)), but + includes additional sanity checks. + + :param im: a core image object + :param box: The crop rectangle, as a (left, upper, right, lower)-tuple. + :returns: A core image object. + """ + + x0, y0, x1, y1 = map(int, map(round, box)) + + absolute_values = (abs(x1 - x0), abs(y1 - y0)) + + _decompression_bomb_check(absolute_values) + + return im.crop((x0, y0, x1, y1)) + + def draft( + self, mode: str | None, size: tuple[int, int] | None + ) -> tuple[str, tuple[int, int, float, float]] | None: + """ + Configures the image file loader so it returns a version of the + image that as closely as possible matches the given mode and + size. For example, you can use this method to convert a color + JPEG to grayscale while loading it. + + If any changes are made, returns a tuple with the chosen ``mode`` and + ``box`` with coordinates of the original image within the altered one. + + Note that this method modifies the :py:class:`~PIL.Image.Image` object + in place. If the image has already been loaded, this method has no + effect. + + Note: This method is not implemented for most images. It is + currently implemented only for JPEG and MPO images. + + :param mode: The requested mode. + :param size: The requested size in pixels, as a 2-tuple: + (width, height). + """ + pass + + def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image: + """ + Filters this image using the given filter. For a list of + available filters, see the :py:mod:`~PIL.ImageFilter` module. + + :param filter: Filter kernel. + :returns: An :py:class:`~PIL.Image.Image` object.""" + + from . import ImageFilter + + self.load() + + if callable(filter): + filter = filter() + if not hasattr(filter, "filter"): + msg = "filter argument should be ImageFilter.Filter instance or class" + raise TypeError(msg) + + multiband = isinstance(filter, ImageFilter.MultibandFilter) + if self.im.bands == 1 or multiband: + return self._new(filter.filter(self.im)) + + ims = [ + self._new(filter.filter(self.im.getband(c))) for c in range(self.im.bands) + ] + return merge(self.mode, ims) + + def getbands(self) -> tuple[str, ...]: + """ + Returns a tuple containing the name of each band in this image. + For example, ``getbands`` on an RGB image returns ("R", "G", "B"). + + :returns: A tuple containing band names. + :rtype: tuple + """ + return ImageMode.getmode(self.mode).bands + + def getbbox(self, *, alpha_only: bool = True) -> tuple[int, int, int, int] | None: + """ + Calculates the bounding box of the non-zero regions in the + image. + + :param alpha_only: Optional flag, defaulting to ``True``. + If ``True`` and the image has an alpha channel, trim transparent pixels. + Otherwise, trim pixels when all channels are zero. + Keyword-only argument. + :returns: The bounding box is returned as a 4-tuple defining the + left, upper, right, and lower pixel coordinate. See + :ref:`coordinate-system`. If the image is completely empty, this + method returns None. + + """ + + self.load() + return self.im.getbbox(alpha_only) + + def getcolors( + self, maxcolors: int = 256 + ) -> list[tuple[int, tuple[int, ...]]] | list[tuple[int, float]] | None: + """ + Returns a list of colors used in this image. + + The colors will be in the image's mode. For example, an RGB image will + return a tuple of (red, green, blue) color values, and a P image will + return the index of the color in the palette. + + :param maxcolors: Maximum number of colors. If this number is + exceeded, this method returns None. The default limit is + 256 colors. + :returns: An unsorted list of (count, pixel) values. + """ + + self.load() + if self.mode in ("1", "L", "P"): + h = self.im.histogram() + out: list[tuple[int, float]] = [(h[i], i) for i in range(256) if h[i]] + if len(out) > maxcolors: + return None + return out + return self.im.getcolors(maxcolors) + + def getdata(self, band: int | None = None) -> core.ImagingCore: + """ + Returns the contents of this image as a sequence object + containing pixel values. The sequence object is flattened, so + that values for line one follow directly after the values of + line zero, and so on. + + Note that the sequence object returned by this method is an + internal PIL data type, which only supports certain sequence + operations. To convert it to an ordinary sequence (e.g. for + printing), use ``list(im.getdata())``. + + :param band: What band to return. The default is to return + all bands. To return a single band, pass in the index + value (e.g. 0 to get the "R" band from an "RGB" image). + :returns: A sequence-like object. + """ + deprecate("Image.Image.getdata", 14, "get_flattened_data") + + self.load() + if band is not None: + return self.im.getband(band) + return self.im # could be abused + + def get_flattened_data( + self, band: int | None = None + ) -> tuple[tuple[int, ...], ...] | tuple[float, ...]: + """ + Returns the contents of this image as a tuple containing pixel values. + The sequence object is flattened, so that values for line one follow + directly after the values of line zero, and so on. + + :param band: What band to return. The default is to return + all bands. To return a single band, pass in the index + value (e.g. 0 to get the "R" band from an "RGB" image). + :returns: A tuple containing pixel values. + """ + self.load() + if band is not None: + return tuple(self.im.getband(band)) + return tuple(self.im) + + def getextrema(self) -> tuple[float, float] | tuple[tuple[int, int], ...]: + """ + Gets the minimum and maximum pixel values for each band in + the image. + + :returns: For a single-band image, a 2-tuple containing the + minimum and maximum pixel value. For a multi-band image, + a tuple containing one 2-tuple for each band. + """ + + self.load() + if self.im.bands > 1: + return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands)) + return self.im.getextrema() + + def getxmp(self) -> dict[str, Any]: + """ + Returns a dictionary containing the XMP tags. + Requires defusedxml to be installed. + + :returns: XMP tags in a dictionary. + """ + + def get_name(tag: str) -> str: + return re.sub("^{[^}]+}", "", tag) + + def get_value(element: Element) -> str | dict[str, Any] | None: + value: dict[str, Any] = {get_name(k): v for k, v in element.attrib.items()} + children = list(element) + if children: + for child in children: + name = get_name(child.tag) + child_value = get_value(child) + if name in value: + if not isinstance(value[name], list): + value[name] = [value[name]] + value[name].append(child_value) + else: + value[name] = child_value + elif value: + if element.text: + value["text"] = element.text + else: + return element.text + return value + + if ElementTree is None: + warnings.warn("XMP data cannot be read without defusedxml dependency") + return {} + if "xmp" not in self.info: + return {} + root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00 ")) + return {get_name(root.tag): get_value(root)} + + def getexif(self) -> Exif: + """ + Gets EXIF data from the image. + + :returns: an :py:class:`~PIL.Image.Exif` object. + """ + if self._exif is None: + self._exif = Exif() + elif self._exif._loaded: + return self._exif + self._exif._loaded = True + + exif_info = self.info.get("exif") + if exif_info is None: + if "Raw profile type exif" in self.info: + exif_info = bytes.fromhex( + "".join(self.info["Raw profile type exif"].split("\n")[3:]) + ) + elif hasattr(self, "tag_v2"): + from . import TiffImagePlugin + + assert isinstance(self, TiffImagePlugin.TiffImageFile) + self._exif.bigtiff = self.tag_v2._bigtiff + self._exif.endian = self.tag_v2._endian + + assert self.fp is not None + self._exif.load_from_fp(self.fp, self.tag_v2._offset) + if exif_info is not None: + self._exif.load(exif_info) + + # XMP tags + if ExifTags.Base.Orientation not in self._exif: + xmp_tags = self.info.get("XML:com.adobe.xmp") + pattern: str | bytes = r'tiff:Orientation(="|>)([0-9])' + if not xmp_tags and (xmp_tags := self.info.get("xmp")): + pattern = rb'tiff:Orientation(="|>)([0-9])' + if xmp_tags: + match = re.search(pattern, xmp_tags) + if match: + self._exif[ExifTags.Base.Orientation] = int(match[2]) + + return self._exif + + def _reload_exif(self) -> None: + if self._exif is None or not self._exif._loaded: + return + self._exif._loaded = False + self.getexif() + + def get_child_images(self) -> list[ImageFile.ImageFile]: + from . import ImageFile + + deprecate("Image.Image.get_child_images", 13) + return ImageFile.ImageFile.get_child_images(self) # type: ignore[arg-type] + + def getim(self) -> CapsuleType: + """ + Returns a capsule that points to the internal image memory. + + :returns: A capsule object. + """ + + self.load() + return self.im.ptr + + def getpalette(self, rawmode: str | None = "RGB") -> list[int] | None: + """ + Returns the image palette as a list. + + :param rawmode: The mode in which to return the palette. ``None`` will + return the palette in its current mode. + + .. versionadded:: 9.1.0 + + :returns: A list of color values [r, g, b, ...], or None if the + image has no palette. + """ + + self.load() + try: + mode = self.im.getpalettemode() + except ValueError: + return None # no palette + if rawmode is None: + rawmode = mode + return list(self.im.getpalette(mode, rawmode)) + + @property + def has_transparency_data(self) -> bool: + """ + Determine if an image has transparency data, whether in the form of an + alpha channel, a palette with an alpha channel, or a "transparency" key + in the info dictionary. + + Note the image might still appear solid, if all of the values shown + within are opaque. + + :returns: A boolean. + """ + if ( + self.mode in ("LA", "La", "PA", "RGBA", "RGBa") + or "transparency" in self.info + ): + return True + if self.mode == "P": + assert self.palette is not None + return self.palette.mode.endswith("A") + return False + + def apply_transparency(self) -> None: + """ + If a P mode image has a "transparency" key in the info dictionary, + remove the key and instead apply the transparency to the palette. + Otherwise, the image is unchanged. + """ + if self.mode != "P" or "transparency" not in self.info: + return + + from . import ImagePalette + + palette = self.getpalette("RGBA") + assert palette is not None + transparency = self.info["transparency"] + if isinstance(transparency, bytes): + for i, alpha in enumerate(transparency): + palette[i * 4 + 3] = alpha + else: + palette[transparency * 4 + 3] = 0 + self.palette = ImagePalette.ImagePalette("RGBA", bytes(palette)) + self.palette.dirty = 1 + + del self.info["transparency"] + + def getpixel( + self, xy: tuple[int, int] | list[int] + ) -> float | tuple[int, ...] | None: + """ + Returns the pixel value at a given position. + + :param xy: The coordinate, given as (x, y). See + :ref:`coordinate-system`. + :returns: The pixel value. If the image is a multi-layer image, + this method returns a tuple. + """ + + self.load() + return self.im.getpixel(tuple(xy)) + + def getprojection(self) -> tuple[list[int], list[int]]: + """ + Get projection to x and y axes + + :returns: Two sequences, indicating where there are non-zero + pixels along the X-axis and the Y-axis, respectively. + """ + + self.load() + x, y = self.im.getprojection() + return list(x), list(y) + + def histogram( + self, mask: Image | None = None, extrema: tuple[float, float] | None = None + ) -> list[int]: + """ + Returns a histogram for the image. The histogram is returned as a + list of pixel counts, one for each pixel value in the source + image. Counts are grouped into 256 bins for each band, even if + the image has more than 8 bits per band. If the image has more + than one band, the histograms for all bands are concatenated (for + example, the histogram for an "RGB" image contains 768 values). + + A bilevel image (mode "1") is treated as a grayscale ("L") image + by this method. + + If a mask is provided, the method returns a histogram for those + parts of the image where the mask image is non-zero. The mask + image must have the same size as the image, and be either a + bi-level image (mode "1") or a grayscale image ("L"). + + :param mask: An optional mask. + :param extrema: An optional tuple of manually-specified extrema. + :returns: A list containing pixel counts. + """ + self.load() + if mask: + mask.load() + return self.im.histogram((0, 0), mask.im) + if self.mode in ("I", "F"): + return self.im.histogram( + extrema if extrema is not None else self.getextrema() + ) + return self.im.histogram() + + def entropy( + self, mask: Image | None = None, extrema: tuple[float, float] | None = None + ) -> float: + """ + Calculates and returns the entropy for the image. + + A bilevel image (mode "1") is treated as a grayscale ("L") + image by this method. + + If a mask is provided, the method employs the histogram for + those parts of the image where the mask image is non-zero. + The mask image must have the same size as the image, and be + either a bi-level image (mode "1") or a grayscale image ("L"). + + :param mask: An optional mask. + :param extrema: An optional tuple of manually-specified extrema. + :returns: A float value representing the image entropy + """ + self.load() + if mask: + mask.load() + return self.im.entropy((0, 0), mask.im) + if self.mode in ("I", "F"): + return self.im.entropy( + extrema if extrema is not None else self.getextrema() + ) + return self.im.entropy() + + def paste( + self, + im: Image | str | float | tuple[float, ...], + box: Image | tuple[int, int, int, int] | tuple[int, int] | None = None, + mask: Image | None = None, + ) -> None: + """ + Pastes another image into this image. The box argument is either + a 2-tuple giving the upper left corner, a 4-tuple defining the + left, upper, right, and lower pixel coordinate, or None (same as + (0, 0)). See :ref:`coordinate-system`. If a 4-tuple is given, the size + of the pasted image must match the size of the region. + + If the modes don't match, the pasted image is converted to the mode of + this image (see the :py:meth:`~PIL.Image.Image.convert` method for + details). + + Instead of an image, the source can be a integer or tuple + containing pixel values. The method then fills the region + with the given color. When creating RGB images, you can + also use color strings as supported by the ImageColor module. See + :ref:`colors` for more information. + + If a mask is given, this method updates only the regions + indicated by the mask. You can use either "1", "L", "LA", "RGBA" + or "RGBa" images (if present, the alpha band is used as mask). + Where the mask is 255, the given image is copied as is. Where + the mask is 0, the current value is preserved. Intermediate + values will mix the two images together, including their alpha + channels if they have them. + + See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to + combine images with respect to their alpha channels. + + :param im: Source image or pixel value (integer, float or tuple). + :param box: An optional 4-tuple giving the region to paste into. + If a 2-tuple is used instead, it's treated as the upper left + corner. If omitted or None, the source is pasted into the + upper left corner. + + If an image is given as the second argument and there is no + third, the box defaults to (0, 0), and the second argument + is interpreted as a mask image. + :param mask: An optional mask image. + """ + + if isinstance(box, Image): + if mask is not None: + msg = "If using second argument as mask, third argument must be None" + raise ValueError(msg) + # abbreviated paste(im, mask) syntax + mask = box + box = None + + if box is None: + box = (0, 0) + + if len(box) == 2: + # upper left corner given; get size from image or mask + if isinstance(im, Image): + size = im.size + elif isinstance(mask, Image): + size = mask.size + else: + # FIXME: use self.size here? + msg = "cannot determine region size; use 4-item box" + raise ValueError(msg) + box += (box[0] + size[0], box[1] + size[1]) + + source: core.ImagingCore | str | float | tuple[float, ...] + if isinstance(im, str): + from . import ImageColor + + source = ImageColor.getcolor(im, self.mode) + elif isinstance(im, Image): + im.load() + if self.mode != im.mode: + if self.mode != "RGB" or im.mode not in ("LA", "RGBA", "RGBa"): + # should use an adapter for this! + im = im.convert(self.mode) + source = im.im + else: + source = im + + self._ensure_mutable() + + if mask: + mask.load() + self.im.paste(source, box, mask.im) + else: + self.im.paste(source, box) + + def alpha_composite( + self, im: Image, dest: Sequence[int] = (0, 0), source: Sequence[int] = (0, 0) + ) -> None: + """'In-place' analog of Image.alpha_composite. Composites an image + onto this image. + + :param im: image to composite over this one + :param dest: Optional 2 tuple (left, top) specifying the upper + left corner in this (destination) image. + :param source: Optional 2 (left, top) tuple for the upper left + corner in the overlay source image, or 4 tuple (left, top, right, + bottom) for the bounds of the source rectangle + + Performance Note: Not currently implemented in-place in the core layer. + """ + + if not isinstance(source, (list, tuple)): + msg = "Source must be a list or tuple" + raise ValueError(msg) + if not isinstance(dest, (list, tuple)): + msg = "Destination must be a list or tuple" + raise ValueError(msg) + + if len(source) == 4: + overlay_crop_box = tuple(source) + elif len(source) == 2: + overlay_crop_box = tuple(source) + im.size + else: + msg = "Source must be a sequence of length 2 or 4" + raise ValueError(msg) + + if not len(dest) == 2: + msg = "Destination must be a sequence of length 2" + raise ValueError(msg) + if min(source) < 0: + msg = "Source must be non-negative" + raise ValueError(msg) + + # over image, crop if it's not the whole image. + if overlay_crop_box == (0, 0) + im.size: + overlay = im + else: + overlay = im.crop(overlay_crop_box) + + # target for the paste + box = tuple(dest) + (dest[0] + overlay.width, dest[1] + overlay.height) + + # destination image. don't copy if we're using the whole image. + if box == (0, 0) + self.size: + background = self + else: + background = self.crop(box) + + result = alpha_composite(background, overlay) + self.paste(result, box) + + def point( + self, + lut: ( + Sequence[float] + | NumpyArray + | Callable[[int], float] + | Callable[[ImagePointTransform], ImagePointTransform | float] + | ImagePointHandler + ), + mode: str | None = None, + ) -> Image: + """ + Maps this image through a lookup table or function. + + :param lut: A lookup table, containing 256 (or 65536 if + self.mode=="I" and mode == "L") values per band in the + image. A function can be used instead, it should take a + single argument. The function is called once for each + possible pixel value, and the resulting table is applied to + all bands of the image. + + It may also be an :py:class:`~PIL.Image.ImagePointHandler` + object:: + + class Example(Image.ImagePointHandler): + def point(self, im: Image) -> Image: + # Return result + :param mode: Output mode (default is same as input). This can only be used if + the source image has mode "L" or "P", and the output has mode "1" or the + source image mode is "I" and the output mode is "L". + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + self.load() + + if isinstance(lut, ImagePointHandler): + return lut.point(self) + + if callable(lut): + # if it isn't a list, it should be a function + if self.mode in ("I", "I;16", "F"): + # check if the function can be used with point_transform + # UNDONE wiredfool -- I think this prevents us from ever doing + # a gamma function point transform on > 8bit images. + scale, offset = _getscaleoffset(lut) # type: ignore[arg-type] + return self._new(self.im.point_transform(scale, offset)) + # for other modes, convert the function to a table + flatLut = [lut(i) for i in range(256)] * self.im.bands # type: ignore[arg-type] + else: + flatLut = lut + + if self.mode == "F": + # FIXME: _imaging returns a confusing error message for this case + msg = "point operation not supported for this mode" + raise ValueError(msg) + + if mode != "F": + flatLut = [round(i) for i in flatLut] + return self._new(self.im.point(flatLut, mode)) + + def putalpha(self, alpha: Image | int) -> None: + """ + Adds or replaces the alpha layer in this image. If the image + does not have an alpha layer, it's converted to "LA" or "RGBA". + The new layer must be either "L" or "1". + + :param alpha: The new alpha layer. This can either be an "L" or "1" + image having the same size as this image, or an integer. + """ + + self._ensure_mutable() + + if self.mode not in ("LA", "PA", "RGBA"): + # attempt to promote self to a matching alpha mode + try: + mode = getmodebase(self.mode) + "A" + try: + self.im.setmode(mode) + except (AttributeError, ValueError) as e: + # do things the hard way + im = self.im.convert(mode) + if im.mode not in ("LA", "PA", "RGBA"): + msg = "alpha channel could not be added" + raise ValueError(msg) from e # sanity check + self.im = im + self._mode = self.im.mode + except KeyError as e: + msg = "illegal image mode" + raise ValueError(msg) from e + + if self.mode in ("LA", "PA"): + band = 1 + else: + band = 3 + + if isinstance(alpha, Image): + # alpha layer + if alpha.mode not in ("1", "L"): + msg = "illegal image mode" + raise ValueError(msg) + alpha.load() + if alpha.mode == "1": + alpha = alpha.convert("L") + else: + # constant alpha + try: + self.im.fillband(band, alpha) + except (AttributeError, ValueError): + # do things the hard way + alpha = new("L", self.size, alpha) + else: + return + + self.im.putband(alpha.im, band) + + def putdata( + self, + data: Sequence[float] | Sequence[Sequence[int]] | core.ImagingCore | NumpyArray, + scale: float = 1.0, + offset: float = 0.0, + ) -> None: + """ + Copies pixel data from a flattened sequence object into the image. The + values should start at the upper left corner (0, 0), continue to the + end of the line, followed directly by the first value of the second + line, and so on. Data will be read until either the image or the + sequence ends. The scale and offset values are used to adjust the + sequence values: **pixel = value*scale + offset**. + + :param data: A flattened sequence object. See :ref:`colors` for more + information about values. + :param scale: An optional scale value. The default is 1.0. + :param offset: An optional offset value. The default is 0.0. + """ + + self._ensure_mutable() + + self.im.putdata(data, scale, offset) + + def putpalette( + self, + data: ImagePalette.ImagePalette | bytes | Sequence[int], + rawmode: str = "RGB", + ) -> None: + """ + Attaches a palette to this image. The image must be a "P", "PA", "L" + or "LA" image. + + The palette sequence must contain at most 256 colors, made up of one + integer value for each channel in the raw mode. + For example, if the raw mode is "RGB", then it can contain at most 768 + values, made up of red, green and blue values for the corresponding pixel + index in the 256 colors. + If the raw mode is "RGBA", then it can contain at most 1024 values, + containing red, green, blue and alpha values. + + Alternatively, an 8-bit string may be used instead of an integer sequence. + + :param data: A palette sequence (either a list or a string). + :param rawmode: The raw mode of the palette. Either "RGB", "RGBA", or a mode + that can be transformed to "RGB" or "RGBA" (e.g. "R", "BGR;15", "RGBA;L"). + """ + from . import ImagePalette + + if self.mode not in ("L", "LA", "P", "PA"): + msg = "illegal image mode" + raise ValueError(msg) + if isinstance(data, ImagePalette.ImagePalette): + if data.rawmode is not None: + palette = ImagePalette.raw(data.rawmode, data.palette) + else: + palette = ImagePalette.ImagePalette(palette=data.palette) + palette.dirty = 1 + else: + if not isinstance(data, bytes): + data = bytes(data) + palette = ImagePalette.raw(rawmode, data) + self._mode = "PA" if "A" in self.mode else "P" + self.palette = palette + self.palette.mode = "RGBA" if "A" in rawmode else "RGB" + self.load() # install new palette + + def putpixel( + self, xy: tuple[int, int], value: float | tuple[int, ...] | list[int] + ) -> None: + """ + Modifies the pixel at the given position. The color is given as + a single numerical value for single-band images, and a tuple for + multi-band images. In addition to this, RGB and RGBA tuples are + accepted for P and PA images. See :ref:`colors` for more information. + + Note that this method is relatively slow. For more extensive changes, + use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw` + module instead. + + See: + + * :py:meth:`~PIL.Image.Image.paste` + * :py:meth:`~PIL.Image.Image.putdata` + * :py:mod:`~PIL.ImageDraw` + + :param xy: The pixel coordinate, given as (x, y). See + :ref:`coordinate-system`. + :param value: The pixel value. + """ + + self._ensure_mutable() + + if ( + self.mode in ("P", "PA") + and isinstance(value, (list, tuple)) + and len(value) in [3, 4] + ): + # RGB or RGBA value for a P or PA image + if self.mode == "PA": + alpha = value[3] if len(value) == 4 else 255 + value = value[:3] + assert self.palette is not None + palette_index = self.palette.getcolor(tuple(value), self) + value = (palette_index, alpha) if self.mode == "PA" else palette_index + return self.im.putpixel(xy, value) + + def remap_palette( + self, dest_map: list[int], source_palette: bytes | bytearray | None = None + ) -> Image: + """ + Rewrites the image to reorder the palette. + + :param dest_map: A list of indexes into the original palette. + e.g. ``[1,0]`` would swap a two item palette, and ``list(range(256))`` + is the identity transform. + :param source_palette: Bytes or None. + :returns: An :py:class:`~PIL.Image.Image` object. + + """ + from . import ImagePalette + + if self.mode not in ("L", "P"): + msg = "illegal image mode" + raise ValueError(msg) + + bands = 3 + palette_mode = "RGB" + if source_palette is None: + if self.mode == "P": + self.load() + palette_mode = self.im.getpalettemode() + if palette_mode == "RGBA": + bands = 4 + source_palette = self.im.getpalette(palette_mode, palette_mode) + else: # L-mode + source_palette = bytearray(i // 3 for i in range(768)) + elif len(source_palette) > 768: + bands = 4 + palette_mode = "RGBA" + + palette_bytes = b"" + new_positions = [0] * 256 + + # pick only the used colors from the palette + for i, oldPosition in enumerate(dest_map): + palette_bytes += source_palette[ + oldPosition * bands : oldPosition * bands + bands + ] + new_positions[oldPosition] = i + + # replace the palette color id of all pixel with the new id + + # Palette images are [0..255], mapped through a 1 or 3 + # byte/color map. We need to remap the whole image + # from palette 1 to palette 2. New_positions is + # an array of indexes into palette 1. Palette 2 is + # palette 1 with any holes removed. + + # We're going to leverage the convert mechanism to use the + # C code to remap the image from palette 1 to palette 2, + # by forcing the source image into 'L' mode and adding a + # mapping 'L' mode palette, then converting back to 'L' + # sans palette thus converting the image bytes, then + # assigning the optimized RGB palette. + + # perf reference, 9500x4000 gif, w/~135 colors + # 14 sec prepatch, 1 sec postpatch with optimization forced. + + mapping_palette = bytearray(new_positions) + + m_im = self.copy() + m_im._mode = "P" + + m_im.palette = ImagePalette.ImagePalette( + palette_mode, palette=mapping_palette * bands + ) + # possibly set palette dirty, then + # m_im.putpalette(mapping_palette, 'L') # converts to 'P' + # or just force it. + # UNDONE -- this is part of the general issue with palettes + m_im.im.putpalette(palette_mode, palette_mode + ";L", m_im.palette.tobytes()) + + m_im = m_im.convert("L") + + m_im.putpalette(palette_bytes, palette_mode) + m_im.palette = ImagePalette.ImagePalette(palette_mode, palette=palette_bytes) + + if "transparency" in self.info: + try: + m_im.info["transparency"] = dest_map.index(self.info["transparency"]) + except ValueError: + if "transparency" in m_im.info: + del m_im.info["transparency"] + + return m_im + + def _get_safe_box( + self, + size: tuple[int, int], + resample: Resampling, + box: tuple[float, float, float, float], + ) -> tuple[int, int, int, int]: + """Expands the box so it includes adjacent pixels + that may be used by resampling with the given resampling filter. + """ + filter_support = _filters_support[resample] - 0.5 + scale_x = (box[2] - box[0]) / size[0] + scale_y = (box[3] - box[1]) / size[1] + support_x = filter_support * scale_x + support_y = filter_support * scale_y + + return ( + max(0, int(box[0] - support_x)), + max(0, int(box[1] - support_y)), + min(self.size[0], math.ceil(box[2] + support_x)), + min(self.size[1], math.ceil(box[3] + support_y)), + ) + + def resize( + self, + size: tuple[int, int] | list[int] | NumpyArray, + resample: int | None = None, + box: tuple[float, float, float, float] | None = None, + reducing_gap: float | None = None, + ) -> Image: + """ + Returns a resized copy of this image. + + :param size: The requested size in pixels, as a tuple or array: + (width, height). + :param resample: An optional resampling filter. This can be + one of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`, + :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`, + :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`. + If the image has mode "1" or "P", it is always set to + :py:data:`Resampling.NEAREST`. Otherwise, the default filter is + :py:data:`Resampling.BICUBIC`. See: :ref:`concept-filters`. + :param box: An optional 4-tuple of floats providing + the source image region to be scaled. + The values must be within (0, 0, width, height) rectangle. + If omitted or None, the entire source is used. + :param reducing_gap: Apply optimization by resizing the image + in two steps. First, reducing the image by integer times + using :py:meth:`~PIL.Image.Image.reduce`. + Second, resizing using regular resampling. The last step + changes size no less than by ``reducing_gap`` times. + ``reducing_gap`` may be None (no first step is performed) + or should be greater than 1.0. The bigger ``reducing_gap``, + the closer the result to the fair resampling. + The smaller ``reducing_gap``, the faster resizing. + With ``reducing_gap`` greater or equal to 3.0, the result is + indistinguishable from fair resampling in most cases. + The default value is None (no optimization). + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + if resample is None: + resample = Resampling.BICUBIC + elif resample not in ( + Resampling.NEAREST, + Resampling.BILINEAR, + Resampling.BICUBIC, + Resampling.LANCZOS, + Resampling.BOX, + Resampling.HAMMING, + ): + msg = f"Unknown resampling filter ({resample})." + + filters = [ + f"{filter[1]} ({filter[0]})" + for filter in ( + (Resampling.NEAREST, "Image.Resampling.NEAREST"), + (Resampling.LANCZOS, "Image.Resampling.LANCZOS"), + (Resampling.BILINEAR, "Image.Resampling.BILINEAR"), + (Resampling.BICUBIC, "Image.Resampling.BICUBIC"), + (Resampling.BOX, "Image.Resampling.BOX"), + (Resampling.HAMMING, "Image.Resampling.HAMMING"), + ) + ] + msg += f" Use {', '.join(filters[:-1])} or {filters[-1]}" + raise ValueError(msg) + + if reducing_gap is not None and reducing_gap < 1.0: + msg = "reducing_gap must be 1.0 or greater" + raise ValueError(msg) + + if box is None: + box = (0, 0) + self.size + + size = tuple(size) + if self.size == size and box == (0, 0) + self.size: + return self.copy() + + if self.mode in ("1", "P"): + resample = Resampling.NEAREST + + if self.mode in ["LA", "RGBA"] and resample != Resampling.NEAREST: + im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode]) + im = im.resize(size, resample, box) + return im.convert(self.mode) + + self.load() + + if reducing_gap is not None and resample != Resampling.NEAREST: + factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1 + factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1 + if factor_x > 1 or factor_y > 1: + reduce_box = self._get_safe_box(size, cast(Resampling, resample), box) + factor = (factor_x, factor_y) + self = ( + self.reduce(factor, box=reduce_box) + if callable(self.reduce) + else Image.reduce(self, factor, box=reduce_box) + ) + box = ( + (box[0] - reduce_box[0]) / factor_x, + (box[1] - reduce_box[1]) / factor_y, + (box[2] - reduce_box[0]) / factor_x, + (box[3] - reduce_box[1]) / factor_y, + ) + + return self._new(self.im.resize(size, resample, box)) + + def reduce( + self, + factor: int | tuple[int, int], + box: tuple[int, int, int, int] | None = None, + ) -> Image: + """ + Returns a copy of the image reduced ``factor`` times. + If the size of the image is not dividable by ``factor``, + the resulting size will be rounded up. + + :param factor: A greater than 0 integer or tuple of two integers + for width and height separately. + :param box: An optional 4-tuple of ints providing + the source image region to be reduced. + The values must be within ``(0, 0, width, height)`` rectangle. + If omitted or ``None``, the entire source is used. + """ + if not isinstance(factor, (list, tuple)): + factor = (factor, factor) + + if box is None: + box = (0, 0) + self.size + + if factor == (1, 1) and box == (0, 0) + self.size: + return self.copy() + + if self.mode in ["LA", "RGBA"]: + im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode]) + im = im.reduce(factor, box) + return im.convert(self.mode) + + self.load() + + return self._new(self.im.reduce(factor, box)) + + def rotate( + self, + angle: float, + resample: Resampling = Resampling.NEAREST, + expand: int | bool = False, + center: tuple[float, float] | None = None, + translate: tuple[int, int] | None = None, + fillcolor: float | tuple[float, ...] | str | None = None, + ) -> Image: + """ + Returns a rotated copy of this image. This method returns a + copy of this image, rotated the given number of degrees counter + clockwise around its centre. + + :param angle: In degrees counter clockwise. + :param resample: An optional resampling filter. This can be + one of :py:data:`Resampling.NEAREST` (use nearest neighbour), + :py:data:`Resampling.BILINEAR` (linear interpolation in a 2x2 + environment), or :py:data:`Resampling.BICUBIC` (cubic spline + interpolation in a 4x4 environment). If omitted, or if the image has + mode "1" or "P", it is set to :py:data:`Resampling.NEAREST`. + See :ref:`concept-filters`. + :param expand: Optional expansion flag. If true, expands the output + image to make it large enough to hold the entire rotated image. + If false or omitted, make the output image the same size as the + input image. Note that the expand flag assumes rotation around + the center and no translation. + :param center: Optional center of rotation (a 2-tuple). Origin is + the upper left corner. Default is the center of the image. + :param translate: An optional post-rotate translation (a 2-tuple). + :param fillcolor: An optional color for area outside the rotated image. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + angle = angle % 360.0 + + # Fast paths regardless of filter, as long as we're not + # translating or changing the center. + if not (center or translate): + if angle == 0: + return self.copy() + if angle == 180: + return self.transpose(Transpose.ROTATE_180) + if angle in (90, 270) and (expand or self.width == self.height): + return self.transpose( + Transpose.ROTATE_90 if angle == 90 else Transpose.ROTATE_270 + ) + + # Calculate the affine matrix. Note that this is the reverse + # transformation (from destination image to source) because we + # want to interpolate the (discrete) destination pixel from + # the local area around the (floating) source pixel. + + # The matrix we actually want (note that it operates from the right): + # (1, 0, tx) (1, 0, cx) ( cos a, sin a, 0) (1, 0, -cx) + # (0, 1, ty) * (0, 1, cy) * (-sin a, cos a, 0) * (0, 1, -cy) + # (0, 0, 1) (0, 0, 1) ( 0, 0, 1) (0, 0, 1) + + # The reverse matrix is thus: + # (1, 0, cx) ( cos -a, sin -a, 0) (1, 0, -cx) (1, 0, -tx) + # (0, 1, cy) * (-sin -a, cos -a, 0) * (0, 1, -cy) * (0, 1, -ty) + # (0, 0, 1) ( 0, 0, 1) (0, 0, 1) (0, 0, 1) + + # In any case, the final translation may be updated at the end to + # compensate for the expand flag. + + w, h = self.size + + if translate is None: + post_trans = (0, 0) + else: + post_trans = translate + if center is None: + center = (w / 2, h / 2) + + angle = -math.radians(angle) + matrix = [ + round(math.cos(angle), 15), + round(math.sin(angle), 15), + 0.0, + round(-math.sin(angle), 15), + round(math.cos(angle), 15), + 0.0, + ] + + def transform(x: float, y: float, matrix: list[float]) -> tuple[float, float]: + (a, b, c, d, e, f) = matrix + return a * x + b * y + c, d * x + e * y + f + + matrix[2], matrix[5] = transform( + -center[0] - post_trans[0], -center[1] - post_trans[1], matrix + ) + matrix[2] += center[0] + matrix[5] += center[1] + + if expand: + # calculate output size + xx = [] + yy = [] + for x, y in ((0, 0), (w, 0), (w, h), (0, h)): + transformed_x, transformed_y = transform(x, y, matrix) + xx.append(transformed_x) + yy.append(transformed_y) + nw = math.ceil(max(xx)) - math.floor(min(xx)) + nh = math.ceil(max(yy)) - math.floor(min(yy)) + + # We multiply a translation matrix from the right. Because of its + # special form, this is the same as taking the image of the + # translation vector as new translation vector. + matrix[2], matrix[5] = transform(-(nw - w) / 2.0, -(nh - h) / 2.0, matrix) + w, h = nw, nh + + return self.transform( + (w, h), Transform.AFFINE, matrix, resample, fillcolor=fillcolor + ) + + def save( + self, fp: StrOrBytesPath | IO[bytes], format: str | None = None, **params: Any + ) -> None: + """ + Saves this image under the given filename. If no format is + specified, the format to use is determined from the filename + extension, if possible. + + Keyword options can be used to provide additional instructions + to the writer. If a writer doesn't recognise an option, it is + silently ignored. The available options are described in the + :doc:`image format documentation + <../handbook/image-file-formats>` for each writer. + + You can use a file object instead of a filename. In this case, + you must always specify the format. The file object must + implement the ``seek``, ``tell``, and ``write`` + methods, and be opened in binary mode. + + :param fp: A filename (string), os.PathLike object or file object. + :param format: Optional format override. If omitted, the + format to use is determined from the filename extension. + If a file object was used instead of a filename, this + parameter should always be used. + :param params: Extra parameters to the image writer. These can also be + set on the image itself through ``encoderinfo``. This is useful when + saving multiple images:: + + # Saving XMP data to a single image + from PIL import Image + red = Image.new("RGB", (1, 1), "#f00") + red.save("out.mpo", xmp=b"test") + + # Saving XMP data to the second frame of an image + from PIL import Image + black = Image.new("RGB", (1, 1)) + red = Image.new("RGB", (1, 1), "#f00") + red.encoderinfo = {"xmp": b"test"} + black.save("out.mpo", save_all=True, append_images=[red]) + :returns: None + :exception ValueError: If the output format could not be determined + from the file name. Use the format option to solve this. + :exception OSError: If the file could not be written. The file + may have been created, and may contain partial data. + """ + + filename: str | bytes = "" + open_fp = False + if is_path(fp): + filename = os.fspath(fp) + open_fp = True + elif fp == sys.stdout: + try: + fp = sys.stdout.buffer + except AttributeError: + pass + if not filename and hasattr(fp, "name") and is_path(fp.name): + # only set the name for metadata purposes + filename = os.fspath(fp.name) + + preinit() + + filename_ext = os.path.splitext(filename)[1].lower() + ext = filename_ext.decode() if isinstance(filename_ext, bytes) else filename_ext + + if not format: + if ext not in EXTENSION: + init() + try: + format = EXTENSION[ext] + except KeyError as e: + msg = f"unknown file extension: {ext}" + raise ValueError(msg) from e + + from . import ImageFile + + # may mutate self! + if isinstance(self, ImageFile.ImageFile) and os.path.abspath( + filename + ) == os.path.abspath(self.filename): + self._ensure_mutable() + else: + self.load() + + save_all = params.pop("save_all", None) + self._default_encoderinfo = params + encoderinfo = getattr(self, "encoderinfo", {}) + self._attach_default_encoderinfo(self) + self.encoderconfig: tuple[Any, ...] = () + + if format.upper() not in SAVE: + init() + if save_all or ( + save_all is None + and params.get("append_images") + and format.upper() in SAVE_ALL + ): + save_handler = SAVE_ALL[format.upper()] + else: + save_handler = SAVE[format.upper()] + + created = False + if open_fp: + created = not os.path.exists(filename) + if params.get("append", False): + # Open also for reading ("+"), because TIFF save_all + # writer needs to go back and edit the written data. + fp = builtins.open(filename, "r+b") + else: + fp = builtins.open(filename, "w+b") + else: + fp = cast(IO[bytes], fp) + + try: + save_handler(self, fp, filename) + except Exception: + if open_fp: + fp.close() + if created: + try: + os.remove(filename) + except PermissionError: + pass + raise + finally: + self.encoderinfo = encoderinfo + if open_fp: + fp.close() + + def _attach_default_encoderinfo(self, im: Image) -> dict[str, Any]: + encoderinfo = getattr(self, "encoderinfo", {}) + self.encoderinfo = {**im._default_encoderinfo, **encoderinfo} + return encoderinfo + + def seek(self, frame: int) -> None: + """ + Seeks to the given frame in this sequence file. If you seek + beyond the end of the sequence, the method raises an + ``EOFError`` exception. When a sequence file is opened, the + library automatically seeks to frame 0. + + See :py:meth:`~PIL.Image.Image.tell`. + + If defined, :attr:`~PIL.Image.Image.n_frames` refers to the + number of available frames. + + :param frame: Frame number, starting at 0. + :exception EOFError: If the call attempts to seek beyond the end + of the sequence. + """ + + # overridden by file handlers + if frame != 0: + msg = "no more images in file" + raise EOFError(msg) + + def show(self, title: str | None = None) -> None: + """ + Displays this image. This method is mainly intended for debugging purposes. + + This method calls :py:func:`PIL.ImageShow.show` internally. You can use + :py:func:`PIL.ImageShow.register` to override its default behaviour. + + The image is first saved to a temporary file. By default, it will be in + PNG format. + + On Unix, the image is then opened using the **xdg-open**, **display**, + **gm**, **eog** or **xv** utility, depending on which one can be found. + + On macOS, the image is opened with the native Preview application. + + On Windows, the image is opened with the standard PNG display utility. + + :param title: Optional title to use for the image window, where possible. + """ + + from . import ImageShow + + ImageShow.show(self, title) + + def split(self) -> tuple[Image, ...]: + """ + Split this image into individual bands. This method returns a + tuple of individual image bands from an image. For example, + splitting an "RGB" image creates three new images each + containing a copy of one of the original bands (red, green, + blue). + + If you need only one band, :py:meth:`~PIL.Image.Image.getchannel` + method can be more convenient and faster. + + :returns: A tuple containing bands. + """ + + self.load() + if self.im.bands == 1: + return (self.copy(),) + return tuple(map(self._new, self.im.split())) + + def getchannel(self, channel: int | str) -> Image: + """ + Returns an image containing a single channel of the source image. + + :param channel: What channel to return. Could be index + (0 for "R" channel of "RGB") or channel name + ("A" for alpha channel of "RGBA"). + :returns: An image in "L" mode. + + .. versionadded:: 4.3.0 + """ + self.load() + + if isinstance(channel, str): + try: + channel = self.getbands().index(channel) + except ValueError as e: + msg = f'The image has no channel "{channel}"' + raise ValueError(msg) from e + + return self._new(self.im.getband(channel)) + + def tell(self) -> int: + """ + Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`. + + If defined, :attr:`~PIL.Image.Image.n_frames` refers to the + number of available frames. + + :returns: Frame number, starting with 0. + """ + return 0 + + def thumbnail( + self, + size: tuple[float, float], + resample: Resampling = Resampling.BICUBIC, + reducing_gap: float | None = 2.0, + ) -> None: + """ + Make this image into a thumbnail. This method modifies the + image to contain a thumbnail version of itself, no larger than + the given size. This method calculates an appropriate thumbnail + size to preserve the aspect of the image, calls the + :py:meth:`~PIL.Image.Image.draft` method to configure the file reader + (where applicable), and finally resizes the image. + + Note that this function modifies the :py:class:`~PIL.Image.Image` + object in place. If you need to use the full resolution image as well, + apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original + image. + + :param size: The requested size in pixels, as a 2-tuple: + (width, height). + :param resample: Optional resampling filter. This can be one + of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`, + :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`, + :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`. + If omitted, it defaults to :py:data:`Resampling.BICUBIC`. + (was :py:data:`Resampling.NEAREST` prior to version 2.5.0). + See: :ref:`concept-filters`. + :param reducing_gap: Apply optimization by resizing the image + in two steps. First, reducing the image by integer times + using :py:meth:`~PIL.Image.Image.reduce` or + :py:meth:`~PIL.Image.Image.draft` for JPEG images. + Second, resizing using regular resampling. The last step + changes size no less than by ``reducing_gap`` times. + ``reducing_gap`` may be None (no first step is performed) + or should be greater than 1.0. The bigger ``reducing_gap``, + the closer the result to the fair resampling. + The smaller ``reducing_gap``, the faster resizing. + With ``reducing_gap`` greater or equal to 3.0, the result is + indistinguishable from fair resampling in most cases. + The default value is 2.0 (very close to fair resampling + while still being faster in many cases). + :returns: None + """ + + provided_size = tuple(map(math.floor, size)) + + def preserve_aspect_ratio() -> tuple[int, int] | None: + def round_aspect(number: float, key: Callable[[int], float]) -> int: + return max(min(math.floor(number), math.ceil(number), key=key), 1) + + x, y = provided_size + if x >= self.width and y >= self.height: + return None + + aspect = self.width / self.height + if x / y >= aspect: + x = round_aspect(y * aspect, key=lambda n: abs(aspect - n / y)) + else: + y = round_aspect( + x / aspect, key=lambda n: 0 if n == 0 else abs(aspect - x / n) + ) + return x, y + + preserved_size = preserve_aspect_ratio() + if preserved_size is None: + return + final_size = preserved_size + + box = None + if reducing_gap is not None: + res = self.draft( + None, (int(size[0] * reducing_gap), int(size[1] * reducing_gap)) + ) + if res is not None: + box = res[1] + + if self.size != final_size: + im = self.resize(final_size, resample, box=box, reducing_gap=reducing_gap) + + self.im = im.im + self._size = final_size + self._mode = self.im.mode + + self.readonly = 0 + + # FIXME: the different transform methods need further explanation + # instead of bloating the method docs, add a separate chapter. + def transform( + self, + size: tuple[int, int], + method: Transform | ImageTransformHandler | SupportsGetData, + data: Sequence[Any] | None = None, + resample: int = Resampling.NEAREST, + fill: int = 1, + fillcolor: float | tuple[float, ...] | str | None = None, + ) -> Image: + """ + Transforms this image. This method creates a new image with the + given size, and the same mode as the original, and copies data + to the new image using the given transform. + + :param size: The output size in pixels, as a 2-tuple: + (width, height). + :param method: The transformation method. This is one of + :py:data:`Transform.EXTENT` (cut out a rectangular subregion), + :py:data:`Transform.AFFINE` (affine transform), + :py:data:`Transform.PERSPECTIVE` (perspective transform), + :py:data:`Transform.QUAD` (map a quadrilateral to a rectangle), or + :py:data:`Transform.MESH` (map a number of source quadrilaterals + in one operation). + + It may also be an :py:class:`~PIL.Image.ImageTransformHandler` + object:: + + class Example(Image.ImageTransformHandler): + def transform(self, size, data, resample, fill=1): + # Return result + + Implementations of :py:class:`~PIL.Image.ImageTransformHandler` + for some of the :py:class:`Transform` methods are provided + in :py:mod:`~PIL.ImageTransform`. + + It may also be an object with a ``method.getdata`` method + that returns a tuple supplying new ``method`` and ``data`` values:: + + class Example: + def getdata(self): + method = Image.Transform.EXTENT + data = (0, 0, 100, 100) + return method, data + :param data: Extra data to the transformation method. + :param resample: Optional resampling filter. It can be one of + :py:data:`Resampling.NEAREST` (use nearest neighbour), + :py:data:`Resampling.BILINEAR` (linear interpolation in a 2x2 + environment), or :py:data:`Resampling.BICUBIC` (cubic spline + interpolation in a 4x4 environment). If omitted, or if the image + has mode "1" or "P", it is set to :py:data:`Resampling.NEAREST`. + See: :ref:`concept-filters`. + :param fill: If ``method`` is an + :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of + the arguments passed to it. Otherwise, it is unused. + :param fillcolor: Optional fill color for the area outside the + transform in the output image. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + if self.mode in ("LA", "RGBA") and resample != Resampling.NEAREST: + return ( + self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode]) + .transform(size, method, data, resample, fill, fillcolor) + .convert(self.mode) + ) + + if isinstance(method, ImageTransformHandler): + return method.transform(size, self, resample=resample, fill=fill) + + if hasattr(method, "getdata"): + # compatibility w. old-style transform objects + method, data = method.getdata() + + if data is None: + msg = "missing method data" + raise ValueError(msg) + + im = new(self.mode, size, fillcolor) + if self.mode == "P" and self.palette: + im.palette = self.palette.copy() + im.info = self.info.copy() + if method == Transform.MESH: + # list of quads + for box, quad in data: + im.__transformer( + box, self, Transform.QUAD, quad, resample, fillcolor is None + ) + else: + im.__transformer( + (0, 0) + size, self, method, data, resample, fillcolor is None + ) + + return im + + def __transformer( + self, + box: tuple[int, int, int, int], + image: Image, + method: Transform, + data: Sequence[float], + resample: int = Resampling.NEAREST, + fill: bool = True, + ) -> None: + w = box[2] - box[0] + h = box[3] - box[1] + + if method == Transform.AFFINE: + data = data[:6] + + elif method == Transform.EXTENT: + # convert extent to an affine transform + x0, y0, x1, y1 = data + xs = (x1 - x0) / w + ys = (y1 - y0) / h + method = Transform.AFFINE + data = (xs, 0, x0, 0, ys, y0) + + elif method == Transform.PERSPECTIVE: + data = data[:8] + + elif method == Transform.QUAD: + # quadrilateral warp. data specifies the four corners + # given as NW, SW, SE, and NE. + nw = data[:2] + sw = data[2:4] + se = data[4:6] + ne = data[6:8] + x0, y0 = nw + As = 1.0 / w + At = 1.0 / h + data = ( + x0, + (ne[0] - x0) * As, + (sw[0] - x0) * At, + (se[0] - sw[0] - ne[0] + x0) * As * At, + y0, + (ne[1] - y0) * As, + (sw[1] - y0) * At, + (se[1] - sw[1] - ne[1] + y0) * As * At, + ) + + else: + msg = "unknown transformation method" + raise ValueError(msg) + + if resample not in ( + Resampling.NEAREST, + Resampling.BILINEAR, + Resampling.BICUBIC, + ): + if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS): + unusable: dict[int, str] = { + Resampling.BOX: "Image.Resampling.BOX", + Resampling.HAMMING: "Image.Resampling.HAMMING", + Resampling.LANCZOS: "Image.Resampling.LANCZOS", + } + msg = unusable[resample] + f" ({resample}) cannot be used." + else: + msg = f"Unknown resampling filter ({resample})." + + filters = [ + f"{filter[1]} ({filter[0]})" + for filter in ( + (Resampling.NEAREST, "Image.Resampling.NEAREST"), + (Resampling.BILINEAR, "Image.Resampling.BILINEAR"), + (Resampling.BICUBIC, "Image.Resampling.BICUBIC"), + ) + ] + msg += f" Use {', '.join(filters[:-1])} or {filters[-1]}" + raise ValueError(msg) + + image.load() + + self.load() + + if image.mode in ("1", "P"): + resample = Resampling.NEAREST + + self.im.transform(box, image.im, method, data, resample, fill) + + def transpose(self, method: Transpose) -> Image: + """ + Transpose image (flip or rotate in 90 degree steps) + + :param method: One of :py:data:`Transpose.FLIP_LEFT_RIGHT`, + :py:data:`Transpose.FLIP_TOP_BOTTOM`, :py:data:`Transpose.ROTATE_90`, + :py:data:`Transpose.ROTATE_180`, :py:data:`Transpose.ROTATE_270`, + :py:data:`Transpose.TRANSPOSE` or :py:data:`Transpose.TRANSVERSE`. + :returns: Returns a flipped or rotated copy of this image. + """ + + self.load() + return self._new(self.im.transpose(method)) + + def effect_spread(self, distance: int) -> Image: + """ + Randomly spread pixels in an image. + + :param distance: Distance to spread pixels. + """ + self.load() + return self._new(self.im.effect_spread(distance)) + + def toqimage(self) -> ImageQt.ImageQt: + """Returns a QImage copy of this image""" + from . import ImageQt + + if not ImageQt.qt_is_installed: + msg = "Qt bindings are not installed" + raise ImportError(msg) + return ImageQt.toqimage(self) + + def toqpixmap(self) -> ImageQt.QPixmap: + """Returns a QPixmap copy of this image""" + from . import ImageQt + + if not ImageQt.qt_is_installed: + msg = "Qt bindings are not installed" + raise ImportError(msg) + return ImageQt.toqpixmap(self) + + +# -------------------------------------------------------------------- +# Abstract handlers. + + +class ImagePointHandler(abc.ABC): + """ + Used as a mixin by point transforms + (for use with :py:meth:`~PIL.Image.Image.point`) + """ + + @abc.abstractmethod + def point(self, im: Image) -> Image: + pass + + +class ImageTransformHandler(abc.ABC): + """ + Used as a mixin by geometry transforms + (for use with :py:meth:`~PIL.Image.Image.transform`) + """ + + @abc.abstractmethod + def transform( + self, + size: tuple[int, int], + image: Image, + **options: Any, + ) -> Image: + pass + + +# -------------------------------------------------------------------- +# Factories + + +def _check_size(size: Any) -> None: + """ + Common check to enforce type and sanity check on size tuples + + :param size: Should be a 2 tuple of (width, height) + :returns: None, or raises a ValueError + """ + + if not isinstance(size, (list, tuple)): + msg = "Size must be a list or tuple" + raise ValueError(msg) + if len(size) != 2: + msg = "Size must be a sequence of length 2" + raise ValueError(msg) + if size[0] < 0 or size[1] < 0: + msg = "Width and height must be >= 0" + raise ValueError(msg) + + +def new( + mode: str, + size: tuple[int, int] | list[int], + color: float | tuple[float, ...] | str | None = 0, +) -> Image: + """ + Creates a new image with the given mode and size. + + :param mode: The mode to use for the new image. See: + :ref:`concept-modes`. + :param size: A 2-tuple, containing (width, height) in pixels. + :param color: What color to use for the image. Default is black. If given, + this should be a single integer or floating point value for single-band + modes, and a tuple for multi-band modes (one value per band). When + creating RGB or HSV images, you can also use color strings as supported + by the ImageColor module. See :ref:`colors` for more information. If the + color is None, the image is not initialised. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + _check_size(size) + + if color is None: + # don't initialize + return Image()._new(core.new(mode, size)) + + if isinstance(color, str): + # css3-style specifier + + from . import ImageColor + + color = ImageColor.getcolor(color, mode) + + im = Image() + if ( + mode == "P" + and isinstance(color, (list, tuple)) + and all(isinstance(i, int) for i in color) + ): + color_ints: tuple[int, ...] = cast(tuple[int, ...], tuple(color)) + if len(color_ints) == 3 or len(color_ints) == 4: + # RGB or RGBA value for a P image + from . import ImagePalette + + im.palette = ImagePalette.ImagePalette() + color = im.palette.getcolor(color_ints) + return im._new(core.fill(mode, size, color)) + + +def frombytes( + mode: str, + size: tuple[int, int], + data: bytes | bytearray | SupportsArrayInterface, + decoder_name: str = "raw", + *args: Any, +) -> Image: + """ + Creates a copy of an image memory from pixel data in a buffer. + + In its simplest form, this function takes three arguments + (mode, size, and unpacked pixel data). + + You can also use any pixel decoder supported by PIL. For more + information on available decoders, see the section + :ref:`Writing Your Own File Codec `. + + Note that this function decodes pixel data only, not entire images. + If you have an entire image in a string, wrap it in a + :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load + it. + + :param mode: The image mode. See: :ref:`concept-modes`. + :param size: The image size. + :param data: A byte buffer containing raw data for the given mode. + :param decoder_name: What decoder to use. + :param args: Additional parameters for the given decoder. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + _check_size(size) + + im = new(mode, size) + if im.width != 0 and im.height != 0: + decoder_args: Any = args + if len(decoder_args) == 1 and isinstance(decoder_args[0], tuple): + # may pass tuple instead of argument list + decoder_args = decoder_args[0] + + if decoder_name == "raw" and decoder_args == (): + decoder_args = mode + + im.frombytes(data, decoder_name, decoder_args) + return im + + +def frombuffer( + mode: str, + size: tuple[int, int], + data: bytes | SupportsArrayInterface, + decoder_name: str = "raw", + *args: Any, +) -> Image: + """ + Creates an image memory referencing pixel data in a byte buffer. + + This function is similar to :py:func:`~PIL.Image.frombytes`, but uses data + in the byte buffer, where possible. This means that changes to the + original buffer object are reflected in this image). Not all modes can + share memory; supported modes include "L", "RGBX", "RGBA", and "CMYK". + + Note that this function decodes pixel data only, not entire images. + If you have an entire image file in a string, wrap it in a + :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load it. + + The default parameters used for the "raw" decoder differs from that used for + :py:func:`~PIL.Image.frombytes`. This is a bug, and will probably be fixed in a + future release. The current release issues a warning if you do this; to disable + the warning, you should provide the full set of parameters. See below for details. + + :param mode: The image mode. See: :ref:`concept-modes`. + :param size: The image size. + :param data: A bytes or other buffer object containing raw + data for the given mode. + :param decoder_name: What decoder to use. + :param args: Additional parameters for the given decoder. For the + default encoder ("raw"), it's recommended that you provide the + full set of parameters:: + + frombuffer(mode, size, data, "raw", mode, 0, 1) + + :returns: An :py:class:`~PIL.Image.Image` object. + + .. versionadded:: 1.1.4 + """ + + _check_size(size) + + # may pass tuple instead of argument list + if len(args) == 1 and isinstance(args[0], tuple): + args = args[0] + + if decoder_name == "raw": + if args == (): + args = mode, 0, 1 + if args[0] in _MAPMODES: + im = new(mode, (0, 0)) + im = im._new(core.map_buffer(data, size, decoder_name, 0, args)) + if mode == "P": + from . import ImagePalette + + im.palette = ImagePalette.ImagePalette("RGB", im.im.getpalette("RGB")) + im.readonly = 1 + return im + + return frombytes(mode, size, data, decoder_name, args) + + +class SupportsArrayInterface(Protocol): + """ + An object that has an ``__array_interface__`` dictionary. + """ + + @property + def __array_interface__(self) -> dict[str, Any]: + raise NotImplementedError() + + +class SupportsArrowArrayInterface(Protocol): + """ + An object that has an ``__arrow_c_array__`` method corresponding to the arrow c + data interface. + """ + + def __arrow_c_array__( + self, requested_schema: "PyCapsule" = None # type: ignore[name-defined] # noqa: F821, UP037 + ) -> tuple["PyCapsule", "PyCapsule"]: # type: ignore[name-defined] # noqa: F821, UP037 + raise NotImplementedError() + + +def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image: + """ + Creates an image memory from an object exporting the array interface + (using the buffer protocol):: + + from PIL import Image + import numpy as np + a = np.zeros((5, 5)) + im = Image.fromarray(a) + + If ``obj`` is not contiguous, then the ``tobytes`` method is called + and :py:func:`~PIL.Image.frombuffer` is used. + + In the case of NumPy, be aware that Pillow modes do not always correspond + to NumPy dtypes. Pillow modes only offer 1-bit pixels, 8-bit pixels, + 32-bit signed integer pixels, and 32-bit floating point pixels. + + Pillow images can also be converted to arrays:: + + from PIL import Image + import numpy as np + im = Image.open("hopper.jpg") + a = np.asarray(im) + + When converting Pillow images to arrays however, only pixel values are + transferred. This means that P and PA mode images will lose their palette. + + :param obj: Object with array interface + :param mode: Optional mode to use when reading ``obj``. Since pixel values do not + contain information about palettes or color spaces, this can be used to place + grayscale L mode data within a P mode image, or read RGB data as YCbCr for + example. + + See: :ref:`concept-modes` for general information about modes. + :returns: An image object. + + .. versionadded:: 1.1.6 + """ + arr = obj.__array_interface__ + shape = arr["shape"] + ndim = len(shape) + strides = arr.get("strides", None) + try: + typekey = (1, 1) + shape[2:], arr["typestr"] + except KeyError as e: + if mode is not None: + typekey = None + color_modes: list[str] = [] + else: + msg = "Cannot handle this data type" + raise TypeError(msg) from e + if typekey is not None: + try: + typemode, rawmode, color_modes = _fromarray_typemap[typekey] + except KeyError as e: + typekey_shape, typestr = typekey + msg = f"Cannot handle this data type: {typekey_shape}, {typestr}" + raise TypeError(msg) from e + if mode is not None: + if mode != typemode and mode not in color_modes: + deprecate("'mode' parameter for changing data types", 13) + rawmode = mode + else: + mode = typemode + if mode in ["1", "L", "I", "P", "F"]: + ndmax = 2 + elif mode == "RGB": + ndmax = 3 + else: + ndmax = 4 + if ndim > ndmax: + msg = f"Too many dimensions: {ndim} > {ndmax}." + raise ValueError(msg) + + size = 1 if ndim == 1 else shape[1], shape[0] + if strides is not None: + if hasattr(obj, "tobytes"): + obj = obj.tobytes() + elif hasattr(obj, "tostring"): + obj = obj.tostring() + else: + msg = "'strides' requires either tobytes() or tostring()" + raise ValueError(msg) + + return frombuffer(mode, size, obj, "raw", rawmode, 0, 1) + + +def fromarrow( + obj: SupportsArrowArrayInterface, mode: str, size: tuple[int, int] +) -> Image: + """Creates an image with zero-copy shared memory from an object exporting + the arrow_c_array interface protocol:: + + from PIL import Image + import pyarrow as pa + arr = pa.array([0]*(5*5*4), type=pa.uint8()) + im = Image.fromarrow(arr, 'RGBA', (5, 5)) + + If the data representation of the ``obj`` is not compatible with + Pillow internal storage, a ValueError is raised. + + Pillow images can also be converted to Arrow objects:: + + from PIL import Image + import pyarrow as pa + im = Image.open('hopper.jpg') + arr = pa.array(im) + + As with array support, when converting Pillow images to arrays, + only pixel values are transferred. This means that P and PA mode + images will lose their palette. + + :param obj: Object with an arrow_c_array interface + :param mode: Image mode. + :param size: Image size. This must match the storage of the arrow object. + :returns: An Image object + + Note that according to the Arrow spec, both the producer and the + consumer should consider the exported array to be immutable, as + unsynchronized updates will potentially cause inconsistent data. + + See: :ref:`arrow-support` for more detailed information + + .. versionadded:: 11.2.1 + + """ + if not hasattr(obj, "__arrow_c_array__"): + msg = "arrow_c_array interface not found" + raise ValueError(msg) + + (schema_capsule, array_capsule) = obj.__arrow_c_array__() + _im = core.new_arrow(mode, size, schema_capsule, array_capsule) + if _im: + return Image()._new(_im) + + msg = "new_arrow returned None without an exception" + raise ValueError(msg) + + +def fromqimage(im: ImageQt.QImage) -> ImageFile.ImageFile: + """Creates an image instance from a QImage image""" + from . import ImageQt + + if not ImageQt.qt_is_installed: + msg = "Qt bindings are not installed" + raise ImportError(msg) + return ImageQt.fromqimage(im) + + +def fromqpixmap(im: ImageQt.QPixmap) -> ImageFile.ImageFile: + """Creates an image instance from a QPixmap image""" + from . import ImageQt + + if not ImageQt.qt_is_installed: + msg = "Qt bindings are not installed" + raise ImportError(msg) + return ImageQt.fromqpixmap(im) + + +_fromarray_typemap = { + # (shape, typestr) => mode, rawmode, color modes + # first two members of shape are set to one + ((1, 1), "|b1"): ("1", "1;8", []), + ((1, 1), "|u1"): ("L", "L", ["P"]), + ((1, 1), "|i1"): ("I", "I;8", []), + ((1, 1), "u2"): ("I", "I;16B", []), + ((1, 1), "i2"): ("I", "I;16BS", []), + ((1, 1), "u4"): ("I", "I;32B", []), + ((1, 1), "i4"): ("I", "I;32BS", []), + ((1, 1), "f4"): ("F", "F;32BF", []), + ((1, 1), "f8"): ("F", "F;64BF", []), + ((1, 1, 2), "|u1"): ("LA", "LA", ["La", "PA"]), + ((1, 1, 3), "|u1"): ("RGB", "RGB", ["YCbCr", "LAB", "HSV"]), + ((1, 1, 4), "|u1"): ("RGBA", "RGBA", ["RGBa", "RGBX", "CMYK"]), + # shortcuts: + ((1, 1), f"{_ENDIAN}i4"): ("I", "I", []), + ((1, 1), f"{_ENDIAN}f4"): ("F", "F", []), +} + + +def _decompression_bomb_check(size: tuple[int, int]) -> None: + if MAX_IMAGE_PIXELS is None: + return + + pixels = max(1, size[0]) * max(1, size[1]) + + if pixels > 2 * MAX_IMAGE_PIXELS: + msg = ( + f"Image size ({pixels} pixels) exceeds limit of {2 * MAX_IMAGE_PIXELS} " + "pixels, could be decompression bomb DOS attack." + ) + raise DecompressionBombError(msg) + + if pixels > MAX_IMAGE_PIXELS: + warnings.warn( + f"Image size ({pixels} pixels) exceeds limit of {MAX_IMAGE_PIXELS} pixels, " + "could be decompression bomb DOS attack.", + DecompressionBombWarning, + ) + + +def open( + fp: StrOrBytesPath | IO[bytes], + mode: Literal["r"] = "r", + formats: list[str] | tuple[str, ...] | None = None, +) -> ImageFile.ImageFile: + """ + Opens and identifies the given image file. + + This is a lazy operation; this function identifies the file, but + the file remains open and the actual image data is not read from + the file until you try to process the data (or call the + :py:meth:`~PIL.Image.Image.load` method). See + :py:func:`~PIL.Image.new`. See :ref:`file-handling`. + + :param fp: A filename (string), os.PathLike object or a file object. + The file object must implement ``file.read``, + ``file.seek``, and ``file.tell`` methods, + and be opened in binary mode. The file object will also seek to zero + before reading. + :param mode: The mode. If given, this argument must be "r". + :param formats: A list or tuple of formats to attempt to load the file in. + This can be used to restrict the set of formats checked. + Pass ``None`` to try all supported formats. You can print the set of + available formats by running ``python3 -m PIL`` or using + the :py:func:`PIL.features.pilinfo` function. + :returns: An :py:class:`~PIL.Image.Image` object. + :exception FileNotFoundError: If the file cannot be found. + :exception PIL.UnidentifiedImageError: If the image cannot be opened and + identified. + :exception ValueError: If the ``mode`` is not "r", or if a ``StringIO`` + instance is used for ``fp``. + :exception TypeError: If ``formats`` is not ``None``, a list or a tuple. + """ + + if mode != "r": + msg = f"bad mode {repr(mode)}" # type: ignore[unreachable] + raise ValueError(msg) + elif isinstance(fp, io.StringIO): + msg = ( # type: ignore[unreachable] + "StringIO cannot be used to open an image. " + "Binary data must be used instead." + ) + raise ValueError(msg) + + if formats is None: + formats = ID + elif not isinstance(formats, (list, tuple)): + msg = "formats must be a list or tuple" # type: ignore[unreachable] + raise TypeError(msg) + + exclusive_fp = False + filename: str | bytes = "" + if is_path(fp): + filename = os.fspath(fp) + fp = builtins.open(filename, "rb") + exclusive_fp = True + else: + fp = cast(IO[bytes], fp) + + try: + fp.seek(0) + except (AttributeError, io.UnsupportedOperation): + fp = io.BytesIO(fp.read()) + exclusive_fp = True + + prefix = fp.read(16) + + preinit() + + warning_messages: list[str] = [] + + def _open_core( + fp: IO[bytes], + filename: str | bytes, + prefix: bytes, + formats: list[str] | tuple[str, ...], + ) -> ImageFile.ImageFile | None: + for i in formats: + i = i.upper() + if i not in OPEN: + init() + try: + factory, accept = OPEN[i] + result = not accept or accept(prefix) + if isinstance(result, str): + warning_messages.append(result) + elif result: + fp.seek(0) + im = factory(fp, filename) + _decompression_bomb_check(im.size) + return im + except (SyntaxError, IndexError, TypeError, struct.error) as e: + if WARN_POSSIBLE_FORMATS: + warning_messages.append(i + " opening failed. " + str(e)) + except BaseException: + if exclusive_fp: + fp.close() + raise + return None + + im = _open_core(fp, filename, prefix, formats) + + if im is None and formats is ID: + checked_formats = ID.copy() + if init(): + im = _open_core( + fp, + filename, + prefix, + tuple(format for format in formats if format not in checked_formats), + ) + + if im: + im._exclusive_fp = exclusive_fp + return im + + if exclusive_fp: + fp.close() + for message in warning_messages: + warnings.warn(message) + msg = "cannot identify image file %r" % (filename if filename else fp) + raise UnidentifiedImageError(msg) + + +# +# Image processing. + + +def alpha_composite(im1: Image, im2: Image) -> Image: + """ + Alpha composite im2 over im1. + + :param im1: The first image. Must have mode RGBA or LA. + :param im2: The second image. Must have the same mode and size as the first image. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + im1.load() + im2.load() + return im1._new(core.alpha_composite(im1.im, im2.im)) + + +def blend(im1: Image, im2: Image, alpha: float) -> Image: + """ + Creates a new image by interpolating between two input images, using + a constant alpha:: + + out = image1 * (1.0 - alpha) + image2 * alpha + + :param im1: The first image. + :param im2: The second image. Must have the same mode and size as + the first image. + :param alpha: The interpolation alpha factor. If alpha is 0.0, a + copy of the first image is returned. If alpha is 1.0, a copy of + the second image is returned. There are no restrictions on the + alpha value. If necessary, the result is clipped to fit into + the allowed output range. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + im1.load() + im2.load() + return im1._new(core.blend(im1.im, im2.im, alpha)) + + +def composite(image1: Image, image2: Image, mask: Image) -> Image: + """ + Create composite image by blending images using a transparency mask. + + :param image1: The first image. + :param image2: The second image. Must have the same mode and + size as the first image. + :param mask: A mask image. This image can have mode + "1", "L", or "RGBA", and must have the same size as the + other two images. + """ + + image = image2.copy() + image.paste(image1, None, mask) + return image + + +def eval(image: Image, *args: Callable[[int], float]) -> Image: + """ + Applies the function (which should take one argument) to each pixel + in the given image. If the image has more than one band, the same + function is applied to each band. Note that the function is + evaluated once for each possible pixel value, so you cannot use + random components or other generators. + + :param image: The input image. + :param function: A function object, taking one integer argument. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + return image.point(args[0]) + + +def merge(mode: str, bands: Sequence[Image]) -> Image: + """ + Merge a set of single band images into a new multiband image. + + :param mode: The mode to use for the output image. See: + :ref:`concept-modes`. + :param bands: A sequence containing one single-band image for + each band in the output image. All bands must have the + same size. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + if getmodebands(mode) != len(bands) or "*" in mode: + msg = "wrong number of bands" + raise ValueError(msg) + for band in bands[1:]: + if band.mode != getmodetype(mode): + msg = "mode mismatch" + raise ValueError(msg) + if band.size != bands[0].size: + msg = "size mismatch" + raise ValueError(msg) + for band in bands: + band.load() + return bands[0]._new(core.merge(mode, *[b.im for b in bands])) + + +# -------------------------------------------------------------------- +# Plugin registry + + +def register_open( + id: str, + factory: ( + Callable[[IO[bytes], str | bytes], ImageFile.ImageFile] + | type[ImageFile.ImageFile] + ), + accept: Callable[[bytes], bool | str] | None = None, +) -> None: + """ + Register an image file plugin. This function should not be used + in application code. + + :param id: An image format identifier. + :param factory: An image file factory method. + :param accept: An optional function that can be used to quickly + reject images having another format. + """ + id = id.upper() + if id not in ID: + ID.append(id) + OPEN[id] = factory, accept + + +def register_mime(id: str, mimetype: str) -> None: + """ + Registers an image MIME type by populating ``Image.MIME``. This function + should not be used in application code. + + ``Image.MIME`` provides a mapping from image format identifiers to mime + formats, but :py:meth:`~PIL.ImageFile.ImageFile.get_format_mimetype` can + provide a different result for specific images. + + :param id: An image format identifier. + :param mimetype: The image MIME type for this format. + """ + MIME[id.upper()] = mimetype + + +def register_save( + id: str, driver: Callable[[Image, IO[bytes], str | bytes], None] +) -> None: + """ + Registers an image save function. This function should not be + used in application code. + + :param id: An image format identifier. + :param driver: A function to save images in this format. + """ + SAVE[id.upper()] = driver + + +def register_save_all( + id: str, driver: Callable[[Image, IO[bytes], str | bytes], None] +) -> None: + """ + Registers an image function to save all the frames + of a multiframe format. This function should not be + used in application code. + + :param id: An image format identifier. + :param driver: A function to save images in this format. + """ + SAVE_ALL[id.upper()] = driver + + +def register_extension(id: str, extension: str) -> None: + """ + Registers an image extension. This function should not be + used in application code. + + :param id: An image format identifier. + :param extension: An extension used for this format. + """ + EXTENSION[extension.lower()] = id.upper() + + +def register_extensions(id: str, extensions: list[str]) -> None: + """ + Registers image extensions. This function should not be + used in application code. + + :param id: An image format identifier. + :param extensions: A list of extensions used for this format. + """ + for extension in extensions: + register_extension(id, extension) + + +def registered_extensions() -> dict[str, str]: + """ + Returns a dictionary containing all file extensions belonging + to registered plugins + """ + init() + return EXTENSION + + +def register_decoder(name: str, decoder: type[ImageFile.PyDecoder]) -> None: + """ + Registers an image decoder. This function should not be + used in application code. + + :param name: The name of the decoder + :param decoder: An ImageFile.PyDecoder object + + .. versionadded:: 4.1.0 + """ + DECODERS[name] = decoder + + +def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None: + """ + Registers an image encoder. This function should not be + used in application code. + + :param name: The name of the encoder + :param encoder: An ImageFile.PyEncoder object + + .. versionadded:: 4.1.0 + """ + ENCODERS[name] = encoder + + +# -------------------------------------------------------------------- +# Simple display support. + + +def _show(image: Image, **options: Any) -> None: + from . import ImageShow + + deprecate("Image._show", 13, "ImageShow.show") + ImageShow.show(image, **options) + + +# -------------------------------------------------------------------- +# Effects + + +def effect_mandelbrot( + size: tuple[int, int], extent: tuple[float, float, float, float], quality: int +) -> Image: + """ + Generate a Mandelbrot set covering the given extent. + + :param size: The requested size in pixels, as a 2-tuple: + (width, height). + :param extent: The extent to cover, as a 4-tuple: + (x0, y0, x1, y1). + :param quality: Quality. + """ + return Image()._new(core.effect_mandelbrot(size, extent, quality)) + + +def effect_noise(size: tuple[int, int], sigma: float) -> Image: + """ + Generate Gaussian noise centered around 128. + + :param size: The requested size in pixels, as a 2-tuple: + (width, height). + :param sigma: Standard deviation of noise. + """ + return Image()._new(core.effect_noise(size, sigma)) + + +def linear_gradient(mode: str) -> Image: + """ + Generate 256x256 linear gradient from black to white, top to bottom. + + :param mode: Input mode. + """ + return Image()._new(core.linear_gradient(mode)) + + +def radial_gradient(mode: str) -> Image: + """ + Generate 256x256 radial gradient from black to white, centre to edge. + + :param mode: Input mode. + """ + return Image()._new(core.radial_gradient(mode)) + + +# -------------------------------------------------------------------- +# Resources + + +def _apply_env_variables(env: dict[str, str] | None = None) -> None: + env_dict = env if env is not None else os.environ + + for var_name, setter in [ + ("PILLOW_ALIGNMENT", core.set_alignment), + ("PILLOW_BLOCK_SIZE", core.set_block_size), + ("PILLOW_BLOCKS_MAX", core.set_blocks_max), + ]: + if var_name not in env_dict: + continue + + var = env_dict[var_name].lower() + + units = 1 + for postfix, mul in [("k", 1024), ("m", 1024 * 1024)]: + if var.endswith(postfix): + units = mul + var = var[: -len(postfix)] + + try: + var_int = int(var) * units + except ValueError: + warnings.warn(f"{var_name} is not int") + continue + + try: + setter(var_int) + except ValueError as e: + warnings.warn(f"{var_name}: {e}") + + +_apply_env_variables() +atexit.register(core.clear_cache) + + +if TYPE_CHECKING: + _ExifBase = MutableMapping[int, Any] +else: + _ExifBase = MutableMapping + + +class Exif(_ExifBase): + """ + This class provides read and write access to EXIF image data:: + + from PIL import Image + im = Image.open("exif.png") + exif = im.getexif() # Returns an instance of this class + + Information can be read and written, iterated over or deleted:: + + print(exif[274]) # 1 + exif[274] = 2 + for k, v in exif.items(): + print("Tag", k, "Value", v) # Tag 274 Value 2 + del exif[274] + + To access information beyond IFD0, :py:meth:`~PIL.Image.Exif.get_ifd` + returns a dictionary:: + + from PIL import ExifTags + im = Image.open("exif_gps.jpg") + exif = im.getexif() + gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo) + print(gps_ifd) + + Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.MakerNote``, + ``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``. + + :py:mod:`~PIL.ExifTags` also has enum classes to provide names for data:: + + print(exif[ExifTags.Base.Software]) # PIL + print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99 + """ + + endian: str | None = None + bigtiff = False + _loaded = False + + def __init__(self) -> None: + self._data: dict[int, Any] = {} + self._hidden_data: dict[int, Any] = {} + self._ifds: dict[int, dict[int, Any]] = {} + self._info: TiffImagePlugin.ImageFileDirectory_v2 | None = None + self._loaded_exif: bytes | None = None + + def _fixup(self, value: Any) -> Any: + try: + if len(value) == 1 and isinstance(value, tuple): + return value[0] + except Exception: + pass + return value + + def _fixup_dict(self, src_dict: dict[int, Any]) -> dict[int, Any]: + # Helper function + # returns a dict with any single item tuples/lists as individual values + return {k: self._fixup(v) for k, v in src_dict.items()} + + def _get_ifd_dict( + self, offset: int, group: int | None = None + ) -> dict[int, Any] | None: + try: + # an offset pointer to the location of the nested embedded IFD. + # It should be a long, but may be corrupted. + self.fp.seek(offset) + except (KeyError, TypeError): + return None + else: + from . import TiffImagePlugin + + info = TiffImagePlugin.ImageFileDirectory_v2(self.head, group=group) + info.load(self.fp) + return self._fixup_dict(dict(info)) + + def _get_head(self) -> bytes: + version = b"\x2b" if self.bigtiff else b"\x2a" + if self.endian == "<": + head = b"II" + version + b"\x00" + o32le(8) + else: + head = b"MM\x00" + version + o32be(8) + if self.bigtiff: + head += o32le(8) if self.endian == "<" else o32be(8) + head += b"\x00\x00\x00\x00" + return head + + def load(self, data: bytes) -> None: + # Extract EXIF information. This is highly experimental, + # and is likely to be replaced with something better in a future + # version. + + # The EXIF record consists of a TIFF file embedded in a JPEG + # application marker (!). + if data == self._loaded_exif: + return + self._loaded_exif = data + self._data.clear() + self._hidden_data.clear() + self._ifds.clear() + while data and data.startswith(b"Exif\x00\x00"): + data = data[6:] + if not data: + self._info = None + return + + self.fp: IO[bytes] = io.BytesIO(data) + self.head = self.fp.read(8) + # process dictionary + from . import TiffImagePlugin + + self._info = TiffImagePlugin.ImageFileDirectory_v2(self.head) + self.endian = self._info._endian + self.fp.seek(self._info.next) + self._info.load(self.fp) + + def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None: + self._loaded_exif = None + self._data.clear() + self._hidden_data.clear() + self._ifds.clear() + + # process dictionary + from . import TiffImagePlugin + + self.fp = fp + if offset is not None: + self.head = self._get_head() + else: + self.head = self.fp.read(8) + self._info = TiffImagePlugin.ImageFileDirectory_v2(self.head) + if self.endian is None: + self.endian = self._info._endian + if offset is None: + offset = self._info.next + self.fp.tell() + self.fp.seek(offset) + self._info.load(self.fp) + + def _get_merged_dict(self) -> dict[int, Any]: + merged_dict = dict(self) + + # get EXIF extension + if ExifTags.IFD.Exif in self: + ifd = self._get_ifd_dict(self[ExifTags.IFD.Exif], ExifTags.IFD.Exif) + if ifd: + merged_dict.update(ifd) + + # GPS + if ExifTags.IFD.GPSInfo in self: + merged_dict[ExifTags.IFD.GPSInfo] = self._get_ifd_dict( + self[ExifTags.IFD.GPSInfo], ExifTags.IFD.GPSInfo + ) + + return merged_dict + + def tobytes(self, offset: int = 8) -> bytes: + from . import TiffImagePlugin + + head = self._get_head() + ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head) + for tag, ifd_dict in self._ifds.items(): + if tag not in self: + ifd[tag] = ifd_dict + for tag, value in self.items(): + if tag in [ + ExifTags.IFD.Exif, + ExifTags.IFD.GPSInfo, + ] and not isinstance(value, dict): + value = self.get_ifd(tag) + if ( + tag == ExifTags.IFD.Exif + and ExifTags.IFD.Interop in value + and not isinstance(value[ExifTags.IFD.Interop], dict) + ): + value = value.copy() + value[ExifTags.IFD.Interop] = self.get_ifd(ExifTags.IFD.Interop) + ifd[tag] = value + return b"Exif\x00\x00" + head + ifd.tobytes(offset) + + def get_ifd(self, tag: int) -> dict[int, Any]: + if tag not in self._ifds: + if tag == ExifTags.IFD.IFD1: + if self._info is not None and self._info.next != 0: + ifd = self._get_ifd_dict(self._info.next) + if ifd is not None: + self._ifds[tag] = ifd + elif tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]: + offset = self._hidden_data.get(tag, self.get(tag)) + if offset is not None: + ifd = self._get_ifd_dict(offset, tag) + if ifd is not None: + self._ifds[tag] = ifd + elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.MakerNote]: + if ExifTags.IFD.Exif not in self._ifds: + self.get_ifd(ExifTags.IFD.Exif) + tag_data = self._ifds[ExifTags.IFD.Exif][tag] + if tag == ExifTags.IFD.MakerNote: + from .TiffImagePlugin import ImageFileDirectory_v2 + + if tag_data.startswith(b"FUJIFILM"): + ifd_offset = i32le(tag_data, 8) + ifd_data = tag_data[ifd_offset:] + + makernote = {} + for i in range(struct.unpack(" 4: + (offset,) = struct.unpack("H", tag_data[:2])[0]): + ifd_tag, typ, count, data = struct.unpack( + ">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2] + ) + if ifd_tag == 0x1101: + # CameraInfo + (offset,) = struct.unpack(">L", data) + self.fp.seek(offset) + + camerainfo: dict[str, int | bytes] = { + "ModelID": self.fp.read(4) + } + + self.fp.read(4) + # Seconds since 2000 + camerainfo["TimeStamp"] = i32le(self.fp.read(12)) + + self.fp.read(4) + camerainfo["InternalSerialNumber"] = self.fp.read(4) + + self.fp.read(12) + parallax = self.fp.read(4) + handler = ImageFileDirectory_v2._load_dispatch[ + TiffTags.FLOAT + ][1] + camerainfo["Parallax"] = handler( + ImageFileDirectory_v2(), parallax, False + )[0] + + self.fp.read(4) + camerainfo["Category"] = self.fp.read(2) + + makernote = {0x1101: camerainfo} + self._ifds[tag] = makernote + else: + # Interop + ifd = self._get_ifd_dict(tag_data, tag) + if ifd is not None: + self._ifds[tag] = ifd + ifd = self._ifds.setdefault(tag, {}) + if tag == ExifTags.IFD.Exif and self._hidden_data: + ifd = { + k: v + for (k, v) in ifd.items() + if k not in (ExifTags.IFD.Interop, ExifTags.IFD.MakerNote) + } + return ifd + + def hide_offsets(self) -> None: + for tag in (ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo): + if tag in self: + self._hidden_data[tag] = self[tag] + del self[tag] + + def __str__(self) -> str: + if self._info is not None: + # Load all keys into self._data + for tag in self._info: + self[tag] + + return str(self._data) + + def __len__(self) -> int: + keys = set(self._data) + if self._info is not None: + keys.update(self._info) + return len(keys) + + def __getitem__(self, tag: int) -> Any: + if self._info is not None and tag not in self._data and tag in self._info: + self._data[tag] = self._fixup(self._info[tag]) + del self._info[tag] + return self._data[tag] + + def __contains__(self, tag: object) -> bool: + return tag in self._data or (self._info is not None and tag in self._info) + + def __setitem__(self, tag: int, value: Any) -> None: + if self._info is not None and tag in self._info: + del self._info[tag] + self._data[tag] = value + + def __delitem__(self, tag: int) -> None: + if self._info is not None and tag in self._info: + del self._info[tag] + else: + del self._data[tag] + if tag in self._ifds: + del self._ifds[tag] + + def __iter__(self) -> Iterator[int]: + keys = set(self._data) + if self._info is not None: + keys.update(self._info) + return iter(keys) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageChops.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageChops.py new file mode 100644 index 0000000000000000000000000000000000000000..29a5c995fd802c9be16784f80707cfecb88b2002 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageChops.py @@ -0,0 +1,311 @@ +# +# The Python Imaging Library. +# $Id$ +# +# standard channel operations +# +# History: +# 1996-03-24 fl Created +# 1996-08-13 fl Added logical operations (for "1" images) +# 2000-10-12 fl Added offset method (from Image.py) +# +# Copyright (c) 1997-2000 by Secret Labs AB +# Copyright (c) 1996-2000 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# + +from __future__ import annotations + +from . import Image + + +def constant(image: Image.Image, value: int) -> Image.Image: + """Fill a channel with a given gray level. + + :rtype: :py:class:`~PIL.Image.Image` + """ + + return Image.new("L", image.size, value) + + +def duplicate(image: Image.Image) -> Image.Image: + """Copy a channel. Alias for :py:meth:`PIL.Image.Image.copy`. + + :rtype: :py:class:`~PIL.Image.Image` + """ + + return image.copy() + + +def invert(image: Image.Image) -> Image.Image: + """ + Invert an image (channel). :: + + out = MAX - image + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image.load() + return image._new(image.im.chop_invert()) + + +def lighter(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Compares the two images, pixel by pixel, and returns a new image containing + the lighter values. :: + + out = max(image1, image2) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_lighter(image2.im)) + + +def darker(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Compares the two images, pixel by pixel, and returns a new image containing + the darker values. :: + + out = min(image1, image2) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_darker(image2.im)) + + +def difference(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Returns the absolute value of the pixel-by-pixel difference between the two + images. :: + + out = abs(image1 - image2) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_difference(image2.im)) + + +def multiply(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Superimposes two images on top of each other. + + If you multiply an image with a solid black image, the result is black. If + you multiply with a solid white image, the image is unaffected. :: + + out = image1 * image2 / MAX + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_multiply(image2.im)) + + +def screen(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Superimposes two inverted images on top of each other. :: + + out = MAX - ((MAX - image1) * (MAX - image2) / MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_screen(image2.im)) + + +def soft_light(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Superimposes two images on top of each other using the Soft Light algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_soft_light(image2.im)) + + +def hard_light(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Superimposes two images on top of each other using the Hard Light algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_hard_light(image2.im)) + + +def overlay(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Superimposes two images on top of each other using the Overlay algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_overlay(image2.im)) + + +def add( + image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0 +) -> Image.Image: + """ + Adds two images, dividing the result by scale and adding the + offset. If omitted, scale defaults to 1.0, and offset to 0.0. :: + + out = ((image1 + image2) / scale + offset) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_add(image2.im, scale, offset)) + + +def subtract( + image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0 +) -> Image.Image: + """ + Subtracts two images, dividing the result by scale and adding the offset. + If omitted, scale defaults to 1.0, and offset to 0.0. :: + + out = ((image1 - image2) / scale + offset) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_subtract(image2.im, scale, offset)) + + +def add_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image: + """Add two images, without clipping the result. :: + + out = ((image1 + image2) % MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_add_modulo(image2.im)) + + +def subtract_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image: + """Subtract two images, without clipping the result. :: + + out = ((image1 - image2) % MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_subtract_modulo(image2.im)) + + +def logical_and(image1: Image.Image, image2: Image.Image) -> Image.Image: + """Logical AND between two images. + + Both of the images must have mode "1". If you would like to perform a + logical AND on an image with a mode other than "1", try + :py:meth:`~PIL.ImageChops.multiply` instead, using a black-and-white mask + as the second image. :: + + out = ((image1 and image2) % MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_and(image2.im)) + + +def logical_or(image1: Image.Image, image2: Image.Image) -> Image.Image: + """Logical OR between two images. + + Both of the images must have mode "1". :: + + out = ((image1 or image2) % MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_or(image2.im)) + + +def logical_xor(image1: Image.Image, image2: Image.Image) -> Image.Image: + """Logical XOR between two images. + + Both of the images must have mode "1". :: + + out = ((bool(image1) != bool(image2)) % MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_xor(image2.im)) + + +def blend(image1: Image.Image, image2: Image.Image, alpha: float) -> Image.Image: + """Blend images using constant transparency weight. Alias for + :py:func:`PIL.Image.blend`. + + :rtype: :py:class:`~PIL.Image.Image` + """ + + return Image.blend(image1, image2, alpha) + + +def composite( + image1: Image.Image, image2: Image.Image, mask: Image.Image +) -> Image.Image: + """Create composite using transparency mask. Alias for + :py:func:`PIL.Image.composite`. + + :rtype: :py:class:`~PIL.Image.Image` + """ + + return Image.composite(image1, image2, mask) + + +def offset(image: Image.Image, xoffset: int, yoffset: int | None = None) -> Image.Image: + """Returns a copy of the image where data has been offset by the given + distances. Data wraps around the edges. If ``yoffset`` is omitted, it + is assumed to be equal to ``xoffset``. + + :param image: Input image. + :param xoffset: The horizontal distance. + :param yoffset: The vertical distance. If omitted, both + distances are set to the same value. + :rtype: :py:class:`~PIL.Image.Image` + """ + + if yoffset is None: + yoffset = xoffset + image.load() + return image._new(image.im.offset(xoffset, yoffset)) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageCms.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageCms.py new file mode 100644 index 0000000000000000000000000000000000000000..513e28acf33867bd4f6d046ac45ce3f71d35a433 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageCms.py @@ -0,0 +1,1076 @@ +# The Python Imaging Library. +# $Id$ + +# Optional color management support, based on Kevin Cazabon's PyCMS +# library. + +# Originally released under LGPL. Graciously donated to PIL in +# March 2009, for distribution under the standard PIL license + +# History: + +# 2009-03-08 fl Added to PIL. + +# Copyright (C) 2002-2003 Kevin Cazabon +# Copyright (c) 2009 by Fredrik Lundh +# Copyright (c) 2013 by Eric Soroos + +# See the README file for information on usage and redistribution. See +# below for the original description. +from __future__ import annotations + +import operator +import sys +from enum import IntEnum, IntFlag +from functools import reduce +from typing import Any, Literal, SupportsFloat, SupportsInt, Union + +from . import Image +from ._deprecate import deprecate +from ._typing import SupportsRead + +try: + from . import _imagingcms as core + + _CmsProfileCompatible = Union[ + str, SupportsRead[bytes], core.CmsProfile, "ImageCmsProfile" + ] +except ImportError as ex: + # Allow error import for doc purposes, but error out when accessing + # anything in core. + from ._util import DeferredError + + core = DeferredError.new(ex) + +_DESCRIPTION = """ +pyCMS + + a Python / PIL interface to the littleCMS ICC Color Management System + Copyright (C) 2002-2003 Kevin Cazabon + kevin@cazabon.com + https://www.cazabon.com + + pyCMS home page: https://www.cazabon.com/pyCMS + littleCMS home page: https://www.littlecms.com + (littleCMS is Copyright (C) 1998-2001 Marti Maria) + + Originally released under LGPL. Graciously donated to PIL in + March 2009, for distribution under the standard PIL license + + The pyCMS.py module provides a "clean" interface between Python/PIL and + pyCMSdll, taking care of some of the more complex handling of the direct + pyCMSdll functions, as well as error-checking and making sure that all + relevant data is kept together. + + While it is possible to call pyCMSdll functions directly, it's not highly + recommended. + + Version History: + + 1.0.0 pil Oct 2013 Port to LCMS 2. + + 0.1.0 pil mod March 10, 2009 + + Renamed display profile to proof profile. The proof + profile is the profile of the device that is being + simulated, not the profile of the device which is + actually used to display/print the final simulation + (that'd be the output profile) - also see LCMSAPI.txt + input colorspace -> using 'renderingIntent' -> proof + colorspace -> using 'proofRenderingIntent' -> output + colorspace + + Added LCMS FLAGS support. + Added FLAGS["SOFTPROOFING"] as default flag for + buildProofTransform (otherwise the proof profile/intent + would be ignored). + + 0.1.0 pil March 2009 - added to PIL, as PIL.ImageCms + + 0.0.2 alpha Jan 6, 2002 + + Added try/except statements around type() checks of + potential CObjects... Python won't let you use type() + on them, and raises a TypeError (stupid, if you ask + me!) + + Added buildProofTransformFromOpenProfiles() function. + Additional fixes in DLL, see DLL code for details. + + 0.0.1 alpha first public release, Dec. 26, 2002 + + Known to-do list with current version (of Python interface, not pyCMSdll): + + none + +""" + +_VERSION = "1.0.0 pil" + + +# --------------------------------------------------------------------. + + +# +# intent/direction values + + +class Intent(IntEnum): + PERCEPTUAL = 0 + RELATIVE_COLORIMETRIC = 1 + SATURATION = 2 + ABSOLUTE_COLORIMETRIC = 3 + + +class Direction(IntEnum): + INPUT = 0 + OUTPUT = 1 + PROOF = 2 + + +# +# flags + + +class Flags(IntFlag): + """Flags and documentation are taken from ``lcms2.h``.""" + + NONE = 0 + NOCACHE = 0x0040 + """Inhibit 1-pixel cache""" + NOOPTIMIZE = 0x0100 + """Inhibit optimizations""" + NULLTRANSFORM = 0x0200 + """Don't transform anyway""" + GAMUTCHECK = 0x1000 + """Out of Gamut alarm""" + SOFTPROOFING = 0x4000 + """Do softproofing""" + BLACKPOINTCOMPENSATION = 0x2000 + NOWHITEONWHITEFIXUP = 0x0004 + """Don't fix scum dot""" + HIGHRESPRECALC = 0x0400 + """Use more memory to give better accuracy""" + LOWRESPRECALC = 0x0800 + """Use less memory to minimize resources""" + # this should be 8BITS_DEVICELINK, but that is not a valid name in Python: + USE_8BITS_DEVICELINK = 0x0008 + """Create 8 bits devicelinks""" + GUESSDEVICECLASS = 0x0020 + """Guess device class (for ``transform2devicelink``)""" + KEEP_SEQUENCE = 0x0080 + """Keep profile sequence for devicelink creation""" + FORCE_CLUT = 0x0002 + """Force CLUT optimization""" + CLUT_POST_LINEARIZATION = 0x0001 + """create postlinearization tables if possible""" + CLUT_PRE_LINEARIZATION = 0x0010 + """create prelinearization tables if possible""" + NONEGATIVES = 0x8000 + """Prevent negative numbers in floating point transforms""" + COPY_ALPHA = 0x04000000 + """Alpha channels are copied on ``cmsDoTransform()``""" + NODEFAULTRESOURCEDEF = 0x01000000 + + _GRIDPOINTS_1 = 1 << 16 + _GRIDPOINTS_2 = 2 << 16 + _GRIDPOINTS_4 = 4 << 16 + _GRIDPOINTS_8 = 8 << 16 + _GRIDPOINTS_16 = 16 << 16 + _GRIDPOINTS_32 = 32 << 16 + _GRIDPOINTS_64 = 64 << 16 + _GRIDPOINTS_128 = 128 << 16 + + @staticmethod + def GRIDPOINTS(n: int) -> Flags: + """ + Fine-tune control over number of gridpoints + + :param n: :py:class:`int` in range ``0 <= n <= 255`` + """ + return Flags.NONE | ((n & 0xFF) << 16) + + +_MAX_FLAG = reduce(operator.or_, Flags) + + +_FLAGS = { + "MATRIXINPUT": 1, + "MATRIXOUTPUT": 2, + "MATRIXONLY": (1 | 2), + "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot + # Don't create prelinearization tables on precalculated transforms + # (internal use): + "NOPRELINEARIZATION": 16, + "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink) + "NOTCACHE": 64, # Inhibit 1-pixel cache + "NOTPRECALC": 256, + "NULLTRANSFORM": 512, # Don't transform anyway + "HIGHRESPRECALC": 1024, # Use more memory to give better accuracy + "LOWRESPRECALC": 2048, # Use less memory to minimize resources + "WHITEBLACKCOMPENSATION": 8192, + "BLACKPOINTCOMPENSATION": 8192, + "GAMUTCHECK": 4096, # Out of Gamut alarm + "SOFTPROOFING": 16384, # Do softproofing + "PRESERVEBLACK": 32768, # Black preservation + "NODEFAULTRESOURCEDEF": 16777216, # CRD special + "GRIDPOINTS": lambda n: (n & 0xFF) << 16, # Gridpoints +} + + +# --------------------------------------------------------------------. +# Experimental PIL-level API +# --------------------------------------------------------------------. + +## +# Profile. + + +class ImageCmsProfile: + def __init__(self, profile: str | SupportsRead[bytes] | core.CmsProfile) -> None: + """ + :param profile: Either a string representing a filename, + a file like object containing a profile or a + low-level profile object + + """ + self.filename: str | None = None + + if isinstance(profile, str): + if sys.platform == "win32": + profile_bytes_path = profile.encode() + try: + profile_bytes_path.decode("ascii") + except UnicodeDecodeError: + with open(profile, "rb") as f: + self.profile = core.profile_frombytes(f.read()) + return + self.filename = profile + self.profile = core.profile_open(profile) + elif hasattr(profile, "read"): + self.profile = core.profile_frombytes(profile.read()) + elif isinstance(profile, core.CmsProfile): + self.profile = profile + else: + msg = "Invalid type for Profile" # type: ignore[unreachable] + raise TypeError(msg) + + def __getattr__(self, name: str) -> Any: + if name in ("product_name", "product_info"): + deprecate(f"ImageCms.ImageCmsProfile.{name}", 13) + return None + msg = f"'{self.__class__.__name__}' object has no attribute '{name}'" + raise AttributeError(msg) + + def tobytes(self) -> bytes: + """ + Returns the profile in a format suitable for embedding in + saved images. + + :returns: a bytes object containing the ICC profile. + """ + + return core.profile_tobytes(self.profile) + + +class ImageCmsTransform(Image.ImagePointHandler): + """ + Transform. This can be used with the procedural API, or with the standard + :py:func:`~PIL.Image.Image.point` method. + + Will return the output profile in the ``output.info['icc_profile']``. + """ + + def __init__( + self, + input: ImageCmsProfile, + output: ImageCmsProfile, + input_mode: str, + output_mode: str, + intent: Intent = Intent.PERCEPTUAL, + proof: ImageCmsProfile | None = None, + proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC, + flags: Flags = Flags.NONE, + ): + if proof is None: + self.transform = core.buildTransform( + input.profile, output.profile, input_mode, output_mode, intent, flags + ) + else: + self.transform = core.buildProofTransform( + input.profile, + output.profile, + proof.profile, + input_mode, + output_mode, + intent, + proof_intent, + flags, + ) + # Note: inputMode and outputMode are for pyCMS compatibility only + self.input_mode = self.inputMode = input_mode + self.output_mode = self.outputMode = output_mode + + self.output_profile = output + + def point(self, im: Image.Image) -> Image.Image: + return self.apply(im) + + def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Image: + if imOut is None: + imOut = Image.new(self.output_mode, im.size, None) + self.transform.apply(im.getim(), imOut.getim()) + imOut.info["icc_profile"] = self.output_profile.tobytes() + return imOut + + def apply_in_place(self, im: Image.Image) -> Image.Image: + if im.mode != self.output_mode: + msg = "mode mismatch" + raise ValueError(msg) # wrong output mode + self.transform.apply(im.getim(), im.getim()) + im.info["icc_profile"] = self.output_profile.tobytes() + return im + + +def get_display_profile(handle: SupportsInt | None = None) -> ImageCmsProfile | None: + """ + (experimental) Fetches the profile for the current display device. + + :returns: ``None`` if the profile is not known. + """ + + if sys.platform != "win32": + return None + + from . import ImageWin # type: ignore[unused-ignore, unreachable] + + if isinstance(handle, ImageWin.HDC): + profile = core.get_display_profile_win32(int(handle), 1) + else: + profile = core.get_display_profile_win32(int(handle or 0)) + if profile is None: + return None + return ImageCmsProfile(profile) + + +# --------------------------------------------------------------------. +# pyCMS compatible layer +# --------------------------------------------------------------------. + + +class PyCMSError(Exception): + """(pyCMS) Exception class. + This is used for all errors in the pyCMS API.""" + + pass + + +def profileToProfile( + im: Image.Image, + inputProfile: _CmsProfileCompatible, + outputProfile: _CmsProfileCompatible, + renderingIntent: Intent = Intent.PERCEPTUAL, + outputMode: str | None = None, + inPlace: bool = False, + flags: Flags = Flags.NONE, +) -> Image.Image | None: + """ + (pyCMS) Applies an ICC transformation to a given image, mapping from + ``inputProfile`` to ``outputProfile``. + + If the input or output profiles specified are not valid filenames, a + :exc:`PyCMSError` will be raised. If ``inPlace`` is ``True`` and + ``outputMode != im.mode``, a :exc:`PyCMSError` will be raised. + If an error occurs during application of the profiles, + a :exc:`PyCMSError` will be raised. + If ``outputMode`` is not a mode supported by the ``outputProfile`` (or by pyCMS), + a :exc:`PyCMSError` will be raised. + + This function applies an ICC transformation to im from ``inputProfile``'s + color space to ``outputProfile``'s color space using the specified rendering + intent to decide how to handle out-of-gamut colors. + + ``outputMode`` can be used to specify that a color mode conversion is to + be done using these profiles, but the specified profiles must be able + to handle that mode. I.e., if converting im from RGB to CMYK using + profiles, the input profile must handle RGB data, and the output + profile must handle CMYK data. + + :param im: An open :py:class:`~PIL.Image.Image` object (i.e. Image.new(...) + or Image.open(...), etc.) + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this image, or a profile object + :param outputProfile: String, as a valid filename path to the ICC output + profile you wish to use for this image, or a profile object + :param renderingIntent: Integer (0-3) specifying the rendering intent you + wish to use for the transform + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :param outputMode: A valid PIL mode for the output image (i.e. "RGB", + "CMYK", etc.). Note: if rendering the image "inPlace", outputMode + MUST be the same mode as the input, or omitted completely. If + omitted, the outputMode will be the same as the mode of the input + image (im.mode) + :param inPlace: Boolean. If ``True``, the original image is modified in-place, + and ``None`` is returned. If ``False`` (default), a new + :py:class:`~PIL.Image.Image` object is returned with the transform applied. + :param flags: Integer (0-...) specifying additional flags + :returns: Either None or a new :py:class:`~PIL.Image.Image` object, depending on + the value of ``inPlace`` + :exception PyCMSError: + """ + + if outputMode is None: + outputMode = im.mode + + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): + msg = "renderingIntent must be an integer between 0 and 3" + raise PyCMSError(msg) + + if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): + msg = f"flags must be an integer between 0 and {_MAX_FLAG}" + raise PyCMSError(msg) + + try: + if not isinstance(inputProfile, ImageCmsProfile): + inputProfile = ImageCmsProfile(inputProfile) + if not isinstance(outputProfile, ImageCmsProfile): + outputProfile = ImageCmsProfile(outputProfile) + transform = ImageCmsTransform( + inputProfile, + outputProfile, + im.mode, + outputMode, + renderingIntent, + flags=flags, + ) + if inPlace: + transform.apply_in_place(im) + imOut = None + else: + imOut = transform.apply(im) + except (OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + return imOut + + +def getOpenProfile( + profileFilename: str | SupportsRead[bytes] | core.CmsProfile, +) -> ImageCmsProfile: + """ + (pyCMS) Opens an ICC profile file. + + The PyCMSProfile object can be passed back into pyCMS for use in creating + transforms and such (as in ImageCms.buildTransformFromOpenProfiles()). + + If ``profileFilename`` is not a valid filename for an ICC profile, + a :exc:`PyCMSError` will be raised. + + :param profileFilename: String, as a valid filename path to the ICC profile + you wish to open, or a file-like object. + :returns: A CmsProfile class object. + :exception PyCMSError: + """ + + try: + return ImageCmsProfile(profileFilename) + except (OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def buildTransform( + inputProfile: _CmsProfileCompatible, + outputProfile: _CmsProfileCompatible, + inMode: str, + outMode: str, + renderingIntent: Intent = Intent.PERCEPTUAL, + flags: Flags = Flags.NONE, +) -> ImageCmsTransform: + """ + (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the + ``outputProfile``. Use applyTransform to apply the transform to a given + image. + + If the input or output profiles specified are not valid filenames, a + :exc:`PyCMSError` will be raised. If an error occurs during creation + of the transform, a :exc:`PyCMSError` will be raised. + + If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile`` + (or by pyCMS), a :exc:`PyCMSError` will be raised. + + This function builds and returns an ICC transform from the ``inputProfile`` + to the ``outputProfile`` using the ``renderingIntent`` to determine what to do + with out-of-gamut colors. It will ONLY work for converting images that + are in ``inMode`` to images that are in ``outMode`` color format (PIL mode, + i.e. "RGB", "RGBA", "CMYK", etc.). + + Building the transform is a fair part of the overhead in + ImageCms.profileToProfile(), so if you're planning on converting multiple + images using the same input/output settings, this can save you time. + Once you have a transform object, it can be used with + ImageCms.applyProfile() to convert images without the need to re-compute + the lookup table for the transform. + + The reason pyCMS returns a class object rather than a handle directly + to the transform is that it needs to keep track of the PIL input/output + modes that the transform is meant for. These attributes are stored in + the ``inMode`` and ``outMode`` attributes of the object (which can be + manually overridden if you really want to, but I don't know of any + time that would be of use, or would even work). + + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this transform, or a profile object + :param outputProfile: String, as a valid filename path to the ICC output + profile you wish to use for this transform, or a profile object + :param inMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param outMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param renderingIntent: Integer (0-3) specifying the rendering intent you + wish to use for the transform + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :param flags: Integer (0-...) specifying additional flags + :returns: A CmsTransform class object. + :exception PyCMSError: + """ + + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): + msg = "renderingIntent must be an integer between 0 and 3" + raise PyCMSError(msg) + + if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): + msg = f"flags must be an integer between 0 and {_MAX_FLAG}" + raise PyCMSError(msg) + + try: + if not isinstance(inputProfile, ImageCmsProfile): + inputProfile = ImageCmsProfile(inputProfile) + if not isinstance(outputProfile, ImageCmsProfile): + outputProfile = ImageCmsProfile(outputProfile) + return ImageCmsTransform( + inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags + ) + except (OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def buildProofTransform( + inputProfile: _CmsProfileCompatible, + outputProfile: _CmsProfileCompatible, + proofProfile: _CmsProfileCompatible, + inMode: str, + outMode: str, + renderingIntent: Intent = Intent.PERCEPTUAL, + proofRenderingIntent: Intent = Intent.ABSOLUTE_COLORIMETRIC, + flags: Flags = Flags.SOFTPROOFING, +) -> ImageCmsTransform: + """ + (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the + ``outputProfile``, but tries to simulate the result that would be + obtained on the ``proofProfile`` device. + + If the input, output, or proof profiles specified are not valid + filenames, a :exc:`PyCMSError` will be raised. + + If an error occurs during creation of the transform, + a :exc:`PyCMSError` will be raised. + + If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile`` + (or by pyCMS), a :exc:`PyCMSError` will be raised. + + This function builds and returns an ICC transform from the ``inputProfile`` + to the ``outputProfile``, but tries to simulate the result that would be + obtained on the ``proofProfile`` device using ``renderingIntent`` and + ``proofRenderingIntent`` to determine what to do with out-of-gamut + colors. This is known as "soft-proofing". It will ONLY work for + converting images that are in ``inMode`` to images that are in outMode + color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.). + + Usage of the resulting transform object is exactly the same as with + ImageCms.buildTransform(). + + Proof profiling is generally used when using an output device to get a + good idea of what the final printed/displayed image would look like on + the ``proofProfile`` device when it's quicker and easier to use the + output device for judging color. Generally, this means that the + output device is a monitor, or a dye-sub printer (etc.), and the simulated + device is something more expensive, complicated, or time consuming + (making it difficult to make a real print for color judgement purposes). + + Soft-proofing basically functions by adjusting the colors on the + output device to match the colors of the device being simulated. However, + when the simulated device has a much wider gamut than the output + device, you may obtain marginal results. + + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this transform, or a profile object + :param outputProfile: String, as a valid filename path to the ICC output + (monitor, usually) profile you wish to use for this transform, or a + profile object + :param proofProfile: String, as a valid filename path to the ICC proof + profile you wish to use for this transform, or a profile object + :param inMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param outMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param renderingIntent: Integer (0-3) specifying the rendering intent you + wish to use for the input->proof (simulated) transform + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :param proofRenderingIntent: Integer (0-3) specifying the rendering intent + you wish to use for proof->output transform + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :param flags: Integer (0-...) specifying additional flags + :returns: A CmsTransform class object. + :exception PyCMSError: + """ + + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): + msg = "renderingIntent must be an integer between 0 and 3" + raise PyCMSError(msg) + + if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): + msg = f"flags must be an integer between 0 and {_MAX_FLAG}" + raise PyCMSError(msg) + + try: + if not isinstance(inputProfile, ImageCmsProfile): + inputProfile = ImageCmsProfile(inputProfile) + if not isinstance(outputProfile, ImageCmsProfile): + outputProfile = ImageCmsProfile(outputProfile) + if not isinstance(proofProfile, ImageCmsProfile): + proofProfile = ImageCmsProfile(proofProfile) + return ImageCmsTransform( + inputProfile, + outputProfile, + inMode, + outMode, + renderingIntent, + proofProfile, + proofRenderingIntent, + flags, + ) + except (OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +buildTransformFromOpenProfiles = buildTransform +buildProofTransformFromOpenProfiles = buildProofTransform + + +def applyTransform( + im: Image.Image, transform: ImageCmsTransform, inPlace: bool = False +) -> Image.Image | None: + """ + (pyCMS) Applies a transform to a given image. + + If ``im.mode != transform.input_mode``, a :exc:`PyCMSError` is raised. + + If ``inPlace`` is ``True`` and ``transform.input_mode != transform.output_mode``, a + :exc:`PyCMSError` is raised. + + If ``im.mode``, ``transform.input_mode`` or ``transform.output_mode`` is not + supported by pyCMSdll or the profiles you used for the transform, a + :exc:`PyCMSError` is raised. + + If an error occurs while the transform is being applied, + a :exc:`PyCMSError` is raised. + + This function applies a pre-calculated transform (from + ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) + to an image. The transform can be used for multiple images, saving + considerable calculation time if doing the same conversion multiple times. + + If you want to modify im in-place instead of receiving a new image as + the return value, set ``inPlace`` to ``True``. This can only be done if + ``transform.input_mode`` and ``transform.output_mode`` are the same, because we + can't change the mode in-place (the buffer sizes for some modes are + different). The default behavior is to return a new :py:class:`~PIL.Image.Image` + object of the same dimensions in mode ``transform.output_mode``. + + :param im: An :py:class:`~PIL.Image.Image` object, and ``im.mode`` must be the same + as the ``input_mode`` supported by the transform. + :param transform: A valid CmsTransform class object + :param inPlace: Bool. If ``True``, ``im`` is modified in place and ``None`` is + returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the + transform applied is returned (and ``im`` is not changed). The default is + ``False``. + :returns: Either ``None``, or a new :py:class:`~PIL.Image.Image` object, + depending on the value of ``inPlace``. The profile will be returned in + the image's ``info['icc_profile']``. + :exception PyCMSError: + """ + + try: + if inPlace: + transform.apply_in_place(im) + imOut = None + else: + imOut = transform.apply(im) + except (TypeError, ValueError) as v: + raise PyCMSError(v) from v + + return imOut + + +def createProfile( + colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = 0 +) -> core.CmsProfile: + """ + (pyCMS) Creates a profile. + + If colorSpace not in ``["LAB", "XYZ", "sRGB"]``, + a :exc:`PyCMSError` is raised. + + If using LAB and ``colorTemp`` is not a positive integer, + a :exc:`PyCMSError` is raised. + + If an error occurs while creating the profile, + a :exc:`PyCMSError` is raised. + + Use this function to create common profiles on-the-fly instead of + having to supply a profile on disk and knowing the path to it. It + returns a normal CmsProfile object that can be passed to + ImageCms.buildTransformFromOpenProfiles() to create a transform to apply + to images. + + :param colorSpace: String, the color space of the profile you wish to + create. + Currently only "LAB", "XYZ", and "sRGB" are supported. + :param colorTemp: Positive number for the white point for the profile, in + degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50 + illuminant if omitted (5000k). colorTemp is ONLY applied to LAB + profiles, and is ignored for XYZ and sRGB. + :returns: A CmsProfile class object + :exception PyCMSError: + """ + + if colorSpace not in ["LAB", "XYZ", "sRGB"]: + msg = ( + f"Color space not supported for on-the-fly profile creation ({colorSpace})" + ) + raise PyCMSError(msg) + + if colorSpace == "LAB": + try: + colorTemp = float(colorTemp) + except (TypeError, ValueError) as e: + msg = f'Color temperature must be numeric, "{colorTemp}" not valid' + raise PyCMSError(msg) from e + + try: + return core.createProfile(colorSpace, colorTemp) + except (TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileName(profile: _CmsProfileCompatible) -> str: + """ + + (pyCMS) Gets the internal product name for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, + a :exc:`PyCMSError` is raised If an error occurs while trying + to obtain the name tag, a :exc:`PyCMSError` is raised. + + Use this function to obtain the INTERNAL name of the profile (stored + in an ICC tag in the profile itself), usually the one used when the + profile was originally created. Sometimes this tag also contains + additional information supplied by the creator. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal name of the profile as stored + in an ICC tag. + :exception PyCMSError: + """ + + try: + # add an extra newline to preserve pyCMS compatibility + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + # do it in python, not c. + # // name was "%s - %s" (model, manufacturer) || Description , + # // but if the Model and Manufacturer were the same or the model + # // was long, Just the model, in 1.x + model = profile.profile.model + manufacturer = profile.profile.manufacturer + + if not (model or manufacturer): + return (profile.profile.profile_description or "") + "\n" + if not manufacturer or (model and len(model) > 30): + return f"{model}\n" + return f"{model} - {manufacturer}\n" + + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileInfo(profile: _CmsProfileCompatible) -> str: + """ + (pyCMS) Gets the internal product information for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, + a :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the info tag, + a :exc:`PyCMSError` is raised. + + Use this function to obtain the information stored in the profile's + info tag. This often contains details about the profile, and how it + was created, as supplied by the creator. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. + :exception PyCMSError: + """ + + try: + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + # add an extra newline to preserve pyCMS compatibility + # Python, not C. the white point bits weren't working well, + # so skipping. + # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint + description = profile.profile.profile_description + cpright = profile.profile.copyright + elements = [element for element in (description, cpright) if element] + return "\r\n\r\n".join(elements) + "\r\n\r\n" + + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileCopyright(profile: _CmsProfileCompatible) -> str: + """ + (pyCMS) Gets the copyright for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the copyright tag, + a :exc:`PyCMSError` is raised. + + Use this function to obtain the information stored in the profile's + copyright tag. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. + :exception PyCMSError: + """ + try: + # add an extra newline to preserve pyCMS compatibility + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + return (profile.profile.copyright or "") + "\n" + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileManufacturer(profile: _CmsProfileCompatible) -> str: + """ + (pyCMS) Gets the manufacturer for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the manufacturer tag, a + :exc:`PyCMSError` is raised. + + Use this function to obtain the information stored in the profile's + manufacturer tag. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. + :exception PyCMSError: + """ + try: + # add an extra newline to preserve pyCMS compatibility + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + return (profile.profile.manufacturer or "") + "\n" + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileModel(profile: _CmsProfileCompatible) -> str: + """ + (pyCMS) Gets the model for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the model tag, + a :exc:`PyCMSError` is raised. + + Use this function to obtain the information stored in the profile's + model tag. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. + :exception PyCMSError: + """ + + try: + # add an extra newline to preserve pyCMS compatibility + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + return (profile.profile.model or "") + "\n" + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileDescription(profile: _CmsProfileCompatible) -> str: + """ + (pyCMS) Gets the description for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the description tag, + a :exc:`PyCMSError` is raised. + + Use this function to obtain the information stored in the profile's + description tag. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in an + ICC tag. + :exception PyCMSError: + """ + + try: + # add an extra newline to preserve pyCMS compatibility + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + return (profile.profile.profile_description or "") + "\n" + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getDefaultIntent(profile: _CmsProfileCompatible) -> int: + """ + (pyCMS) Gets the default intent name for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the default intent, a + :exc:`PyCMSError` is raised. + + Use this function to determine the default (and usually best optimized) + rendering intent for this profile. Most profiles support multiple + rendering intents, but are intended mostly for one type of conversion. + If you wish to use a different intent than returned, use + ImageCms.isIntentSupported() to verify it will work first. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: Integer 0-3 specifying the default rendering intent for this + profile. + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :exception PyCMSError: + """ + + try: + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + return profile.profile.rendering_intent + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def isIntentSupported( + profile: _CmsProfileCompatible, intent: Intent, direction: Direction +) -> Literal[-1, 1]: + """ + (pyCMS) Checks if a given intent is supported. + + Use this function to verify that you can use your desired + ``intent`` with ``profile``, and that ``profile`` can be used for the + input/output/proof profile as you desire. + + Some profiles are created specifically for one "direction", can cannot + be used for others. Some profiles can only be used for certain + rendering intents, so it's best to either verify this before trying + to create a transform with them (using this function), or catch the + potential :exc:`PyCMSError` that will occur if they don't + support the modes you select. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :param intent: Integer (0-3) specifying the rendering intent you wish to + use with this profile + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :param direction: Integer specifying if the profile is to be used for + input, output, or proof + + INPUT = 0 (or use ImageCms.Direction.INPUT) + OUTPUT = 1 (or use ImageCms.Direction.OUTPUT) + PROOF = 2 (or use ImageCms.Direction.PROOF) + + :returns: 1 if the intent/direction are supported, -1 if they are not. + :exception PyCMSError: + """ + + try: + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + # FIXME: I get different results for the same data w. different + # compilers. Bug in LittleCMS or in the binding? + if profile.profile.is_intent_supported(intent, direction): + return 1 + else: + return -1 + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageColor.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageColor.py new file mode 100644 index 0000000000000000000000000000000000000000..9a15a8eb7597998f1bc9a01e8eae3588c087838b --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageColor.py @@ -0,0 +1,320 @@ +# +# The Python Imaging Library +# $Id$ +# +# map CSS3-style colour description strings to RGB +# +# History: +# 2002-10-24 fl Added support for CSS-style color strings +# 2002-12-15 fl Added RGBA support +# 2004-03-27 fl Fixed remaining int() problems for Python 1.5.2 +# 2004-07-19 fl Fixed gray/grey spelling issues +# 2009-03-05 fl Fixed rounding error in grayscale calculation +# +# Copyright (c) 2002-2004 by Secret Labs AB +# Copyright (c) 2002-2004 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import re +from functools import lru_cache + +from . import Image + + +@lru_cache +def getrgb(color: str) -> tuple[int, int, int] | tuple[int, int, int, int]: + """ + Convert a color string to an RGB or RGBA tuple. If the string cannot be + parsed, this function raises a :py:exc:`ValueError` exception. + + .. versionadded:: 1.1.4 + + :param color: A color string + :return: ``(red, green, blue[, alpha])`` + """ + if len(color) > 100: + msg = "color specifier is too long" + raise ValueError(msg) + color = color.lower() + + rgb = colormap.get(color, None) + if rgb: + if isinstance(rgb, tuple): + return rgb + rgb_tuple = getrgb(rgb) + assert len(rgb_tuple) == 3 + colormap[color] = rgb_tuple + return rgb_tuple + + # check for known string formats + if re.match("#[a-f0-9]{3}$", color): + return int(color[1] * 2, 16), int(color[2] * 2, 16), int(color[3] * 2, 16) + + if re.match("#[a-f0-9]{4}$", color): + return ( + int(color[1] * 2, 16), + int(color[2] * 2, 16), + int(color[3] * 2, 16), + int(color[4] * 2, 16), + ) + + if re.match("#[a-f0-9]{6}$", color): + return int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16) + + if re.match("#[a-f0-9]{8}$", color): + return ( + int(color[1:3], 16), + int(color[3:5], 16), + int(color[5:7], 16), + int(color[7:9], 16), + ) + + m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) + if m: + return int(m.group(1)), int(m.group(2)), int(m.group(3)) + + m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color) + if m: + return ( + int((int(m.group(1)) * 255) / 100.0 + 0.5), + int((int(m.group(2)) * 255) / 100.0 + 0.5), + int((int(m.group(3)) * 255) / 100.0 + 0.5), + ) + + m = re.match( + r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color + ) + if m: + from colorsys import hls_to_rgb + + rgb_floats = hls_to_rgb( + float(m.group(1)) / 360.0, + float(m.group(3)) / 100.0, + float(m.group(2)) / 100.0, + ) + return ( + int(rgb_floats[0] * 255 + 0.5), + int(rgb_floats[1] * 255 + 0.5), + int(rgb_floats[2] * 255 + 0.5), + ) + + m = re.match( + r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color + ) + if m: + from colorsys import hsv_to_rgb + + rgb_floats = hsv_to_rgb( + float(m.group(1)) / 360.0, + float(m.group(2)) / 100.0, + float(m.group(3)) / 100.0, + ) + return ( + int(rgb_floats[0] * 255 + 0.5), + int(rgb_floats[1] * 255 + 0.5), + int(rgb_floats[2] * 255 + 0.5), + ) + + m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) + if m: + return int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4)) + msg = f"unknown color specifier: {repr(color)}" + raise ValueError(msg) + + +@lru_cache +def getcolor(color: str, mode: str) -> int | tuple[int, ...]: + """ + Same as :py:func:`~PIL.ImageColor.getrgb` for most modes. However, if + ``mode`` is HSV, converts the RGB value to a HSV value, or if ``mode`` is + not color or a palette image, converts the RGB value to a grayscale value. + If the string cannot be parsed, this function raises a :py:exc:`ValueError` + exception. + + .. versionadded:: 1.1.4 + + :param color: A color string + :param mode: Convert result to this mode + :return: ``graylevel, (graylevel, alpha) or (red, green, blue[, alpha])`` + """ + # same as getrgb, but converts the result to the given mode + rgb, alpha = getrgb(color), 255 + if len(rgb) == 4: + alpha = rgb[3] + rgb = rgb[:3] + + if mode == "HSV": + from colorsys import rgb_to_hsv + + r, g, b = rgb + h, s, v = rgb_to_hsv(r / 255, g / 255, b / 255) + return int(h * 255), int(s * 255), int(v * 255) + elif Image.getmodebase(mode) == "L": + r, g, b = rgb + # ITU-R Recommendation 601-2 for nonlinear RGB + # scaled to 24 bits to match the convert's implementation. + graylevel = (r * 19595 + g * 38470 + b * 7471 + 0x8000) >> 16 + if mode[-1] == "A": + return graylevel, alpha + return graylevel + elif mode[-1] == "A": + return rgb + (alpha,) + return rgb + + +colormap: dict[str, str | tuple[int, int, int]] = { + # X11 colour table from https://drafts.csswg.org/css-color-4/, with + # gray/grey spelling issues fixed. This is a superset of HTML 4.0 + # colour names used in CSS 1. + "aliceblue": "#f0f8ff", + "antiquewhite": "#faebd7", + "aqua": "#00ffff", + "aquamarine": "#7fffd4", + "azure": "#f0ffff", + "beige": "#f5f5dc", + "bisque": "#ffe4c4", + "black": "#000000", + "blanchedalmond": "#ffebcd", + "blue": "#0000ff", + "blueviolet": "#8a2be2", + "brown": "#a52a2a", + "burlywood": "#deb887", + "cadetblue": "#5f9ea0", + "chartreuse": "#7fff00", + "chocolate": "#d2691e", + "coral": "#ff7f50", + "cornflowerblue": "#6495ed", + "cornsilk": "#fff8dc", + "crimson": "#dc143c", + "cyan": "#00ffff", + "darkblue": "#00008b", + "darkcyan": "#008b8b", + "darkgoldenrod": "#b8860b", + "darkgray": "#a9a9a9", + "darkgrey": "#a9a9a9", + "darkgreen": "#006400", + "darkkhaki": "#bdb76b", + "darkmagenta": "#8b008b", + "darkolivegreen": "#556b2f", + "darkorange": "#ff8c00", + "darkorchid": "#9932cc", + "darkred": "#8b0000", + "darksalmon": "#e9967a", + "darkseagreen": "#8fbc8f", + "darkslateblue": "#483d8b", + "darkslategray": "#2f4f4f", + "darkslategrey": "#2f4f4f", + "darkturquoise": "#00ced1", + "darkviolet": "#9400d3", + "deeppink": "#ff1493", + "deepskyblue": "#00bfff", + "dimgray": "#696969", + "dimgrey": "#696969", + "dodgerblue": "#1e90ff", + "firebrick": "#b22222", + "floralwhite": "#fffaf0", + "forestgreen": "#228b22", + "fuchsia": "#ff00ff", + "gainsboro": "#dcdcdc", + "ghostwhite": "#f8f8ff", + "gold": "#ffd700", + "goldenrod": "#daa520", + "gray": "#808080", + "grey": "#808080", + "green": "#008000", + "greenyellow": "#adff2f", + "honeydew": "#f0fff0", + "hotpink": "#ff69b4", + "indianred": "#cd5c5c", + "indigo": "#4b0082", + "ivory": "#fffff0", + "khaki": "#f0e68c", + "lavender": "#e6e6fa", + "lavenderblush": "#fff0f5", + "lawngreen": "#7cfc00", + "lemonchiffon": "#fffacd", + "lightblue": "#add8e6", + "lightcoral": "#f08080", + "lightcyan": "#e0ffff", + "lightgoldenrodyellow": "#fafad2", + "lightgreen": "#90ee90", + "lightgray": "#d3d3d3", + "lightgrey": "#d3d3d3", + "lightpink": "#ffb6c1", + "lightsalmon": "#ffa07a", + "lightseagreen": "#20b2aa", + "lightskyblue": "#87cefa", + "lightslategray": "#778899", + "lightslategrey": "#778899", + "lightsteelblue": "#b0c4de", + "lightyellow": "#ffffe0", + "lime": "#00ff00", + "limegreen": "#32cd32", + "linen": "#faf0e6", + "magenta": "#ff00ff", + "maroon": "#800000", + "mediumaquamarine": "#66cdaa", + "mediumblue": "#0000cd", + "mediumorchid": "#ba55d3", + "mediumpurple": "#9370db", + "mediumseagreen": "#3cb371", + "mediumslateblue": "#7b68ee", + "mediumspringgreen": "#00fa9a", + "mediumturquoise": "#48d1cc", + "mediumvioletred": "#c71585", + "midnightblue": "#191970", + "mintcream": "#f5fffa", + "mistyrose": "#ffe4e1", + "moccasin": "#ffe4b5", + "navajowhite": "#ffdead", + "navy": "#000080", + "oldlace": "#fdf5e6", + "olive": "#808000", + "olivedrab": "#6b8e23", + "orange": "#ffa500", + "orangered": "#ff4500", + "orchid": "#da70d6", + "palegoldenrod": "#eee8aa", + "palegreen": "#98fb98", + "paleturquoise": "#afeeee", + "palevioletred": "#db7093", + "papayawhip": "#ffefd5", + "peachpuff": "#ffdab9", + "peru": "#cd853f", + "pink": "#ffc0cb", + "plum": "#dda0dd", + "powderblue": "#b0e0e6", + "purple": "#800080", + "rebeccapurple": "#663399", + "red": "#ff0000", + "rosybrown": "#bc8f8f", + "royalblue": "#4169e1", + "saddlebrown": "#8b4513", + "salmon": "#fa8072", + "sandybrown": "#f4a460", + "seagreen": "#2e8b57", + "seashell": "#fff5ee", + "sienna": "#a0522d", + "silver": "#c0c0c0", + "skyblue": "#87ceeb", + "slateblue": "#6a5acd", + "slategray": "#708090", + "slategrey": "#708090", + "snow": "#fffafa", + "springgreen": "#00ff7f", + "steelblue": "#4682b4", + "tan": "#d2b48c", + "teal": "#008080", + "thistle": "#d8bfd8", + "tomato": "#ff6347", + "turquoise": "#40e0d0", + "violet": "#ee82ee", + "wheat": "#f5deb3", + "white": "#ffffff", + "whitesmoke": "#f5f5f5", + "yellow": "#ffff00", + "yellowgreen": "#9acd32", +} diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageDraw.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageDraw.py new file mode 100644 index 0000000000000000000000000000000000000000..8bcf2d8ee06ff87e966cf7bab9e1f448848cedc8 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageDraw.py @@ -0,0 +1,1036 @@ +# +# The Python Imaging Library +# $Id$ +# +# drawing interface operations +# +# History: +# 1996-04-13 fl Created (experimental) +# 1996-08-07 fl Filled polygons, ellipses. +# 1996-08-13 fl Added text support +# 1998-06-28 fl Handle I and F images +# 1998-12-29 fl Added arc; use arc primitive to draw ellipses +# 1999-01-10 fl Added shape stuff (experimental) +# 1999-02-06 fl Added bitmap support +# 1999-02-11 fl Changed all primitives to take options +# 1999-02-20 fl Fixed backwards compatibility +# 2000-10-12 fl Copy on write, when necessary +# 2001-02-18 fl Use default ink for bitmap/text also in fill mode +# 2002-10-24 fl Added support for CSS-style color strings +# 2002-12-10 fl Added experimental support for RGBA-on-RGB drawing +# 2002-12-11 fl Refactored low-level drawing API (work in progress) +# 2004-08-26 fl Made Draw() a factory function, added getdraw() support +# 2004-09-04 fl Added width support to line primitive +# 2004-09-10 fl Added font mode handling +# 2006-06-19 fl Added font bearing support (getmask2) +# +# Copyright (c) 1997-2006 by Secret Labs AB +# Copyright (c) 1996-2006 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import math +import struct +from collections.abc import Sequence +from typing import cast + +from . import Image, ImageColor, ImageText + +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + from types import ModuleType + from typing import Any, AnyStr + + from . import ImageDraw2, ImageFont + from ._typing import Coords, _Ink + +# experimental access to the outline API +Outline: Callable[[], Image.core._Outline] = Image.core.outline + +""" +A simple 2D drawing interface for PIL images. +

+Application code should use the Draw factory, instead of +directly. +""" + + +class ImageDraw: + font: ( + ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont | None + ) = None + + def __init__(self, im: Image.Image, mode: str | None = None) -> None: + """ + Create a drawing instance. + + :param im: The image to draw in. + :param mode: Optional mode to use for color values. For RGB + images, this argument can be RGB or RGBA (to blend the + drawing into the image). For all other modes, this argument + must be the same as the image mode. If omitted, the mode + defaults to the mode of the image. + """ + im._ensure_mutable() + blend = 0 + if mode is None: + mode = im.mode + if mode != im.mode: + if mode == "RGBA" and im.mode == "RGB": + blend = 1 + else: + msg = "mode mismatch" + raise ValueError(msg) + if mode == "P": + self.palette = im.palette + else: + self.palette = None + self._image = im + self.im = im.im + self.draw = Image.core.draw(self.im, blend) + self.mode = mode + if mode in ("I", "F"): + self.ink = self.draw.draw_ink(1) + else: + self.ink = self.draw.draw_ink(-1) + if mode in ("1", "P", "I", "F"): + # FIXME: fix Fill2 to properly support matte for I+F images + self.fontmode = "1" + else: + self.fontmode = "L" # aliasing is okay for other modes + self.fill = False + + def getfont( + self, + ) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont: + """ + Get the current default font. + + To set the default font for this ImageDraw instance:: + + from PIL import ImageDraw, ImageFont + draw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + + To set the default font for all future ImageDraw instances:: + + from PIL import ImageDraw, ImageFont + ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + + If the current default font is ``None``, + it is initialized with ``ImageFont.load_default()``. + + :returns: An image font.""" + if not self.font: + # FIXME: should add a font repository + from . import ImageFont + + self.font = ImageFont.load_default() + return self.font + + def _getfont( + self, font_size: float | None + ) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont: + if font_size is not None: + from . import ImageFont + + return ImageFont.load_default(font_size) + else: + return self.getfont() + + def _getink( + self, ink: _Ink | None, fill: _Ink | None = None + ) -> tuple[int | None, int | None]: + result_ink = None + result_fill = None + if ink is None and fill is None: + if self.fill: + result_fill = self.ink + else: + result_ink = self.ink + else: + if ink is not None: + if isinstance(ink, str): + ink = ImageColor.getcolor(ink, self.mode) + if self.palette and isinstance(ink, tuple): + ink = self.palette.getcolor(ink, self._image) + result_ink = self.draw.draw_ink(ink) + if fill is not None: + if isinstance(fill, str): + fill = ImageColor.getcolor(fill, self.mode) + if self.palette and isinstance(fill, tuple): + fill = self.palette.getcolor(fill, self._image) + result_fill = self.draw.draw_ink(fill) + return result_ink, result_fill + + def arc( + self, + xy: Coords, + start: float, + end: float, + fill: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw an arc.""" + ink, fill = self._getink(fill) + if ink is not None: + self.draw.draw_arc(xy, start, end, ink, width) + + def bitmap( + self, xy: Sequence[int], bitmap: Image.Image, fill: _Ink | None = None + ) -> None: + """Draw a bitmap.""" + bitmap.load() + ink, fill = self._getink(fill) + if ink is None: + ink = fill + if ink is not None: + self.draw.draw_bitmap(xy, bitmap.im, ink) + + def chord( + self, + xy: Coords, + start: float, + end: float, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a chord.""" + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_chord(xy, start, end, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + self.draw.draw_chord(xy, start, end, ink, 0, width) + + def ellipse( + self, + xy: Coords, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw an ellipse.""" + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_ellipse(xy, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + self.draw.draw_ellipse(xy, ink, 0, width) + + def circle( + self, + xy: Sequence[float], + radius: float, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a circle given center coordinates and a radius.""" + ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius) + self.ellipse(ellipse_xy, fill, outline, width) + + def line( + self, + xy: Coords, + fill: _Ink | None = None, + width: int = 0, + joint: str | None = None, + ) -> None: + """Draw a line, or a connected sequence of line segments.""" + ink = self._getink(fill)[0] + if ink is not None: + self.draw.draw_lines(xy, ink, width) + if joint == "curve" and width > 4: + points: Sequence[Sequence[float]] + if isinstance(xy[0], (list, tuple)): + points = cast(Sequence[Sequence[float]], xy) + else: + points = [ + cast(Sequence[float], tuple(xy[i : i + 2])) + for i in range(0, len(xy), 2) + ] + for i in range(1, len(points) - 1): + point = points[i] + angles = [ + math.degrees(math.atan2(end[0] - start[0], start[1] - end[1])) + % 360 + for start, end in ( + (points[i - 1], point), + (point, points[i + 1]), + ) + ] + if angles[0] == angles[1]: + # This is a straight line, so no joint is required + continue + + def coord_at_angle( + coord: Sequence[float], angle: float + ) -> tuple[float, ...]: + x, y = coord + angle -= 90 + distance = width / 2 - 1 + return tuple( + p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d)) + for p, p_d in ( + (x, distance * math.cos(math.radians(angle))), + (y, distance * math.sin(math.radians(angle))), + ) + ) + + flipped = ( + angles[1] > angles[0] and angles[1] - 180 > angles[0] + ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0]) + coords = [ + (point[0] - width / 2 + 1, point[1] - width / 2 + 1), + (point[0] + width / 2 - 1, point[1] + width / 2 - 1), + ] + if flipped: + start, end = (angles[1] + 90, angles[0] + 90) + else: + start, end = (angles[0] - 90, angles[1] - 90) + self.pieslice(coords, start - 90, end - 90, fill) + + if width > 8: + # Cover potential gaps between the line and the joint + if flipped: + gap_coords = [ + coord_at_angle(point, angles[0] + 90), + point, + coord_at_angle(point, angles[1] + 90), + ] + else: + gap_coords = [ + coord_at_angle(point, angles[0] - 90), + point, + coord_at_angle(point, angles[1] - 90), + ] + self.line(gap_coords, fill, width=3) + + def shape( + self, + shape: Image.core._Outline, + fill: _Ink | None = None, + outline: _Ink | None = None, + ) -> None: + """(Experimental) Draw a shape.""" + shape.close() + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_outline(shape, fill_ink, 1) + if ink is not None and ink != fill_ink: + self.draw.draw_outline(shape, ink, 0) + + def pieslice( + self, + xy: Coords, + start: float, + end: float, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a pieslice.""" + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_pieslice(xy, start, end, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + self.draw.draw_pieslice(xy, start, end, ink, 0, width) + + def point(self, xy: Coords, fill: _Ink | None = None) -> None: + """Draw one or more individual pixels.""" + ink, fill = self._getink(fill) + if ink is not None: + self.draw.draw_points(xy, ink) + + def polygon( + self, + xy: Coords, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a polygon.""" + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_polygon(xy, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + if width == 1: + self.draw.draw_polygon(xy, ink, 0, width) + elif self.im is not None: + # To avoid expanding the polygon outwards, + # use the fill as a mask + mask = Image.new("1", self.im.size) + mask_ink = self._getink(1)[0] + draw = Draw(mask) + draw.draw.draw_polygon(xy, mask_ink, 1) + + self.draw.draw_polygon(xy, ink, 0, width * 2 - 1, mask.im) + + def regular_polygon( + self, + bounding_circle: Sequence[Sequence[float] | float], + n_sides: int, + rotation: float = 0, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a regular polygon.""" + xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) + self.polygon(xy, fill, outline, width) + + def rectangle( + self, + xy: Coords, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a rectangle.""" + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_rectangle(xy, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + self.draw.draw_rectangle(xy, ink, 0, width) + + def rounded_rectangle( + self, + xy: Coords, + radius: float = 0, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + *, + corners: tuple[bool, bool, bool, bool] | None = None, + ) -> None: + """Draw a rounded rectangle.""" + if isinstance(xy[0], (list, tuple)): + (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy) + else: + x0, y0, x1, y1 = cast(Sequence[float], xy) + if x1 < x0: + msg = "x1 must be greater than or equal to x0" + raise ValueError(msg) + if y1 < y0: + msg = "y1 must be greater than or equal to y0" + raise ValueError(msg) + if corners is None: + corners = (True, True, True, True) + + d = radius * 2 + + x0 = round(x0) + y0 = round(y0) + x1 = round(x1) + y1 = round(y1) + full_x, full_y = False, False + if all(corners): + full_x = d >= x1 - x0 - 1 + if full_x: + # The two left and two right corners are joined + d = x1 - x0 + full_y = d >= y1 - y0 - 1 + if full_y: + # The two top and two bottom corners are joined + d = y1 - y0 + if full_x and full_y: + # If all corners are joined, that is a circle + return self.ellipse(xy, fill, outline, width) + + if d == 0 or not any(corners): + # If the corners have no curve, + # or there are no corners, + # that is a rectangle + return self.rectangle(xy, fill, outline, width) + + r = int(d // 2) + ink, fill_ink = self._getink(outline, fill) + + def draw_corners(pieslice: bool) -> None: + parts: tuple[tuple[tuple[float, float, float, float], int, int], ...] + if full_x: + # Draw top and bottom halves + parts = ( + ((x0, y0, x0 + d, y0 + d), 180, 360), + ((x0, y1 - d, x0 + d, y1), 0, 180), + ) + elif full_y: + # Draw left and right halves + parts = ( + ((x0, y0, x0 + d, y0 + d), 90, 270), + ((x1 - d, y0, x1, y0 + d), 270, 90), + ) + else: + # Draw four separate corners + parts = tuple( + part + for i, part in enumerate( + ( + ((x0, y0, x0 + d, y0 + d), 180, 270), + ((x1 - d, y0, x1, y0 + d), 270, 360), + ((x1 - d, y1 - d, x1, y1), 0, 90), + ((x0, y1 - d, x0 + d, y1), 90, 180), + ) + ) + if corners[i] + ) + for part in parts: + if pieslice: + self.draw.draw_pieslice(*(part + (fill_ink, 1))) + else: + self.draw.draw_arc(*(part + (ink, width))) + + if fill_ink is not None: + draw_corners(True) + + if full_x: + self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill_ink, 1) + elif x1 - r - 1 > x0 + r + 1: + self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill_ink, 1) + if not full_x and not full_y: + left = [x0, y0, x0 + r, y1] + if corners[0]: + left[1] += r + 1 + if corners[3]: + left[3] -= r + 1 + self.draw.draw_rectangle(left, fill_ink, 1) + + right = [x1 - r, y0, x1, y1] + if corners[1]: + right[1] += r + 1 + if corners[2]: + right[3] -= r + 1 + self.draw.draw_rectangle(right, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + draw_corners(False) + + if not full_x: + top = [x0, y0, x1, y0 + width - 1] + if corners[0]: + top[0] += r + 1 + if corners[1]: + top[2] -= r + 1 + self.draw.draw_rectangle(top, ink, 1) + + bottom = [x0, y1 - width + 1, x1, y1] + if corners[3]: + bottom[0] += r + 1 + if corners[2]: + bottom[2] -= r + 1 + self.draw.draw_rectangle(bottom, ink, 1) + if not full_y: + left = [x0, y0, x0 + width - 1, y1] + if corners[0]: + left[1] += r + 1 + if corners[3]: + left[3] -= r + 1 + self.draw.draw_rectangle(left, ink, 1) + + right = [x1 - width + 1, y0, x1, y1] + if corners[1]: + right[1] += r + 1 + if corners[2]: + right[3] -= r + 1 + self.draw.draw_rectangle(right, ink, 1) + + def text( + self, + xy: tuple[float, float], + text: AnyStr | ImageText.Text, + fill: _Ink | None = None, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + stroke_fill: _Ink | None = None, + embedded_color: bool = False, + *args: Any, + **kwargs: Any, + ) -> None: + """Draw text.""" + if isinstance(text, ImageText.Text): + image_text = text + else: + if font is None: + font = self._getfont(kwargs.get("font_size")) + image_text = ImageText.Text( + text, font, self.mode, spacing, direction, features, language + ) + if embedded_color: + image_text.embed_color() + if stroke_width: + image_text.stroke(stroke_width, stroke_fill) + + def getink(fill: _Ink | None) -> int: + ink, fill_ink = self._getink(fill) + if ink is None: + assert fill_ink is not None + return fill_ink + return ink + + ink = getink(fill) + if ink is None: + return + + stroke_ink = None + if image_text.stroke_width: + stroke_ink = ( + getink(image_text.stroke_fill) + if image_text.stroke_fill is not None + else ink + ) + + for xy, anchor, line in image_text._split(xy, anchor, align): + + def draw_text(ink: int, stroke_width: float = 0) -> None: + mode = self.fontmode + if stroke_width == 0 and embedded_color: + mode = "RGBA" + coord = [] + for i in range(2): + coord.append(int(xy[i])) + start = (math.modf(xy[0])[0], math.modf(xy[1])[0]) + try: + mask, offset = image_text.font.getmask2( # type: ignore[union-attr,misc] + line, + mode, + direction=direction, + features=features, + language=language, + stroke_width=stroke_width, + stroke_filled=True, + anchor=anchor, + ink=ink, + start=start, + *args, + **kwargs, + ) + coord = [coord[0] + offset[0], coord[1] + offset[1]] + except AttributeError: + try: + mask = image_text.font.getmask( # type: ignore[misc] + line, + mode, + direction, + features, + language, + stroke_width, + anchor, + ink, + start=start, + *args, + **kwargs, + ) + except TypeError: + mask = image_text.font.getmask(line) + if mode == "RGBA": + # image_text.font.getmask2(mode="RGBA") + # returns color in RGB bands and mask in A + # extract mask and set text alpha + color, mask = mask, mask.getband(3) + ink_alpha = struct.pack("i", ink)[3] + color.fillband(3, ink_alpha) + x, y = coord + if self.im is not None: + self.im.paste( + color, (x, y, x + mask.size[0], y + mask.size[1]), mask + ) + else: + self.draw.draw_bitmap(coord, mask, ink) + + if stroke_ink is not None: + # Draw stroked text + draw_text(stroke_ink, image_text.stroke_width) + + # Draw normal text + if ink != stroke_ink: + draw_text(ink) + else: + # Only draw normal text + draw_text(ink) + + def multiline_text( + self, + xy: tuple[float, float], + text: AnyStr, + fill: _Ink | None = None, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + stroke_fill: _Ink | None = None, + embedded_color: bool = False, + *, + font_size: float | None = None, + ) -> None: + return self.text( + xy, + text, + fill, + font, + anchor, + spacing, + align, + direction, + features, + language, + stroke_width, + stroke_fill, + embedded_color, + font_size=font_size, + ) + + def textlength( + self, + text: AnyStr, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + embedded_color: bool = False, + *, + font_size: float | None = None, + ) -> float: + """Get the length of a given string, in pixels with 1/64 precision.""" + if font is None: + font = self._getfont(font_size) + image_text = ImageText.Text( + text, + font, + self.mode, + direction=direction, + features=features, + language=language, + ) + if embedded_color: + image_text.embed_color() + return image_text.get_length() + + def textbbox( + self, + xy: tuple[float, float], + text: AnyStr, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + embedded_color: bool = False, + *, + font_size: float | None = None, + ) -> tuple[float, float, float, float]: + """Get the bounding box of a given string, in pixels.""" + if font is None: + font = self._getfont(font_size) + image_text = ImageText.Text( + text, font, self.mode, spacing, direction, features, language + ) + if embedded_color: + image_text.embed_color() + if stroke_width: + image_text.stroke(stroke_width) + return image_text.get_bbox(xy, anchor, align) + + def multiline_textbbox( + self, + xy: tuple[float, float], + text: AnyStr, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + embedded_color: bool = False, + *, + font_size: float | None = None, + ) -> tuple[float, float, float, float]: + return self.textbbox( + xy, + text, + font, + anchor, + spacing, + align, + direction, + features, + language, + stroke_width, + embedded_color, + font_size=font_size, + ) + + +def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw: + """ + A simple 2D drawing interface for PIL images. + + :param im: The image to draw in. + :param mode: Optional mode to use for color values. For RGB + images, this argument can be RGB or RGBA (to blend the + drawing into the image). For all other modes, this argument + must be the same as the image mode. If omitted, the mode + defaults to the mode of the image. + """ + try: + return getattr(im, "getdraw")(mode) + except AttributeError: + return ImageDraw(im, mode) + + +def getdraw(im: Image.Image | None = None) -> tuple[ImageDraw2.Draw | None, ModuleType]: + """ + :param im: The image to draw in. + :returns: A (drawing context, drawing resource factory) tuple. + """ + from . import ImageDraw2 + + draw = ImageDraw2.Draw(im) if im is not None else None + return draw, ImageDraw2 + + +def floodfill( + image: Image.Image, + xy: tuple[int, int], + value: float | tuple[int, ...], + border: float | tuple[int, ...] | None = None, + thresh: float = 0, +) -> None: + """ + .. warning:: This method is experimental. + + Fills a bounded region with a given color. + + :param image: Target image. + :param xy: Seed position (a 2-item coordinate tuple). See + :ref:`coordinate-system`. + :param value: Fill color. + :param border: Optional border value. If given, the region consists of + pixels with a color different from the border color. If not given, + the region consists of pixels having the same color as the seed + pixel. + :param thresh: Optional threshold value which specifies a maximum + tolerable difference of a pixel value from the 'background' in + order for it to be replaced. Useful for filling regions of + non-homogeneous, but similar, colors. + """ + # based on an implementation by Eric S. Raymond + # amended by yo1995 @20180806 + pixel = image.load() + assert pixel is not None + x, y = xy + try: + background = pixel[x, y] + if _color_diff(value, background) <= thresh: + return # seed point already has fill color + pixel[x, y] = value + except (ValueError, IndexError): + return # seed point outside image + edge = {(x, y)} + # use a set to keep record of current and previous edge pixels + # to reduce memory consumption + full_edge = set() + while edge: + new_edge = set() + for x, y in edge: # 4 adjacent method + for s, t in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)): + # If already processed, or if a coordinate is negative, skip + if (s, t) in full_edge or s < 0 or t < 0: + continue + try: + p = pixel[s, t] + except (ValueError, IndexError): + pass + else: + full_edge.add((s, t)) + if border is None: + fill = _color_diff(p, background) <= thresh + else: + fill = p not in (value, border) + if fill: + pixel[s, t] = value + new_edge.add((s, t)) + full_edge = edge # discard pixels processed + edge = new_edge + + +def _compute_regular_polygon_vertices( + bounding_circle: Sequence[Sequence[float] | float], n_sides: int, rotation: float +) -> list[tuple[float, float]]: + """ + Generate a list of vertices for a 2D regular polygon. + + :param bounding_circle: The bounding circle is a sequence defined + by a point and radius. The polygon is inscribed in this circle. + (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``) + :param n_sides: Number of sides + (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon) + :param rotation: Apply an arbitrary rotation to the polygon + (e.g. ``rotation=90``, applies a 90 degree rotation) + :return: List of regular polygon vertices + (e.g. ``[(25, 50), (50, 50), (50, 25), (25, 25)]``) + + How are the vertices computed? + 1. Compute the following variables + - theta: Angle between the apothem & the nearest polygon vertex + - side_length: Length of each polygon edge + - centroid: Center of bounding circle (1st, 2nd elements of bounding_circle) + - polygon_radius: Polygon radius (last element of bounding_circle) + - angles: Location of each polygon vertex in polar grid + (e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0]) + + 2. For each angle in angles, get the polygon vertex at that angle + The vertex is computed using the equation below. + X= xcos(φ) + ysin(φ) + Y= −xsin(φ) + ycos(φ) + + Note: + φ = angle in degrees + x = 0 + y = polygon_radius + + The formula above assumes rotation around the origin. + In our case, we are rotating around the centroid. + To account for this, we use the formula below + X = xcos(φ) + ysin(φ) + centroid_x + Y = −xsin(φ) + ycos(φ) + centroid_y + """ + # 1. Error Handling + # 1.1 Check `n_sides` has an appropriate value + if not isinstance(n_sides, int): + msg = "n_sides should be an int" # type: ignore[unreachable] + raise TypeError(msg) + if n_sides < 3: + msg = "n_sides should be an int > 2" + raise ValueError(msg) + + # 1.2 Check `bounding_circle` has an appropriate value + if not isinstance(bounding_circle, (list, tuple)): + msg = "bounding_circle should be a sequence" + raise TypeError(msg) + + if len(bounding_circle) == 3: + if not all(isinstance(i, (int, float)) for i in bounding_circle): + msg = "bounding_circle should only contain numeric data" + raise ValueError(msg) + + *centroid, polygon_radius = cast(list[float], list(bounding_circle)) + elif len(bounding_circle) == 2 and isinstance(bounding_circle[0], (list, tuple)): + if not all( + isinstance(i, (int, float)) for i in bounding_circle[0] + ) or not isinstance(bounding_circle[1], (int, float)): + msg = "bounding_circle should only contain numeric data" + raise ValueError(msg) + + if len(bounding_circle[0]) != 2: + msg = "bounding_circle centre should contain 2D coordinates (e.g. (x, y))" + raise ValueError(msg) + + centroid = cast(list[float], list(bounding_circle[0])) + polygon_radius = cast(float, bounding_circle[1]) + else: + msg = ( + "bounding_circle should contain 2D coordinates " + "and a radius (e.g. (x, y, r) or ((x, y), r) )" + ) + raise ValueError(msg) + + if polygon_radius <= 0: + msg = "bounding_circle radius should be > 0" + raise ValueError(msg) + + # 1.3 Check `rotation` has an appropriate value + if not isinstance(rotation, (int, float)): + msg = "rotation should be an int or float" # type: ignore[unreachable] + raise ValueError(msg) + + # 2. Define Helper Functions + def _apply_rotation(point: list[float], degrees: float) -> tuple[float, float]: + return ( + round( + point[0] * math.cos(math.radians(360 - degrees)) + - point[1] * math.sin(math.radians(360 - degrees)) + + centroid[0], + 2, + ), + round( + point[1] * math.cos(math.radians(360 - degrees)) + + point[0] * math.sin(math.radians(360 - degrees)) + + centroid[1], + 2, + ), + ) + + def _compute_polygon_vertex(angle: float) -> tuple[float, float]: + start_point = [polygon_radius, 0] + return _apply_rotation(start_point, angle) + + def _get_angles(n_sides: int, rotation: float) -> list[float]: + angles = [] + degrees = 360 / n_sides + # Start with the bottom left polygon vertex + current_angle = (270 - 0.5 * degrees) + rotation + for _ in range(n_sides): + angles.append(current_angle) + current_angle += degrees + if current_angle > 360: + current_angle -= 360 + return angles + + # 3. Variable Declarations + angles = _get_angles(n_sides, rotation) + + # 4. Compute Vertices + return [_compute_polygon_vertex(angle) for angle in angles] + + +def _color_diff( + color1: float | tuple[int, ...], color2: float | tuple[int, ...] +) -> float: + """ + Uses 1-norm distance to calculate difference between two values. + """ + first = color1 if isinstance(color1, tuple) else (color1,) + second = color2 if isinstance(color2, tuple) else (color2,) + + return sum(abs(first[i] - second[i]) for i in range(len(second))) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageDraw2.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageDraw2.py new file mode 100644 index 0000000000000000000000000000000000000000..3d68658ed5b79a36597e4953b888c41aa82fc7da --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageDraw2.py @@ -0,0 +1,243 @@ +# +# The Python Imaging Library +# $Id$ +# +# WCK-style drawing interface operations +# +# History: +# 2003-12-07 fl created +# 2005-05-15 fl updated; added to PIL as ImageDraw2 +# 2005-05-15 fl added text support +# 2005-05-20 fl added arc/chord/pieslice support +# +# Copyright (c) 2003-2005 by Secret Labs AB +# Copyright (c) 2003-2005 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# + + +""" +(Experimental) WCK-style drawing interface operations + +.. seealso:: :py:mod:`PIL.ImageDraw` +""" +from __future__ import annotations + +from typing import Any, AnyStr, BinaryIO + +from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath +from ._typing import Coords, StrOrBytesPath + + +class Pen: + """Stores an outline color and width.""" + + def __init__(self, color: str, width: int = 1, opacity: int = 255) -> None: + self.color = ImageColor.getrgb(color) + self.width = width + + +class Brush: + """Stores a fill color""" + + def __init__(self, color: str, opacity: int = 255) -> None: + self.color = ImageColor.getrgb(color) + + +class Font: + """Stores a TrueType font and color""" + + def __init__( + self, color: str, file: StrOrBytesPath | BinaryIO, size: float = 12 + ) -> None: + # FIXME: add support for bitmap fonts + self.color = ImageColor.getrgb(color) + self.font = ImageFont.truetype(file, size) + + +class Draw: + """ + (Experimental) WCK-style drawing interface + """ + + def __init__( + self, + image: Image.Image | str, + size: tuple[int, int] | list[int] | None = None, + color: float | tuple[float, ...] | str | None = None, + ) -> None: + if isinstance(image, str): + if size is None: + msg = "If image argument is mode string, size must be a list or tuple" + raise ValueError(msg) + image = Image.new(image, size, color) + self.draw = ImageDraw.Draw(image) + self.image = image + self.transform: tuple[float, float, float, float, float, float] | None = None + + def flush(self) -> Image.Image: + return self.image + + def render( + self, + op: str, + xy: Coords, + pen: Pen | Brush | None, + brush: Brush | Pen | None = None, + **kwargs: Any, + ) -> None: + # handle color arguments + outline = fill = None + width = 1 + if isinstance(pen, Pen): + outline = pen.color + width = pen.width + elif isinstance(brush, Pen): + outline = brush.color + width = brush.width + if isinstance(brush, Brush): + fill = brush.color + elif isinstance(pen, Brush): + fill = pen.color + # handle transformation + if self.transform: + path = ImagePath.Path(xy) + path.transform(self.transform) + xy = path + # render the item + if op in ("arc", "line"): + kwargs.setdefault("fill", outline) + else: + kwargs.setdefault("fill", fill) + kwargs.setdefault("outline", outline) + if op == "line": + kwargs.setdefault("width", width) + getattr(self.draw, op)(xy, **kwargs) + + def settransform(self, offset: tuple[float, float]) -> None: + """Sets a transformation offset.""" + (xoffset, yoffset) = offset + self.transform = (1, 0, xoffset, 0, 1, yoffset) + + def arc( + self, + xy: Coords, + pen: Pen | Brush | None, + start: float, + end: float, + *options: Any, + ) -> None: + """ + Draws an arc (a portion of a circle outline) between the start and end + angles, inside the given bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.arc` + """ + self.render("arc", xy, pen, *options, start=start, end=end) + + def chord( + self, + xy: Coords, + pen: Pen | Brush | None, + start: float, + end: float, + *options: Any, + ) -> None: + """ + Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points + with a straight line. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.chord` + """ + self.render("chord", xy, pen, *options, start=start, end=end) + + def ellipse(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None: + """ + Draws an ellipse inside the given bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse` + """ + self.render("ellipse", xy, pen, *options) + + def line(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None: + """ + Draws a line between the coordinates in the ``xy`` list. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line` + """ + self.render("line", xy, pen, *options) + + def pieslice( + self, + xy: Coords, + pen: Pen | Brush | None, + start: float, + end: float, + *options: Any, + ) -> None: + """ + Same as arc, but also draws straight lines between the end points and the + center of the bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.pieslice` + """ + self.render("pieslice", xy, pen, *options, start=start, end=end) + + def polygon(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None: + """ + Draws a polygon. + + The polygon outline consists of straight lines between the given + coordinates, plus a straight line between the last and the first + coordinate. + + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.polygon` + """ + self.render("polygon", xy, pen, *options) + + def rectangle(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None: + """ + Draws a rectangle. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.rectangle` + """ + self.render("rectangle", xy, pen, *options) + + def text(self, xy: tuple[float, float], text: AnyStr, font: Font) -> None: + """ + Draws the string at the given position. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text` + """ + if self.transform: + path = ImagePath.Path(xy) + path.transform(self.transform) + xy = path + self.draw.text(xy, text, font=font.font, fill=font.color) + + def textbbox( + self, xy: tuple[float, float], text: AnyStr, font: Font + ) -> tuple[float, float, float, float]: + """ + Returns bounding box (in pixels) of given text. + + :return: ``(left, top, right, bottom)`` bounding box + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox` + """ + if self.transform: + path = ImagePath.Path(xy) + path.transform(self.transform) + xy = path + return self.draw.textbbox(xy, text, font=font.font) + + def textlength(self, text: AnyStr, font: Font) -> float: + """ + Returns length (in pixels) of given text. + This is the amount by which following text should be offset. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textlength` + """ + return self.draw.textlength(text, font=font.font) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageEnhance.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageEnhance.py new file mode 100644 index 0000000000000000000000000000000000000000..0e7e6dd8ae631ad3577bda1d3e823bd2a3227536 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageEnhance.py @@ -0,0 +1,113 @@ +# +# The Python Imaging Library. +# $Id$ +# +# image enhancement classes +# +# For a background, see "Image Processing By Interpolation and +# Extrapolation", Paul Haeberli and Douglas Voorhies. Available +# at http://www.graficaobscura.com/interp/index.html +# +# History: +# 1996-03-23 fl Created +# 2009-06-16 fl Fixed mean calculation +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1996. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import Image, ImageFilter, ImageStat + + +class _Enhance: + image: Image.Image + degenerate: Image.Image + + def enhance(self, factor: float) -> Image.Image: + """ + Returns an enhanced image. + + :param factor: A floating point value controlling the enhancement. + Factor 1.0 always returns a copy of the original image, + lower factors mean less color (brightness, contrast, + etc), and higher values more. There are no restrictions + on this value. + :rtype: :py:class:`~PIL.Image.Image` + """ + return Image.blend(self.degenerate, self.image, factor) + + +class Color(_Enhance): + """Adjust image color balance. + + This class can be used to adjust the colour balance of an image, in + a manner similar to the controls on a colour TV set. An enhancement + factor of 0.0 gives a black and white image. A factor of 1.0 gives + the original image. + """ + + def __init__(self, image: Image.Image) -> None: + self.image = image + self.intermediate_mode = "L" + if "A" in image.getbands(): + self.intermediate_mode = "LA" + + if self.intermediate_mode != image.mode: + image = image.convert(self.intermediate_mode).convert(image.mode) + self.degenerate = image + + +class Contrast(_Enhance): + """Adjust image contrast. + + This class can be used to control the contrast of an image, similar + to the contrast control on a TV set. An enhancement factor of 0.0 + gives a solid gray image. A factor of 1.0 gives the original image. + """ + + def __init__(self, image: Image.Image) -> None: + self.image = image + if image.mode != "L": + image = image.convert("L") + mean = int(ImageStat.Stat(image).mean[0] + 0.5) + self.degenerate = Image.new("L", image.size, mean) + if self.degenerate.mode != self.image.mode: + self.degenerate = self.degenerate.convert(self.image.mode) + + if "A" in self.image.getbands(): + self.degenerate.putalpha(self.image.getchannel("A")) + + +class Brightness(_Enhance): + """Adjust image brightness. + + This class can be used to control the brightness of an image. An + enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the + original image. + """ + + def __init__(self, image: Image.Image) -> None: + self.image = image + self.degenerate = Image.new(image.mode, image.size, 0) + + if "A" in image.getbands(): + self.degenerate.putalpha(image.getchannel("A")) + + +class Sharpness(_Enhance): + """Adjust image sharpness. + + This class can be used to adjust the sharpness of an image. An + enhancement factor of 0.0 gives a blurred image, a factor of 1.0 gives the + original image, and a factor of 2.0 gives a sharpened image. + """ + + def __init__(self, image: Image.Image) -> None: + self.image = image + self.degenerate = image.filter(ImageFilter.SMOOTH) + + if "A" in image.getbands(): + self.degenerate.putalpha(image.getchannel("A")) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageFile.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageFile.py new file mode 100644 index 0000000000000000000000000000000000000000..3390dfa97dd176891379c4cf0653ea45b62ab1c0 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageFile.py @@ -0,0 +1,938 @@ +# +# The Python Imaging Library. +# $Id$ +# +# base class for image file handlers +# +# history: +# 1995-09-09 fl Created +# 1996-03-11 fl Fixed load mechanism. +# 1996-04-15 fl Added pcx/xbm decoders. +# 1996-04-30 fl Added encoders. +# 1996-12-14 fl Added load helpers +# 1997-01-11 fl Use encode_to_file where possible +# 1997-08-27 fl Flush output in _save +# 1998-03-05 fl Use memory mapping for some modes +# 1999-02-04 fl Use memory mapping also for "I;16" and "I;16B" +# 1999-05-31 fl Added image parser +# 2000-10-12 fl Set readonly flag on memory-mapped images +# 2002-03-20 fl Use better messages for common decoder errors +# 2003-04-21 fl Fall back on mmap/map_buffer if map is not available +# 2003-10-30 fl Added StubImageFile class +# 2004-02-25 fl Made incremental parser more robust +# +# Copyright (c) 1997-2004 by Secret Labs AB +# Copyright (c) 1995-2004 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import abc +import io +import itertools +import logging +import os +import struct +from typing import IO, Any, NamedTuple, cast + +from . import ExifTags, Image +from ._util import DeferredError, is_path + +TYPE_CHECKING = False +if TYPE_CHECKING: + from ._typing import StrOrBytesPath + +logger = logging.getLogger(__name__) + +MAXBLOCK = 65536 +""" +By default, Pillow processes image data in blocks. This helps to prevent excessive use +of resources. Codecs may disable this behaviour with ``_pulls_fd`` or ``_pushes_fd``. + +When reading an image, this is the number of bytes to read at once. + +When writing an image, this is the number of bytes to write at once. +If the image width times 4 is greater, then that will be used instead. +Plugins may also set a greater number. + +User code may set this to another number. +""" + +SAFEBLOCK = 1024 * 1024 + +LOAD_TRUNCATED_IMAGES = False +"""Whether or not to load truncated image files. User code may change this.""" + +ERRORS = { + -1: "image buffer overrun error", + -2: "decoding error", + -3: "unknown error", + -8: "bad configuration", + -9: "out of memory error", +} +""" +Dict of known error codes returned from :meth:`.PyDecoder.decode`, +:meth:`.PyEncoder.encode` :meth:`.PyEncoder.encode_to_pyfd` and +:meth:`.PyEncoder.encode_to_file`. +""" + + +# +# -------------------------------------------------------------------- +# Helpers + + +def _get_oserror(error: int, *, encoder: bool) -> OSError: + try: + msg = Image.core.getcodecstatus(error) + except AttributeError: + msg = ERRORS.get(error) + if not msg: + msg = f"{'encoder' if encoder else 'decoder'} error {error}" + msg += f" when {'writing' if encoder else 'reading'} image file" + return OSError(msg) + + +def _tilesort(t: _Tile) -> int: + # sort on offset + return t[2] + + +class _Tile(NamedTuple): + codec_name: str + extents: tuple[int, int, int, int] | None + offset: int = 0 + args: tuple[Any, ...] | str | None = None + + +# +# -------------------------------------------------------------------- +# ImageFile base class + + +class ImageFile(Image.Image): + """Base class for image file format handlers.""" + + def __init__( + self, fp: StrOrBytesPath | IO[bytes], filename: str | bytes | None = None + ) -> None: + super().__init__() + + self._min_frame = 0 + + self.custom_mimetype: str | None = None + + self.tile: list[_Tile] = [] + """ A list of tile descriptors """ + + self.readonly = 1 # until we know better + + self.decoderconfig: tuple[Any, ...] = () + self.decodermaxblock = MAXBLOCK + + self.fp: IO[bytes] | None + self._fp: IO[bytes] | DeferredError + if is_path(fp): + # filename + self.fp = open(fp, "rb") + self.filename = os.fspath(fp) + self._exclusive_fp = True + else: + # stream + self.fp = cast(IO[bytes], fp) + self.filename = filename if filename is not None else "" + # can be overridden + self._exclusive_fp = False + + try: + try: + self._open() + except ( + IndexError, # end of data + TypeError, # end of data (ord) + KeyError, # unsupported mode + EOFError, # got header but not the first frame + struct.error, + ) as v: + raise SyntaxError(v) from v + + if not self.mode or self.size[0] <= 0 or self.size[1] <= 0: + msg = "not identified by this driver" + raise SyntaxError(msg) + except BaseException: + # close the file only if we have opened it this constructor + if self._exclusive_fp: + self.fp.close() + raise + + def _open(self) -> None: + pass + + # Context manager support + def __enter__(self) -> ImageFile: + return self + + def _close_fp(self) -> None: + if getattr(self, "_fp", False) and not isinstance(self._fp, DeferredError): + if self._fp != self.fp: + self._fp.close() + self._fp = DeferredError(ValueError("Operation on closed image")) + if self.fp: + self.fp.close() + + def __exit__(self, *args: object) -> None: + if getattr(self, "_exclusive_fp", False): + self._close_fp() + self.fp = None + + def close(self) -> None: + """ + Closes the file pointer, if possible. + + This operation will destroy the image core and release its memory. + The image data will be unusable afterward. + + This function is required to close images that have multiple frames or + have not had their file read and closed by the + :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for + more information. + """ + try: + self._close_fp() + self.fp = None + except Exception as msg: + logger.debug("Error closing: %s", msg) + + super().close() + + def get_child_images(self) -> list[ImageFile]: + child_images = [] + exif = self.getexif() + ifds = [] + if ExifTags.Base.SubIFDs in exif: + subifd_offsets = exif[ExifTags.Base.SubIFDs] + if subifd_offsets: + if not isinstance(subifd_offsets, tuple): + subifd_offsets = (subifd_offsets,) + for subifd_offset in subifd_offsets: + ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset)) + ifd1 = exif.get_ifd(ExifTags.IFD.IFD1) + if ifd1 and ifd1.get(ExifTags.Base.JpegIFOffset): + assert exif._info is not None + ifds.append((ifd1, exif._info.next)) + + offset = None + for ifd, ifd_offset in ifds: + assert self.fp is not None + current_offset = self.fp.tell() + if offset is None: + offset = current_offset + + fp = self.fp + if ifd is not None: + thumbnail_offset = ifd.get(ExifTags.Base.JpegIFOffset) + if thumbnail_offset is not None: + thumbnail_offset += getattr(self, "_exif_offset", 0) + self.fp.seek(thumbnail_offset) + + length = ifd.get(ExifTags.Base.JpegIFByteCount) + assert isinstance(length, int) + data = self.fp.read(length) + fp = io.BytesIO(data) + + with Image.open(fp) as im: + from . import TiffImagePlugin + + if thumbnail_offset is None and isinstance( + im, TiffImagePlugin.TiffImageFile + ): + im._frame_pos = [ifd_offset] + im._seek(0) + im.load() + child_images.append(im) + + if offset is not None: + assert self.fp is not None + self.fp.seek(offset) + return child_images + + def get_format_mimetype(self) -> str | None: + if self.custom_mimetype: + return self.custom_mimetype + if self.format is not None: + return Image.MIME.get(self.format.upper()) + return None + + def __getstate__(self) -> list[Any]: + return super().__getstate__() + [self.filename] + + def __setstate__(self, state: list[Any]) -> None: + self.tile = [] + if len(state) > 5: + self.filename = state[5] + super().__setstate__(state) + + def verify(self) -> None: + """Check file integrity""" + + # raise exception if something's wrong. must be called + # directly after open, and closes file when finished. + if self._exclusive_fp and self.fp: + self.fp.close() + self.fp = None + + def load(self) -> Image.core.PixelAccess | None: + """Load image data based on tile list""" + + if not self.tile and self._im is None: + msg = "cannot load this image" + raise OSError(msg) + + pixel = Image.Image.load(self) + if not self.tile: + return pixel + + self.map: mmap.mmap | None = None + use_mmap = self.filename and len(self.tile) == 1 + + assert self.fp is not None + readonly = 0 + + # look for read/seek overrides + if hasattr(self, "load_read"): + read = self.load_read + # don't use mmap if there are custom read/seek functions + use_mmap = False + else: + read = self.fp.read + + if hasattr(self, "load_seek"): + seek = self.load_seek + use_mmap = False + else: + seek = self.fp.seek + + if use_mmap: + # try memory mapping + decoder_name, extents, offset, args = self.tile[0] + if isinstance(args, str): + args = (args, 0, 1) + if ( + decoder_name == "raw" + and isinstance(args, tuple) + and len(args) >= 3 + and args[0] == self.mode + and args[0] in Image._MAPMODES + ): + if offset < 0: + msg = "Tile offset cannot be negative" + raise ValueError(msg) + try: + # use mmap, if possible + import mmap + + with open(self.filename) as fp: + self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) + if offset + self.size[1] * args[1] > self.map.size(): + msg = "buffer is not large enough" + raise OSError(msg) + self.im = Image.core.map_buffer( + self.map, self.size, decoder_name, offset, args + ) + readonly = 1 + # After trashing self.im, + # we might need to reload the palette data. + if self.palette: + self.palette.dirty = 1 + except (AttributeError, OSError, ImportError): + self.map = None + + self.load_prepare() + err_code = -3 # initialize to unknown error + if not self.map: + # sort tiles in file order + self.tile.sort(key=_tilesort) + + # FIXME: This is a hack to handle TIFF's JpegTables tag. + prefix = getattr(self, "tile_prefix", b"") + + # Remove consecutive duplicates that only differ by their offset + self.tile = [ + list(tiles)[-1] + for _, tiles in itertools.groupby( + self.tile, lambda tile: (tile[0], tile[1], tile[3]) + ) + ] + for i, (decoder_name, extents, offset, args) in enumerate(self.tile): + seek(offset) + decoder = Image._getdecoder( + self.mode, decoder_name, args, self.decoderconfig + ) + try: + decoder.setimage(self.im, extents) + if decoder.pulls_fd: + decoder.setfd(self.fp) + err_code = decoder.decode(b"")[1] + else: + b = prefix + while True: + read_bytes = self.decodermaxblock + if i + 1 < len(self.tile): + next_offset = self.tile[i + 1].offset + if next_offset > offset: + read_bytes = next_offset - offset + try: + s = read(read_bytes) + except (IndexError, struct.error) as e: + # truncated png/gif + if LOAD_TRUNCATED_IMAGES: + break + else: + msg = "image file is truncated" + raise OSError(msg) from e + + if not s: # truncated jpeg + if LOAD_TRUNCATED_IMAGES: + break + else: + msg = ( + "image file is truncated " + f"({len(b)} bytes not processed)" + ) + raise OSError(msg) + + b = b + s + n, err_code = decoder.decode(b) + if n < 0: + break + b = b[n:] + finally: + # Need to cleanup here to prevent leaks + decoder.cleanup() + + self.tile = [] + self.readonly = readonly + + self.load_end() + + if self._exclusive_fp and self._close_exclusive_fp_after_loading: + self.fp.close() + self.fp = None + + if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0: + # still raised if decoder fails to return anything + raise _get_oserror(err_code, encoder=False) + + return Image.Image.load(self) + + def load_prepare(self) -> None: + # create image memory if necessary + if self._im is None: + self.im = Image.core.new(self.mode, self.size) + # create palette (optional) + if self.mode == "P": + Image.Image.load(self) + + def load_end(self) -> None: + # may be overridden + pass + + # may be defined for contained formats + # def load_seek(self, pos: int) -> None: + # pass + + # may be defined for blocked formats (e.g. PNG) + # def load_read(self, read_bytes: int) -> bytes: + # pass + + def _seek_check(self, frame: int) -> bool: + if ( + frame < self._min_frame + # Only check upper limit on frames if additional seek operations + # are not required to do so + or ( + not (hasattr(self, "_n_frames") and self._n_frames is None) + and frame >= getattr(self, "n_frames") + self._min_frame + ) + ): + msg = "attempt to seek outside sequence" + raise EOFError(msg) + + return self.tell() != frame + + +class StubHandler(abc.ABC): + def open(self, im: StubImageFile) -> None: + pass + + @abc.abstractmethod + def load(self, im: StubImageFile) -> Image.Image: + pass + + +class StubImageFile(ImageFile, metaclass=abc.ABCMeta): + """ + Base class for stub image loaders. + + A stub loader is an image loader that can identify files of a + certain format, but relies on external code to load the file. + """ + + @abc.abstractmethod + def _open(self) -> None: + pass + + def load(self) -> Image.core.PixelAccess | None: + loader = self._load() + if loader is None: + msg = f"cannot find loader for this {self.format} file" + raise OSError(msg) + image = loader.load(self) + assert image is not None + # become the other object (!) + self.__class__ = image.__class__ # type: ignore[assignment] + self.__dict__ = image.__dict__ + return image.load() + + @abc.abstractmethod + def _load(self) -> StubHandler | None: + """(Hook) Find actual image loader.""" + pass + + +class Parser: + """ + Incremental image parser. This class implements the standard + feed/close consumer interface. + """ + + incremental = None + image: Image.Image | None = None + data: bytes | None = None + decoder: Image.core.ImagingDecoder | PyDecoder | None = None + offset = 0 + finished = 0 + + def reset(self) -> None: + """ + (Consumer) Reset the parser. Note that you can only call this + method immediately after you've created a parser; parser + instances cannot be reused. + """ + assert self.data is None, "cannot reuse parsers" + + def feed(self, data: bytes) -> None: + """ + (Consumer) Feed data to the parser. + + :param data: A string buffer. + :exception OSError: If the parser failed to parse the image file. + """ + # collect data + + if self.finished: + return + + if self.data is None: + self.data = data + else: + self.data = self.data + data + + # parse what we have + if self.decoder: + if self.offset > 0: + # skip header + skip = min(len(self.data), self.offset) + self.data = self.data[skip:] + self.offset = self.offset - skip + if self.offset > 0 or not self.data: + return + + n, e = self.decoder.decode(self.data) + + if n < 0: + # end of stream + self.data = None + self.finished = 1 + if e < 0: + # decoding error + self.image = None + raise _get_oserror(e, encoder=False) + else: + # end of image + return + self.data = self.data[n:] + + elif self.image: + # if we end up here with no decoder, this file cannot + # be incrementally parsed. wait until we've gotten all + # available data + pass + + else: + # attempt to open this file + try: + with io.BytesIO(self.data) as fp: + im = Image.open(fp) + except OSError: + pass # not enough data + else: + flag = hasattr(im, "load_seek") or hasattr(im, "load_read") + if flag or len(im.tile) != 1: + # custom load code, or multiple tiles + self.decode = None + else: + # initialize decoder + im.load_prepare() + d, e, o, a = im.tile[0] + im.tile = [] + self.decoder = Image._getdecoder(im.mode, d, a, im.decoderconfig) + self.decoder.setimage(im.im, e) + + # calculate decoder offset + self.offset = o + if self.offset <= len(self.data): + self.data = self.data[self.offset :] + self.offset = 0 + + self.image = im + + def __enter__(self) -> Parser: + return self + + def __exit__(self, *args: object) -> None: + self.close() + + def close(self) -> Image.Image: + """ + (Consumer) Close the stream. + + :returns: An image object. + :exception OSError: If the parser failed to parse the image file either + because it cannot be identified or cannot be + decoded. + """ + # finish decoding + if self.decoder: + # get rid of what's left in the buffers + self.feed(b"") + self.data = self.decoder = None + if not self.finished: + msg = "image was incomplete" + raise OSError(msg) + if not self.image: + msg = "cannot parse this image" + raise OSError(msg) + if self.data: + # incremental parsing not possible; reopen the file + # not that we have all data + with io.BytesIO(self.data) as fp: + try: + self.image = Image.open(fp) + finally: + self.image.load() + return self.image + + +# -------------------------------------------------------------------- + + +def _save(im: Image.Image, fp: IO[bytes], tile: list[_Tile], bufsize: int = 0) -> None: + """Helper to save image based on tile list + + :param im: Image object. + :param fp: File object. + :param tile: Tile list. + :param bufsize: Optional buffer size + """ + + im.load() + if not hasattr(im, "encoderconfig"): + im.encoderconfig = () + tile.sort(key=_tilesort) + # FIXME: make MAXBLOCK a configuration parameter + # It would be great if we could have the encoder specify what it needs + # But, it would need at least the image size in most cases. RawEncode is + # a tricky case. + bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c + try: + fh = fp.fileno() + fp.flush() + _encode_tile(im, fp, tile, bufsize, fh) + except (AttributeError, io.UnsupportedOperation) as exc: + _encode_tile(im, fp, tile, bufsize, None, exc) + if hasattr(fp, "flush"): + fp.flush() + + +def _encode_tile( + im: Image.Image, + fp: IO[bytes], + tile: list[_Tile], + bufsize: int, + fh: int | None, + exc: BaseException | None = None, +) -> None: + for encoder_name, extents, offset, args in tile: + if offset > 0: + fp.seek(offset) + encoder = Image._getencoder(im.mode, encoder_name, args, im.encoderconfig) + try: + encoder.setimage(im.im, extents) + if encoder.pushes_fd: + encoder.setfd(fp) + errcode = encoder.encode_to_pyfd()[1] + else: + if exc: + # compress to Python file-compatible object + while True: + errcode, data = encoder.encode(bufsize)[1:] + fp.write(data) + if errcode: + break + else: + # slight speedup: compress to real file object + assert fh is not None + errcode = encoder.encode_to_file(fh, bufsize) + if errcode < 0: + raise _get_oserror(errcode, encoder=True) from exc + finally: + encoder.cleanup() + + +def _safe_read(fp: IO[bytes], size: int) -> bytes: + """ + Reads large blocks in a safe way. Unlike fp.read(n), this function + doesn't trust the user. If the requested size is larger than + SAFEBLOCK, the file is read block by block. + + :param fp: File handle. Must implement a read method. + :param size: Number of bytes to read. + :returns: A string containing size bytes of data. + + Raises an OSError if the file is truncated and the read cannot be completed + + """ + if size <= 0: + return b"" + if size <= SAFEBLOCK: + data = fp.read(size) + if len(data) < size: + msg = "Truncated File Read" + raise OSError(msg) + return data + blocks: list[bytes] = [] + remaining_size = size + while remaining_size > 0: + block = fp.read(min(remaining_size, SAFEBLOCK)) + if not block: + break + blocks.append(block) + remaining_size -= len(block) + if sum(len(block) for block in blocks) < size: + msg = "Truncated File Read" + raise OSError(msg) + return b"".join(blocks) + + +class PyCodecState: + def __init__(self) -> None: + self.xsize = 0 + self.ysize = 0 + self.xoff = 0 + self.yoff = 0 + + def extents(self) -> tuple[int, int, int, int]: + return self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize + + +class PyCodec: + fd: IO[bytes] | None + + def __init__(self, mode: str, *args: Any) -> None: + self.im: Image.core.ImagingCore | None = None + self.state = PyCodecState() + self.fd = None + self.mode = mode + self.init(args) + + def init(self, args: tuple[Any, ...]) -> None: + """ + Override to perform codec specific initialization + + :param args: Tuple of arg items from the tile entry + :returns: None + """ + self.args = args + + def cleanup(self) -> None: + """ + Override to perform codec specific cleanup + + :returns: None + """ + pass + + def setfd(self, fd: IO[bytes]) -> None: + """ + Called from ImageFile to set the Python file-like object + + :param fd: A Python file-like object + :returns: None + """ + self.fd = fd + + def setimage( + self, + im: Image.core.ImagingCore, + extents: tuple[int, int, int, int] | None = None, + ) -> None: + """ + Called from ImageFile to set the core output image for the codec + + :param im: A core image object + :param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle + for this tile + :returns: None + """ + + # following c code + self.im = im + + if extents: + (x0, y0, x1, y1) = extents + else: + (x0, y0, x1, y1) = (0, 0, 0, 0) + + if x0 == 0 and x1 == 0: + self.state.xsize, self.state.ysize = self.im.size + else: + self.state.xoff = x0 + self.state.yoff = y0 + self.state.xsize = x1 - x0 + self.state.ysize = y1 - y0 + + if self.state.xsize <= 0 or self.state.ysize <= 0: + msg = "Size cannot be negative" + raise ValueError(msg) + + if ( + self.state.xsize + self.state.xoff > self.im.size[0] + or self.state.ysize + self.state.yoff > self.im.size[1] + ): + msg = "Tile cannot extend outside image" + raise ValueError(msg) + + +class PyDecoder(PyCodec): + """ + Python implementation of a format decoder. Override this class and + add the decoding logic in the :meth:`decode` method. + + See :ref:`Writing Your Own File Codec in Python` + """ + + _pulls_fd = False + + @property + def pulls_fd(self) -> bool: + return self._pulls_fd + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + """ + Override to perform the decoding process. + + :param buffer: A bytes object with the data to be decoded. + :returns: A tuple of ``(bytes consumed, errcode)``. + If finished with decoding return -1 for the bytes consumed. + Err codes are from :data:`.ImageFile.ERRORS`. + """ + msg = "unavailable in base decoder" + raise NotImplementedError(msg) + + def set_as_raw( + self, data: bytes, rawmode: str | None = None, extra: tuple[Any, ...] = () + ) -> None: + """ + Convenience method to set the internal image from a stream of raw data + + :param data: Bytes to be set + :param rawmode: The rawmode to be used for the decoder. + If not specified, it will default to the mode of the image + :param extra: Extra arguments for the decoder. + :returns: None + """ + + if not rawmode: + rawmode = self.mode + d = Image._getdecoder(self.mode, "raw", rawmode, extra) + assert self.im is not None + d.setimage(self.im, self.state.extents()) + s = d.decode(data) + + if s[0] >= 0: + msg = "not enough image data" + raise ValueError(msg) + if s[1] != 0: + msg = "cannot decode image data" + raise ValueError(msg) + + +class PyEncoder(PyCodec): + """ + Python implementation of a format encoder. Override this class and + add the decoding logic in the :meth:`encode` method. + + See :ref:`Writing Your Own File Codec in Python` + """ + + _pushes_fd = False + + @property + def pushes_fd(self) -> bool: + return self._pushes_fd + + def encode(self, bufsize: int) -> tuple[int, int, bytes]: + """ + Override to perform the encoding process. + + :param bufsize: Buffer size. + :returns: A tuple of ``(bytes encoded, errcode, bytes)``. + If finished with encoding return 1 for the error code. + Err codes are from :data:`.ImageFile.ERRORS`. + """ + msg = "unavailable in base encoder" + raise NotImplementedError(msg) + + def encode_to_pyfd(self) -> tuple[int, int]: + """ + If ``pushes_fd`` is ``True``, then this method will be used, + and ``encode()`` will only be called once. + + :returns: A tuple of ``(bytes consumed, errcode)``. + Err codes are from :data:`.ImageFile.ERRORS`. + """ + if not self.pushes_fd: + return 0, -8 # bad configuration + bytes_consumed, errcode, data = self.encode(0) + if data: + assert self.fd is not None + self.fd.write(data) + return bytes_consumed, errcode + + def encode_to_file(self, fh: int, bufsize: int) -> int: + """ + :param fh: File handle. + :param bufsize: Buffer size. + + :returns: If finished successfully, return 0. + Otherwise, return an error code. Err codes are from + :data:`.ImageFile.ERRORS`. + """ + errcode = 0 + while errcode == 0: + status, errcode, buf = self.encode(bufsize) + if status > 0: + os.write(fh, buf[status:]) + return errcode diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageFilter.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageFilter.py new file mode 100644 index 0000000000000000000000000000000000000000..9326eeeda9de4d479ae4c8c056d19caec12a576f --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageFilter.py @@ -0,0 +1,607 @@ +# +# The Python Imaging Library. +# $Id$ +# +# standard filters +# +# History: +# 1995-11-27 fl Created +# 2002-06-08 fl Added rank and mode filters +# 2003-09-15 fl Fixed rank calculation in rank filter; added expand call +# +# Copyright (c) 1997-2003 by Secret Labs AB. +# Copyright (c) 1995-2002 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import abc +import functools +from collections.abc import Sequence +from typing import cast + +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + from types import ModuleType + from typing import Any + + from . import _imaging + from ._typing import NumpyArray + + +class Filter(abc.ABC): + @abc.abstractmethod + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + pass + + +class MultibandFilter(Filter): + pass + + +class BuiltinFilter(MultibandFilter): + filterargs: tuple[Any, ...] + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + if image.mode == "P": + msg = "cannot filter palette images" + raise ValueError(msg) + return image.filter(*self.filterargs) + + +class Kernel(BuiltinFilter): + """ + Create a convolution kernel. This only supports 3x3 and 5x5 integer and floating + point kernels. + + Kernels can only be applied to "L" and "RGB" images. + + :param size: Kernel size, given as (width, height). This must be (3,3) or (5,5). + :param kernel: A sequence containing kernel weights. The kernel will be flipped + vertically before being applied to the image. + :param scale: Scale factor. If given, the result for each pixel is divided by this + value. The default is the sum of the kernel weights. + :param offset: Offset. If given, this value is added to the result, after it has + been divided by the scale factor. + """ + + name = "Kernel" + + def __init__( + self, + size: tuple[int, int], + kernel: Sequence[float], + scale: float | None = None, + offset: float = 0, + ) -> None: + if scale is None: + # default scale is sum of kernel + scale = functools.reduce(lambda a, b: a + b, kernel) + if size[0] * size[1] != len(kernel): + msg = "not enough coefficients in kernel" + raise ValueError(msg) + self.filterargs = size, scale, offset, kernel + + +class RankFilter(Filter): + """ + Create a rank filter. The rank filter sorts all pixels in + a window of the given size, and returns the ``rank``'th value. + + :param size: The kernel size, in pixels. + :param rank: What pixel value to pick. Use 0 for a min filter, + ``size * size / 2`` for a median filter, ``size * size - 1`` + for a max filter, etc. + """ + + name = "Rank" + + def __init__(self, size: int, rank: int) -> None: + self.size = size + self.rank = rank + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + if image.mode == "P": + msg = "cannot filter palette images" + raise ValueError(msg) + image = image.expand(self.size // 2, self.size // 2) + return image.rankfilter(self.size, self.rank) + + +class MedianFilter(RankFilter): + """ + Create a median filter. Picks the median pixel value in a window with the + given size. + + :param size: The kernel size, in pixels. + """ + + name = "Median" + + def __init__(self, size: int = 3) -> None: + self.size = size + self.rank = size * size // 2 + + +class MinFilter(RankFilter): + """ + Create a min filter. Picks the lowest pixel value in a window with the + given size. + + :param size: The kernel size, in pixels. + """ + + name = "Min" + + def __init__(self, size: int = 3) -> None: + self.size = size + self.rank = 0 + + +class MaxFilter(RankFilter): + """ + Create a max filter. Picks the largest pixel value in a window with the + given size. + + :param size: The kernel size, in pixels. + """ + + name = "Max" + + def __init__(self, size: int = 3) -> None: + self.size = size + self.rank = size * size - 1 + + +class ModeFilter(Filter): + """ + Create a mode filter. Picks the most frequent pixel value in a box with the + given size. Pixel values that occur only once or twice are ignored; if no + pixel value occurs more than twice, the original pixel value is preserved. + + :param size: The kernel size, in pixels. + """ + + name = "Mode" + + def __init__(self, size: int = 3) -> None: + self.size = size + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + return image.modefilter(self.size) + + +class GaussianBlur(MultibandFilter): + """Blurs the image with a sequence of extended box filters, which + approximates a Gaussian kernel. For details on accuracy see + + + :param radius: Standard deviation of the Gaussian kernel. Either a sequence of two + numbers for x and y, or a single number for both. + """ + + name = "GaussianBlur" + + def __init__(self, radius: float | Sequence[float] = 2) -> None: + self.radius = radius + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + xy = self.radius + if isinstance(xy, (int, float)): + xy = (xy, xy) + if xy == (0, 0): + return image.copy() + return image.gaussian_blur(xy) + + +class BoxBlur(MultibandFilter): + """Blurs the image by setting each pixel to the average value of the pixels + in a square box extending radius pixels in each direction. + Supports float radius of arbitrary size. Uses an optimized implementation + which runs in linear time relative to the size of the image + for any radius value. + + :param radius: Size of the box in a direction. Either a sequence of two numbers for + x and y, or a single number for both. + + Radius 0 does not blur, returns an identical image. + Radius 1 takes 1 pixel in each direction, i.e. 9 pixels in total. + """ + + name = "BoxBlur" + + def __init__(self, radius: float | Sequence[float]) -> None: + xy = radius if isinstance(radius, (tuple, list)) else (radius, radius) + if xy[0] < 0 or xy[1] < 0: + msg = "radius must be >= 0" + raise ValueError(msg) + self.radius = radius + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + xy = self.radius + if isinstance(xy, (int, float)): + xy = (xy, xy) + if xy == (0, 0): + return image.copy() + return image.box_blur(xy) + + +class UnsharpMask(MultibandFilter): + """Unsharp mask filter. + + See Wikipedia's entry on `digital unsharp masking`_ for an explanation of + the parameters. + + :param radius: Blur Radius + :param percent: Unsharp strength, in percent + :param threshold: Threshold controls the minimum brightness change that + will be sharpened + + .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking + + """ + + name = "UnsharpMask" + + def __init__( + self, radius: float = 2, percent: int = 150, threshold: int = 3 + ) -> None: + self.radius = radius + self.percent = percent + self.threshold = threshold + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + return image.unsharp_mask(self.radius, self.percent, self.threshold) + + +class BLUR(BuiltinFilter): + name = "Blur" + # fmt: off + filterargs = (5, 5), 16, 0, ( + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 1, + ) + # fmt: on + + +class CONTOUR(BuiltinFilter): + name = "Contour" + # fmt: off + filterargs = (3, 3), 1, 255, ( + -1, -1, -1, + -1, 8, -1, + -1, -1, -1, + ) + # fmt: on + + +class DETAIL(BuiltinFilter): + name = "Detail" + # fmt: off + filterargs = (3, 3), 6, 0, ( + 0, -1, 0, + -1, 10, -1, + 0, -1, 0, + ) + # fmt: on + + +class EDGE_ENHANCE(BuiltinFilter): + name = "Edge-enhance" + # fmt: off + filterargs = (3, 3), 2, 0, ( + -1, -1, -1, + -1, 10, -1, + -1, -1, -1, + ) + # fmt: on + + +class EDGE_ENHANCE_MORE(BuiltinFilter): + name = "Edge-enhance More" + # fmt: off + filterargs = (3, 3), 1, 0, ( + -1, -1, -1, + -1, 9, -1, + -1, -1, -1, + ) + # fmt: on + + +class EMBOSS(BuiltinFilter): + name = "Emboss" + # fmt: off + filterargs = (3, 3), 1, 128, ( + -1, 0, 0, + 0, 1, 0, + 0, 0, 0, + ) + # fmt: on + + +class FIND_EDGES(BuiltinFilter): + name = "Find Edges" + # fmt: off + filterargs = (3, 3), 1, 0, ( + -1, -1, -1, + -1, 8, -1, + -1, -1, -1, + ) + # fmt: on + + +class SHARPEN(BuiltinFilter): + name = "Sharpen" + # fmt: off + filterargs = (3, 3), 16, 0, ( + -2, -2, -2, + -2, 32, -2, + -2, -2, -2, + ) + # fmt: on + + +class SMOOTH(BuiltinFilter): + name = "Smooth" + # fmt: off + filterargs = (3, 3), 13, 0, ( + 1, 1, 1, + 1, 5, 1, + 1, 1, 1, + ) + # fmt: on + + +class SMOOTH_MORE(BuiltinFilter): + name = "Smooth More" + # fmt: off + filterargs = (5, 5), 100, 0, ( + 1, 1, 1, 1, 1, + 1, 5, 5, 5, 1, + 1, 5, 44, 5, 1, + 1, 5, 5, 5, 1, + 1, 1, 1, 1, 1, + ) + # fmt: on + + +class Color3DLUT(MultibandFilter): + """Three-dimensional color lookup table. + + Transforms 3-channel pixels using the values of the channels as coordinates + in the 3D lookup table and interpolating the nearest elements. + + This method allows you to apply almost any color transformation + in constant time by using pre-calculated decimated tables. + + .. versionadded:: 5.2.0 + + :param size: Size of the table. One int or tuple of (int, int, int). + Minimal size in any dimension is 2, maximum is 65. + :param table: Flat lookup table. A list of ``channels * size**3`` + float elements or a list of ``size**3`` channels-sized + tuples with floats. Channels are changed first, + then first dimension, then second, then third. + Value 0.0 corresponds lowest value of output, 1.0 highest. + :param channels: Number of channels in the table. Could be 3 or 4. + Default is 3. + :param target_mode: A mode for the result image. Should have not less + than ``channels`` channels. Default is ``None``, + which means that mode wouldn't be changed. + """ + + name = "Color 3D LUT" + + def __init__( + self, + size: int | tuple[int, int, int], + table: Sequence[float] | Sequence[Sequence[int]] | NumpyArray, + channels: int = 3, + target_mode: str | None = None, + **kwargs: bool, + ) -> None: + if channels not in (3, 4): + msg = "Only 3 or 4 output channels are supported" + raise ValueError(msg) + self.size = size = self._check_size(size) + self.channels = channels + self.mode = target_mode + + # Hidden flag `_copy_table=False` could be used to avoid extra copying + # of the table if the table is specially made for the constructor. + copy_table = kwargs.get("_copy_table", True) + items = size[0] * size[1] * size[2] + wrong_size = False + + numpy: ModuleType | None = None + if hasattr(table, "shape"): + try: + import numpy + except ImportError: + pass + + if numpy and isinstance(table, numpy.ndarray): + numpy_table: NumpyArray = table + if copy_table: + numpy_table = numpy_table.copy() + + if numpy_table.shape in [ + (items * channels,), + (items, channels), + (size[2], size[1], size[0], channels), + ]: + table = numpy_table.reshape(items * channels) + else: + wrong_size = True + + else: + if copy_table: + table = list(table) + + # Convert to a flat list + if table and isinstance(table[0], (list, tuple)): + raw_table = cast(Sequence[Sequence[int]], table) + flat_table: list[int] = [] + for pixel in raw_table: + if len(pixel) != channels: + msg = ( + "The elements of the table should " + f"have a length of {channels}." + ) + raise ValueError(msg) + flat_table.extend(pixel) + table = flat_table + + if wrong_size or len(table) != items * channels: + msg = ( + "The table should have either channels * size**3 float items " + "or size**3 items of channels-sized tuples with floats. " + f"Table should be: {channels}x{size[0]}x{size[1]}x{size[2]}. " + f"Actual length: {len(table)}" + ) + raise ValueError(msg) + self.table = table + + @staticmethod + def _check_size(size: Any) -> tuple[int, int, int]: + try: + _, _, _ = size + except ValueError as e: + msg = "Size should be either an integer or a tuple of three integers." + raise ValueError(msg) from e + except TypeError: + size = (size, size, size) + size = tuple(int(x) for x in size) + for size_1d in size: + if not 2 <= size_1d <= 65: + msg = "Size should be in [2, 65] range." + raise ValueError(msg) + return size + + @classmethod + def generate( + cls, + size: int | tuple[int, int, int], + callback: Callable[[float, float, float], tuple[float, ...]], + channels: int = 3, + target_mode: str | None = None, + ) -> Color3DLUT: + """Generates new LUT using provided callback. + + :param size: Size of the table. Passed to the constructor. + :param callback: Function with three parameters which correspond + three color channels. Will be called ``size**3`` + times with values from 0.0 to 1.0 and should return + a tuple with ``channels`` elements. + :param channels: The number of channels which should return callback. + :param target_mode: Passed to the constructor of the resulting + lookup table. + """ + size_1d, size_2d, size_3d = cls._check_size(size) + if channels not in (3, 4): + msg = "Only 3 or 4 output channels are supported" + raise ValueError(msg) + + table: list[float] = [0] * (size_1d * size_2d * size_3d * channels) + idx_out = 0 + for b in range(size_3d): + for g in range(size_2d): + for r in range(size_1d): + table[idx_out : idx_out + channels] = callback( + r / (size_1d - 1), g / (size_2d - 1), b / (size_3d - 1) + ) + idx_out += channels + + return cls( + (size_1d, size_2d, size_3d), + table, + channels=channels, + target_mode=target_mode, + _copy_table=False, + ) + + def transform( + self, + callback: Callable[..., tuple[float, ...]], + with_normals: bool = False, + channels: int | None = None, + target_mode: str | None = None, + ) -> Color3DLUT: + """Transforms the table values using provided callback and returns + a new LUT with altered values. + + :param callback: A function which takes old lookup table values + and returns a new set of values. The number + of arguments which function should take is + ``self.channels`` or ``3 + self.channels`` + if ``with_normals`` flag is set. + Should return a tuple of ``self.channels`` or + ``channels`` elements if it is set. + :param with_normals: If true, ``callback`` will be called with + coordinates in the color cube as the first + three arguments. Otherwise, ``callback`` + will be called only with actual color values. + :param channels: The number of channels in the resulting lookup table. + :param target_mode: Passed to the constructor of the resulting + lookup table. + """ + if channels not in (None, 3, 4): + msg = "Only 3 or 4 output channels are supported" + raise ValueError(msg) + ch_in = self.channels + ch_out = channels or ch_in + size_1d, size_2d, size_3d = self.size + + table: list[float] = [0] * (size_1d * size_2d * size_3d * ch_out) + idx_in = 0 + idx_out = 0 + for b in range(size_3d): + for g in range(size_2d): + for r in range(size_1d): + values = self.table[idx_in : idx_in + ch_in] + if with_normals: + values = callback( + r / (size_1d - 1), + g / (size_2d - 1), + b / (size_3d - 1), + *values, + ) + else: + values = callback(*values) + table[idx_out : idx_out + ch_out] = values + idx_in += ch_in + idx_out += ch_out + + return type(self)( + self.size, + table, + channels=ch_out, + target_mode=target_mode or self.mode, + _copy_table=False, + ) + + def __repr__(self) -> str: + r = [ + f"{self.__class__.__name__} from {self.table.__class__.__name__}", + "size={:d}x{:d}x{:d}".format(*self.size), + f"channels={self.channels:d}", + ] + if self.mode: + r.append(f"target_mode={self.mode}") + return "<{}>".format(" ".join(r)) + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + from . import Image + + return image.color_lut_3d( + self.mode or image.mode, + Image.Resampling.BILINEAR, + self.channels, + self.size, + self.table, + ) diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageFont.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageFont.py new file mode 100644 index 0000000000000000000000000000000000000000..d11f7bf01ad062ae376b3c01c8014541b7037abb --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageFont.py @@ -0,0 +1,1320 @@ +# +# The Python Imaging Library. +# $Id$ +# +# PIL raster font management +# +# History: +# 1996-08-07 fl created (experimental) +# 1997-08-25 fl minor adjustments to handle fonts from pilfont 0.3 +# 1999-02-06 fl rewrote most font management stuff in C +# 1999-03-17 fl take pth files into account in load_path (from Richard Jones) +# 2001-02-17 fl added freetype support +# 2001-05-09 fl added TransposedFont wrapper class +# 2002-03-04 fl make sure we have a "L" or "1" font +# 2002-12-04 fl skip non-directory entries in the system path +# 2003-04-29 fl add embedded default font +# 2003-09-27 fl added support for truetype charmap encodings +# +# Todo: +# Adapt to PILFONT2 format (16-bit fonts, compressed, single file) +# +# Copyright (c) 1997-2003 by Secret Labs AB +# Copyright (c) 1996-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# + +from __future__ import annotations + +import base64 +import os +import sys +import warnings +from enum import IntEnum +from io import BytesIO +from types import ModuleType +from typing import IO, Any, BinaryIO, TypedDict, cast + +from . import Image +from ._typing import StrOrBytesPath +from ._util import DeferredError, is_path + +TYPE_CHECKING = False +if TYPE_CHECKING: + from . import ImageFile + from ._imaging import ImagingFont + from ._imagingft import Font + + +class Axis(TypedDict): + minimum: int | None + default: int | None + maximum: int | None + name: bytes | None + + +class Layout(IntEnum): + BASIC = 0 + RAQM = 1 + + +MAX_STRING_LENGTH = 1_000_000 + + +core: ModuleType | DeferredError +try: + from . import _imagingft as core +except ImportError as ex: + core = DeferredError.new(ex) + + +def _string_length_check(text: str | bytes | bytearray) -> None: + if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH: + msg = "too many characters in string" + raise ValueError(msg) + + +# FIXME: add support for pilfont2 format (see FontFile.py) + +# -------------------------------------------------------------------- +# Font metrics format: +# "PILfont" LF +# fontdescriptor LF +# (optional) key=value... LF +# "DATA" LF +# binary data: 256*10*2 bytes (dx, dy, dstbox, srcbox) +# +# To place a character, cut out srcbox and paste at dstbox, +# relative to the character position. Then move the character +# position according to dx, dy. +# -------------------------------------------------------------------- + + +class ImageFont: + """PIL font wrapper""" + + font: ImagingFont + + def _load_pilfont(self, filename: str) -> None: + with open(filename, "rb") as fp: + image: ImageFile.ImageFile | None = None + root = os.path.splitext(filename)[0] + + for ext in (".png", ".gif", ".pbm"): + if image: + image.close() + try: + fullname = root + ext + image = Image.open(fullname) + except Exception: + pass + else: + if image and image.mode in ("1", "L"): + break + else: + if image: + image.close() + + msg = f"cannot find glyph data file {root}.{{gif|pbm|png}}" + raise OSError(msg) + + self.file = fullname + + self._load_pilfont_data(fp, image) + image.close() + + def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None: + # check image + if image.mode not in ("1", "L"): + image.close() + + msg = "invalid font image mode" + raise TypeError(msg) + + # read PILfont header + if file.read(8) != b"PILfont\n": + image.close() + + msg = "Not a PILfont file" + raise SyntaxError(msg) + file.readline() + self.info = [] # FIXME: should be a dictionary + while True: + s = file.readline() + if not s or s == b"DATA\n": + break + self.info.append(s) + + # read PILfont metrics + data = file.read(256 * 20) + + image.load() + + self.font = Image.core.font(image.im, data) + + def getmask( + self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any + ) -> Image.core.ImagingCore: + """ + Create a bitmap for the text. + + If the font uses antialiasing, the bitmap should have mode ``L`` and use a + maximum value of 255. Otherwise, it should have mode ``1``. + + :param text: Text to render. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + .. versionadded:: 1.1.5 + + :return: An internal PIL storage memory instance as defined by the + :py:mod:`PIL.Image.core` interface module. + """ + _string_length_check(text) + Image._decompression_bomb_check(self.font.getsize(text)) + return self.font.getmask(text, mode) + + def getbbox( + self, text: str | bytes | bytearray, *args: Any, **kwargs: Any + ) -> tuple[int, int, int, int]: + """ + Returns bounding box (in pixels) of given text. + + .. versionadded:: 9.2.0 + + :param text: Text to render. + + :return: ``(left, top, right, bottom)`` bounding box + """ + _string_length_check(text) + width, height = self.font.getsize(text) + return 0, 0, width, height + + def getlength( + self, text: str | bytes | bytearray, *args: Any, **kwargs: Any + ) -> int: + """ + Returns length (in pixels) of given text. + This is the amount by which following text should be offset. + + .. versionadded:: 9.2.0 + """ + _string_length_check(text) + width, height = self.font.getsize(text) + return width + + +## +# Wrapper for FreeType fonts. Application code should use the +# truetype factory function to create font objects. + + +class FreeTypeFont: + """FreeType font wrapper (requires _imagingft service)""" + + font: Font + font_bytes: bytes + + def __init__( + self, + font: StrOrBytesPath | BinaryIO, + size: float = 10, + index: int = 0, + encoding: str = "", + layout_engine: Layout | None = None, + ) -> None: + # FIXME: use service provider instead + + if isinstance(core, DeferredError): + raise core.ex + + if size <= 0: + msg = f"font size must be greater than 0, not {size}" + raise ValueError(msg) + + self.path = font + self.size = size + self.index = index + self.encoding = encoding + + if layout_engine not in (Layout.BASIC, Layout.RAQM): + layout_engine = Layout.BASIC + if core.HAVE_RAQM: + layout_engine = Layout.RAQM + elif layout_engine == Layout.RAQM and not core.HAVE_RAQM: + warnings.warn( + "Raqm layout was requested, but Raqm is not available. " + "Falling back to basic layout." + ) + layout_engine = Layout.BASIC + + self.layout_engine = layout_engine + + def load_from_bytes(f: IO[bytes]) -> None: + self.font_bytes = f.read() + self.font = core.getfont( + "", size, index, encoding, self.font_bytes, layout_engine + ) + + if is_path(font): + font = os.fspath(font) + if sys.platform == "win32": + font_bytes_path = font if isinstance(font, bytes) else font.encode() + try: + font_bytes_path.decode("ascii") + except UnicodeDecodeError: + # FreeType cannot load fonts with non-ASCII characters on Windows + # So load it into memory first + with open(font, "rb") as f: + load_from_bytes(f) + return + self.font = core.getfont( + font, size, index, encoding, layout_engine=layout_engine + ) + else: + load_from_bytes(cast(IO[bytes], font)) + + def __getstate__(self) -> list[Any]: + return [self.path, self.size, self.index, self.encoding, self.layout_engine] + + def __setstate__(self, state: list[Any]) -> None: + path, size, index, encoding, layout_engine = state + FreeTypeFont.__init__(self, path, size, index, encoding, layout_engine) + + def getname(self) -> tuple[str | None, str | None]: + """ + :return: A tuple of the font family (e.g. Helvetica) and the font style + (e.g. Bold) + """ + return self.font.family, self.font.style + + def getmetrics(self) -> tuple[int, int]: + """ + :return: A tuple of the font ascent (the distance from the baseline to + the highest outline point) and descent (the distance from the + baseline to the lowest outline point, a negative value) + """ + return self.font.ascent, self.font.descent + + def getlength( + self, + text: str | bytes, + mode: str = "", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + ) -> float: + """ + Returns length (in pixels with 1/64 precision) of given text when rendered + in font with provided direction, features, and language. + + This is the amount by which following text should be offset. + Text bounding box may extend past the length in some fonts, + e.g. when using italics or accents. + + The result is returned as a float; it is a whole number if using basic layout. + + Note that the sum of two lengths may not equal the length of a concatenated + string due to kerning. If you need to adjust for kerning, include the following + character and subtract its length. + + For example, instead of :: + + hello = font.getlength("Hello") + world = font.getlength("World") + hello_world = hello + world # not adjusted for kerning + assert hello_world == font.getlength("HelloWorld") # may fail + + use :: + + hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning + world = font.getlength("World") + hello_world = hello + world # adjusted for kerning + assert hello_world == font.getlength("HelloWorld") # True + + or disable kerning with (requires libraqm) :: + + hello = draw.textlength("Hello", font, features=["-kern"]) + world = draw.textlength("World", font, features=["-kern"]) + hello_world = hello + world # kerning is disabled, no need to adjust + assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) + + .. versionadded:: 8.0.0 + + :param text: Text to measure. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + `_ + Requires libraqm. + + :return: Either width for horizontal text, or height for vertical text. + """ + _string_length_check(text) + return self.font.getlength(text, mode, direction, features, language) / 64 + + def getbbox( + self, + text: str | bytes, + mode: str = "", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + anchor: str | None = None, + ) -> tuple[float, float, float, float]: + """ + Returns bounding box (in pixels) of given text relative to given anchor + when rendered in font with provided direction, features, and language. + + Use :py:meth:`getlength()` to get the offset of following text with + 1/64 pixel precision. The bounding box includes extra margins for + some fonts, e.g. italics or accents. + + .. versionadded:: 8.0.0 + + :param text: Text to render. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + `_ + Requires libraqm. + + :param stroke_width: The width of the text stroke. + + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left, + specifically ``la`` for horizontal text and ``lt`` for + vertical text. See :ref:`text-anchors` for details. + + :return: ``(left, top, right, bottom)`` bounding box + """ + _string_length_check(text) + size, offset = self.font.getsize( + text, mode, direction, features, language, anchor + ) + left, top = offset[0] - stroke_width, offset[1] - stroke_width + width, height = size[0] + 2 * stroke_width, size[1] + 2 * stroke_width + return left, top, left + width, top + height + + def getmask( + self, + text: str | bytes, + mode: str = "", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + anchor: str | None = None, + ink: int = 0, + start: tuple[float, float] | None = None, + ) -> Image.core.ImagingCore: + """ + Create a bitmap for the text. + + If the font uses antialiasing, the bitmap should have mode ``L`` and use a + maximum value of 255. If the font has embedded color data, the bitmap + should have mode ``RGBA``. Otherwise, it should have mode ``1``. + + :param text: Text to render. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + .. versionadded:: 1.1.5 + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + `_ + Requires libraqm. + + .. versionadded:: 6.0.0 + + :param stroke_width: The width of the text stroke. + + .. versionadded:: 6.2.0 + + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left, + specifically ``la`` for horizontal text and ``lt`` for + vertical text. See :ref:`text-anchors` for details. + + .. versionadded:: 8.0.0 + + :param ink: Foreground ink for rendering in RGBA mode. + + .. versionadded:: 8.0.0 + + :param start: Tuple of horizontal and vertical offset, as text may render + differently when starting at fractional coordinates. + + .. versionadded:: 9.4.0 + + :return: An internal PIL storage memory instance as defined by the + :py:mod:`PIL.Image.core` interface module. + """ + return self.getmask2( + text, + mode, + direction=direction, + features=features, + language=language, + stroke_width=stroke_width, + anchor=anchor, + ink=ink, + start=start, + )[0] + + def getmask2( + self, + text: str | bytes, + mode: str = "", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + anchor: str | None = None, + ink: int = 0, + start: tuple[float, float] | None = None, + *args: Any, + **kwargs: Any, + ) -> tuple[Image.core.ImagingCore, tuple[int, int]]: + """ + Create a bitmap for the text. + + If the font uses antialiasing, the bitmap should have mode ``L`` and use a + maximum value of 255. If the font has embedded color data, the bitmap + should have mode ``RGBA``. Otherwise, it should have mode ``1``. + + :param text: Text to render. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + .. versionadded:: 1.1.5 + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + `_ + Requires libraqm. + + .. versionadded:: 6.0.0 + + :param stroke_width: The width of the text stroke. + + .. versionadded:: 6.2.0 + + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left, + specifically ``la`` for horizontal text and ``lt`` for + vertical text. See :ref:`text-anchors` for details. + + .. versionadded:: 8.0.0 + + :param ink: Foreground ink for rendering in RGBA mode. + + .. versionadded:: 8.0.0 + + :param start: Tuple of horizontal and vertical offset, as text may render + differently when starting at fractional coordinates. + + .. versionadded:: 9.4.0 + + :return: A tuple of an internal PIL storage memory instance as defined by the + :py:mod:`PIL.Image.core` interface module, and the text offset, the + gap between the starting coordinate and the first marking + """ + _string_length_check(text) + if start is None: + start = (0, 0) + + def fill(width: int, height: int) -> Image.core.ImagingCore: + size = (width, height) + Image._decompression_bomb_check(size) + return Image.core.fill("RGBA" if mode == "RGBA" else "L", size) + + return self.font.render( + text, + fill, + mode, + direction, + features, + language, + stroke_width, + kwargs.get("stroke_filled", False), + anchor, + ink, + start, + ) + + def font_variant( + self, + font: StrOrBytesPath | BinaryIO | None = None, + size: float | None = None, + index: int | None = None, + encoding: str | None = None, + layout_engine: Layout | None = None, + ) -> FreeTypeFont: + """ + Create a copy of this FreeTypeFont object, + using any specified arguments to override the settings. + + Parameters are identical to the parameters used to initialize this + object. + + :return: A FreeTypeFont object. + """ + if font is None: + try: + font = BytesIO(self.font_bytes) + except AttributeError: + font = self.path + return FreeTypeFont( + font=font, + size=self.size if size is None else size, + index=self.index if index is None else index, + encoding=self.encoding if encoding is None else encoding, + layout_engine=layout_engine or self.layout_engine, + ) + + def get_variation_names(self) -> list[bytes]: + """ + :returns: A list of the named styles in a variation font. + :exception OSError: If the font is not a variation font. + """ + names = [] + for name in self.font.getvarnames(): + name = name.replace(b"\x00", b"") + if name not in names: + names.append(name) + return names + + def set_variation_by_name(self, name: str | bytes) -> None: + """ + :param name: The name of the style. + :exception OSError: If the font is not a variation font. + """ + names = self.get_variation_names() + if not isinstance(name, bytes): + name = name.encode() + index = names.index(name) + 1 + + if index == getattr(self, "_last_variation_index", None): + # When the same name is set twice in a row, + # there is an 'unknown freetype error' + # https://savannah.nongnu.org/bugs/?56186 + return + self._last_variation_index = index + + self.font.setvarname(index) + + def get_variation_axes(self) -> list[Axis]: + """ + :returns: A list of the axes in a variation font. + :exception OSError: If the font is not a variation font. + """ + axes = self.font.getvaraxes() + for axis in axes: + if axis["name"]: + axis["name"] = axis["name"].replace(b"\x00", b"") + return axes + + def set_variation_by_axes(self, axes: list[float]) -> None: + """ + :param axes: A list of values for each axis. + :exception OSError: If the font is not a variation font. + """ + self.font.setvaraxes(axes) + + +class TransposedFont: + """Wrapper for writing rotated or mirrored text""" + + def __init__( + self, font: ImageFont | FreeTypeFont, orientation: Image.Transpose | None = None + ): + """ + Wrapper that creates a transposed font from any existing font + object. + + :param font: A font object. + :param orientation: An optional orientation. If given, this should + be one of Image.Transpose.FLIP_LEFT_RIGHT, Image.Transpose.FLIP_TOP_BOTTOM, + Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_180, or + Image.Transpose.ROTATE_270. + """ + self.font = font + self.orientation = orientation # any 'transpose' argument, or None + + def getmask( + self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any + ) -> Image.core.ImagingCore: + im = self.font.getmask(text, mode, *args, **kwargs) + if self.orientation is not None: + return im.transpose(self.orientation) + return im + + def getbbox( + self, text: str | bytes, *args: Any, **kwargs: Any + ) -> tuple[int, int, float, float]: + # TransposedFont doesn't support getmask2, move top-left point to (0, 0) + # this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont + left, top, right, bottom = self.font.getbbox(text, *args, **kwargs) + width = right - left + height = bottom - top + if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270): + return 0, 0, height, width + return 0, 0, width, height + + def getlength(self, text: str | bytes, *args: Any, **kwargs: Any) -> float: + if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270): + msg = "text length is undefined for text rotated by 90 or 270 degrees" + raise ValueError(msg) + return self.font.getlength(text, *args, **kwargs) + + +def load(filename: str) -> ImageFont: + """ + Load a font file. This function loads a font object from the given + bitmap font file, and returns the corresponding font object. For loading TrueType + or OpenType fonts instead, see :py:func:`~PIL.ImageFont.truetype`. + + :param filename: Name of font file. + :return: A font object. + :exception OSError: If the file could not be read. + """ + f = ImageFont() + f._load_pilfont(filename) + return f + + +def truetype( + font: StrOrBytesPath | BinaryIO, + size: float = 10, + index: int = 0, + encoding: str = "", + layout_engine: Layout | None = None, +) -> FreeTypeFont: + """ + Load a TrueType or OpenType font from a file or file-like object, + and create a font object. This function loads a font object from the given + file or file-like object, and creates a font object for a font of the given + size. For loading bitmap fonts instead, see :py:func:`~PIL.ImageFont.load` + and :py:func:`~PIL.ImageFont.load_path`. + + Pillow uses FreeType to open font files. On Windows, be aware that FreeType + will keep the file open as long as the FreeTypeFont object exists. Windows + limits the number of files that can be open in C at once to 512, so if many + fonts are opened simultaneously and that limit is approached, an + ``OSError`` may be thrown, reporting that FreeType "cannot open resource". + A workaround would be to copy the file(s) into memory, and open that instead. + + This function requires the _imagingft service. + + :param font: A filename or file-like object containing a TrueType font. + If the file is not found in this filename, the loader may also + search in other directories, such as: + + * The :file:`fonts/` directory on Windows, + * :file:`/Library/Fonts/`, :file:`/System/Library/Fonts/` + and :file:`~/Library/Fonts/` on macOS. + * :file:`~/.local/share/fonts`, :file:`/usr/local/share/fonts`, + and :file:`/usr/share/fonts` on Linux; or those specified by + the ``XDG_DATA_HOME`` and ``XDG_DATA_DIRS`` environment variables + for user-installed and system-wide fonts, respectively. + + :param size: The requested size, in pixels. + :param index: Which font face to load (default is first available face). + :param encoding: Which font encoding to use (default is Unicode). Possible + encodings include (see the FreeType documentation for more + information): + + * "unic" (Unicode) + * "symb" (Microsoft Symbol) + * "ADOB" (Adobe Standard) + * "ADBE" (Adobe Expert) + * "ADBC" (Adobe Custom) + * "armn" (Apple Roman) + * "sjis" (Shift JIS) + * "gb " (PRC) + * "big5" + * "wans" (Extended Wansung) + * "joha" (Johab) + * "lat1" (Latin-1) + + This specifies the character set to use. It does not alter the + encoding of any text provided in subsequent operations. + :param layout_engine: Which layout engine to use, if available: + :attr:`.ImageFont.Layout.BASIC` or :attr:`.ImageFont.Layout.RAQM`. + If it is available, Raqm layout will be used by default. + Otherwise, basic layout will be used. + + Raqm layout is recommended for all non-English text. If Raqm layout + is not required, basic layout will have better performance. + + You can check support for Raqm layout using + :py:func:`PIL.features.check_feature` with ``feature="raqm"``. + + .. versionadded:: 4.2.0 + :return: A font object. + :exception OSError: If the file could not be read. + :exception ValueError: If the font size is not greater than zero. + """ + + def freetype(font: StrOrBytesPath | BinaryIO) -> FreeTypeFont: + return FreeTypeFont(font, size, index, encoding, layout_engine) + + try: + return freetype(font) + except OSError: + if not is_path(font): + raise + ttf_filename = os.path.basename(font) + + dirs = [] + if sys.platform == "win32": + # check the windows font repository + # NOTE: must use uppercase WINDIR, to work around bugs in + # 1.5.2's os.environ.get() + windir = os.environ.get("WINDIR") + if windir: + dirs.append(os.path.join(windir, "fonts")) + elif sys.platform in ("linux", "linux2"): + data_home = os.environ.get("XDG_DATA_HOME") + if not data_home: + # The freedesktop spec defines the following default directory for + # when XDG_DATA_HOME is unset or empty. This user-level directory + # takes precedence over system-level directories. + data_home = os.path.expanduser("~/.local/share") + xdg_dirs = [data_home] + + data_dirs = os.environ.get("XDG_DATA_DIRS") + if not data_dirs: + # Similarly, defaults are defined for the system-level directories + data_dirs = "/usr/local/share:/usr/share" + xdg_dirs += data_dirs.split(":") + + dirs += [os.path.join(xdg_dir, "fonts") for xdg_dir in xdg_dirs] + elif sys.platform == "darwin": + dirs += [ + "/Library/Fonts", + "/System/Library/Fonts", + os.path.expanduser("~/Library/Fonts"), + ] + + ext = os.path.splitext(ttf_filename)[1] + first_font_with_a_different_extension = None + for directory in dirs: + for walkroot, walkdir, walkfilenames in os.walk(directory): + for walkfilename in walkfilenames: + if ext and walkfilename == ttf_filename: + return freetype(os.path.join(walkroot, walkfilename)) + elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename: + fontpath = os.path.join(walkroot, walkfilename) + if os.path.splitext(fontpath)[1] == ".ttf": + return freetype(fontpath) + if not ext and first_font_with_a_different_extension is None: + first_font_with_a_different_extension = fontpath + if first_font_with_a_different_extension: + return freetype(first_font_with_a_different_extension) + raise + + +def load_path(filename: str | bytes) -> ImageFont: + """ + Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a + bitmap font along the Python path. + + :param filename: Name of font file. + :return: A font object. + :exception OSError: If the file could not be read. + """ + if not isinstance(filename, str): + filename = filename.decode("utf-8") + for directory in sys.path: + try: + return load(os.path.join(directory, filename)) + except OSError: + pass + msg = f'cannot find font file "{filename}" in sys.path' + if os.path.exists(filename): + msg += f', did you mean ImageFont.load("{filename}") instead?' + + raise OSError(msg) + + +def load_default_imagefont() -> ImageFont: + f = ImageFont() + f._load_pilfont_data( + # courB08 + BytesIO( + base64.b64decode( + b""" +UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA +BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL +AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA +AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB +ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A +BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB +//kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA +AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH +AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA +ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv +AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/ +/gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5 +AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA +AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG +AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA +BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA +AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA +2AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF +AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA//// ++gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA +////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA +BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv +AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA +AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA +AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA +BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP// +//kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA +AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF +AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB +mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn +AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA +AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7 +AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA +Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB +//sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA +AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ +AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC +DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ +AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/ ++wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5 +AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/ +///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG +AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA +BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA +Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC +eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG +AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA//// ++gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA +////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA +BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT +AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A +AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA +Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA +Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP// +//cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA +AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ +AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA +LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5 +AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA +AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5 +AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA +AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG +AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA +EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK +AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA +pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG +AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA//// ++QAGAAIAzgAKANUAEw== +""" + ) + ), + Image.open( + BytesIO( + base64.b64decode( + b""" +iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u +Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9 +M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g +LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F +IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA +Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791 +NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx +in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9 +SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY +AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt +y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG +ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY +lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H +/Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3 +AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47 +c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/ +/yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw +pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv +oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR +evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA +AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v// +Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR +w7IkEbzhVQAAAABJRU5ErkJggg== +""" + ) + ) + ), + ) + return f + + +def load_default(size: float | None = None) -> FreeTypeFont | ImageFont: + """If FreeType support is available, load a version of Aileron Regular, + https://dotcolon.net/fonts/aileron, with a more limited character set. + + Otherwise, load a "better than nothing" font. + + .. versionadded:: 1.1.4 + + :param size: The font size of Aileron Regular. + + .. versionadded:: 10.1.0 + + :return: A font object. + """ + if isinstance(core, ModuleType) or size is not None: + return truetype( + BytesIO( + base64.b64decode( + b""" +AAEAAAAPAIAAAwBwRkZUTYwDlUAAADFoAAAAHEdERUYAqADnAAAo8AAAACRHUE9ThhmITwAAKfgAA +AduR1NVQnHxefoAACkUAAAA4k9TLzJovoHLAAABeAAAAGBjbWFw5lFQMQAAA6gAAAGqZ2FzcP//AA +MAACjoAAAACGdseWYmRXoPAAAGQAAAHfhoZWFkE18ayQAAAPwAAAA2aGhlYQboArEAAAE0AAAAJGh +tdHjjERZ8AAAB2AAAAdBsb2NhuOexrgAABVQAAADqbWF4cAC7AEYAAAFYAAAAIG5hbWUr+h5lAAAk +OAAAA6Jwb3N0D3oPTQAAJ9wAAAEKAAEAAAABGhxJDqIhXw889QALA+gAAAAA0Bqf2QAAAADhCh2h/ +2r/LgOxAyAAAAAIAAIAAAAAAAAAAQAAA8r/GgAAA7j/av9qA7EAAQAAAAAAAAAAAAAAAAAAAHQAAQ +AAAHQAQwAFAAAAAAACAAAAAQABAAAAQAAAAAAAAAADAfoBkAAFAAgCigJYAAAASwKKAlgAAAFeADI +BPgAAAAAFAAAAAAAAAAAAAAcAAAAAAAAAAAAAAABVS1dOAEAAIPsCAwL/GgDIA8oA5iAAAJMAAAAA +AhICsgAAACAAAwH0AAAAAAAAAU0AAADYAAAA8gA5AVMAVgJEAEYCRAA1AuQAKQKOAEAAsAArATsAZ +AE7AB4CMABVAkQAUADc/+EBEgAgANwAJQEv//sCRAApAkQAggJEADwCRAAtAkQAIQJEADkCRAArAk +QAMgJEACwCRAAxANwAJQDc/+ECRABnAkQAUAJEAEQB8wAjA1QANgJ/AB0CcwBkArsALwLFAGQCSwB +kAjcAZALGAC8C2gBkAQgAZAIgADcCYQBkAj8AZANiAGQCzgBkAuEALwJWAGQC3QAvAmsAZAJJADQC +ZAAiAqoAXgJuACADuAAaAnEAGQJFABMCTwAuATMAYgEv//sBJwAiAkQAUAH0ADIBLAApAhMAJAJjA +EoCEQAeAmcAHgIlAB4BIgAVAmcAHgJRAEoA7gA+AOn/8wIKAEoA9wBGA1cASgJRAEoCSgAeAmMASg +JnAB4BSgBKAcsAGAE5ABQCUABCAgIAAQMRAAEB4v/6AgEAAQHOABQBLwBAAPoAYAEvACECRABNA0Y +AJAItAHgBKgAcAkQAUAEsAHQAygAgAi0AOQD3ADYA9wAWAaEANgGhABYCbAAlAYMAeAGDADkA6/9q +AhsAFAIKABUB/QAVAAAAAwAAAAMAAAAcAAEAAAAAAKQAAwABAAAAHAAEAIgAAAAeABAAAwAOAH4Aq +QCrALEAtAC3ALsgGSAdICYgOiBEISL7Av//AAAAIACpAKsAsAC0ALcAuyAYIBwgJiA5IEQhIvsB// +//4/+5/7j/tP+y/7D/reBR4E/gR+A14CzfTwVxAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAEGAAABAAAAAAAAAAECAAAAAgAAAAAAAAAAAAAAAAAAAAEAAAMEBQYHCAkKCwwNDg8QERIT +FBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMT +U5PUFFSU1RVVldYWVpbXF1eX2BhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAA +AAAAAAYnFmAAAAAABlAAAAAAAAAAAAAAAAAAAAAAAAAAAAY2htAAAAAAAAAABrbGlqAAAAAHAAbm9 +ycwBnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmACYAJgAmAD4AUgCCAMoBCgFO +AVwBcgGIAaYBvAHKAdYB6AH2AgwCIAJKAogCpgLWAw4DIgNkA5wDugPUA+gD/AQQBEYEogS8BPoFJ +gVSBWoFgAWwBcoF1gX6BhQGJAZMBmgGiga0BuIHGgdUB2YHkAeiB8AH3AfyCAoIHAgqCDoITghcCG +oIogjSCPoJKglYCXwJwgnqCgIKKApACl4Klgq8CtwLDAs8C1YLjAuyC9oL7gwMDCYMSAxgDKAMrAz +qDQoNTA1mDYQNoA2uDcAN2g3oDfYODA4iDkoOXA5sDnoOnA7EDvwAAAAFAAAAAAH0ArwAAwAGAAkA +DAAPAAAxESERAxMhExcRASELARETAfT6qv6syKr+jgFUqsiqArz9RAGLAP/+1P8B/v3VAP8BLP4CA +P8AAgA5//IAuQKyAAMACwAANyMDMwIyFhQGIiY0oE4MZk84JCQ4JLQB/v3AJDgkJDgAAgBWAeUBPA +LfAAMABwAAEyMnMxcjJzOmRgpagkYKWgHl+vr6AAAAAAIARgAAAf4CsgAbAB8AAAEHMxUjByM3Iwc +jNyM1MzcjNTM3MwczNzMHMxUrAQczAZgdZXEvOi9bLzovWmYdZXEvOi9bLzovWp9bHlsBn4w429vb +2ziMONvb29s4jAAAAAMANf+mAg4DDAAfACYALAAAJRQGBxUjNS4BJzMeARcRLgE0Njc1MxUeARcjJ +icVHgEBFBYXNQ4BExU+ATU0Ag5xWDpgcgRcBz41Xl9oVTpVYwpcC1ttXP6cLTQuM5szOrVRZwlOTQ +ZqVzZECAEAGlukZAlOTQdrUG8O7iNlAQgxNhDlCDj+8/YGOjReAAAAAAUAKf/yArsCvAAHAAsAFQA +dACcAABIyFhQGIiY0EyMBMwQiBhUUFjI2NTQSMhYUBiImNDYiBhUUFjI2NTR5iFBQiFCVVwHAV/5c +OiMjOiPmiFBQiFCxOiMjOiMCvFaSVlaS/ZoCsjIzMC80NC8w/uNWklZWkhozMC80NC8wAAAAAgBA/ +/ICbgLAACIALgAAARUjEQYjIiY1NDY3LgE1NDYzMhcVJiMiBhUUFhcWOwE1MxUFFBYzMjc1IyIHDg +ECbmBcYYOOVkg7R4hsQjY4Q0RNRD4SLDxW/pJUXzksPCkUUk0BgUb+zBVUZ0BkDw5RO1huCkULQzp +COAMBcHDHRz0J/AIHRQAAAAEAKwHlAIUC3wADAAATIycze0YKWgHl+gAAAAABAGT/sAEXAwwACQAA +EzMGEBcjLgE0Nt06dXU6OUBAAwzG/jDGVePs4wAAAAEAHv+wANEDDAAJAAATMx4BFAYHIzYQHjo5Q +EA5OnUDDFXj7ONVxgHQAAAAAQBVAFIB2wHbAA4AAAE3FwcXBycHJzcnNxcnMwEtmxOfcTJjYzJxnx +ObCj4BKD07KYolmZkliik7PbMAAQBQAFUB9AIlAAsAAAEjFSM1IzUzNTMVMwH0tTq1tTq1AR/Kyjj +OzgAAAAAB/+H/iACMAGQABAAANwcjNzOMWlFOXVrS3AAAAQAgAP8A8gE3AAMAABMjNTPy0tIA/zgA +AQAl//IApQByAAcAADYyFhQGIiY0STgkJDgkciQ4JCQ4AAAAAf/7/+IBNALQAAMAABcjEzM5Pvs+H +gLuAAAAAAIAKf/yAhsCwAADAAcAABIgECA2IBAgKQHy/g5gATL+zgLA/TJEAkYAAAAAAQCCAAABlg +KyAAgAAAERIxEHNTc2MwGWVr6SIygCsv1OAldxW1sWAAEAPAAAAg4CwAAZAAA3IRUhNRM+ATU0JiM +iDwEjNz4BMzIWFRQGB7kBUv4x+kI2QTt+EAFWAQp8aGVtSl5GRjEA/0RVLzlLmAoKa3FsUkNxXQAA +AAEALf/yAhYCwAAqAAABHgEVFAYjIi8BMxceATMyNjU0KwE1MzI2NTQmIyIGDwEjNz4BMzIWFRQGA +YxBSZJo2RUBVgEHV0JBUaQREUBUQzc5TQcBVgEKfGhfcEMBbxJbQl1x0AoKRkZHPn9GSD80QUVCCg +pfbGBPOlgAAAACACEAAAIkArIACgAPAAAlIxUjNSE1ATMRMyMRBg8BAiRXVv6qAVZWV60dHLCurq4 +rAdn+QgFLMibzAAABADn/8gIZArIAHQAAATIWFRQGIyIvATMXFjMyNjU0JiMiByMTIRUhBzc2ATNv +d5Fl1RQBVgIad0VSTkVhL1IwAYj+vh8rMAHHgGdtgcUKCoFXTU5bYgGRRvAuHQAAAAACACv/8gITA +sAAFwAjAAABMhYVFAYjIhE0NjMyFh8BIycmIyIDNzYTMjY1NCYjIgYVFBYBLmp7imr0l3RZdAgBXA +IYZ5wKJzU6QVNJSz5SUAHSgWltiQFGxcNlVQoKdv7sPiz+ZF1LTmJbU0lhAAAAAQAyAAACGgKyAAY +AAAEVASMBITUCGv6oXAFL/oECsij9dgJsRgAAAAMALP/xAhgCwAAWACAALAAAAR4BFRQGIyImNTQ2 +Ny4BNTQ2MhYVFAYmIgYVFBYyNjU0AzI2NTQmIyIGFRQWAZQ5S5BmbIpPOjA7ecp5P2F8Q0J8RIVJS +0pLTEtOAW0TXTxpZ2ZqPF0SE1A3VWVlVTdQ/UU0N0RENzT9/ko+Ok1NOj1LAAIAMf/yAhkCwAAXAC +MAAAEyERQGIyImLwEzFxYzMhMHBiMiJjU0NhMyNjU0JiMiBhUUFgEl9Jd0WXQIAVwCGGecCic1SWp +7imo+UlBAQVNJAsD+usXDZVUKCnYBFD4sgWltif5kW1NJYV1LTmIAAAACACX/8gClAiAABwAPAAAS +MhYUBiImNBIyFhQGIiY0STgkJDgkJDgkJDgkAiAkOCQkOP52JDgkJDgAAAAC/+H/iAClAiAABwAMA +AASMhYUBiImNBMHIzczSTgkJDgkaFpSTl4CICQ4JCQ4/mba5gAAAQBnAB4B+AH0AAYAAAENARUlNS +UB+P6qAVb+bwGRAbCmpkbJRMkAAAIAUAC7AfQBuwADAAcAAAEhNSERITUhAfT+XAGk/lwBpAGDOP8 +AOAABAEQAHgHVAfQABgAAARUFNS0BNQHV/m8BVv6qAStEyUSmpkYAAAAAAgAj//IB1ALAABgAIAAA +ATIWFRQHDgEHIz4BNz4BNTQmIyIGByM+ARIyFhQGIiY0AQRibmktIAJWBSEqNig+NTlHBFoDezQ4J +CQ4JALAZ1BjaS03JS1DMD5LLDQ/SUVgcv2yJDgkJDgAAAAAAgA2/5gDFgKYADYAQgAAAQMGFRQzMj +Y1NCYjIg4CFRQWMzI2NxcGIyImNTQ+AjMyFhUUBiMiJwcGIyImNTQ2MzIfATcHNzYmIyIGFRQzMjY +Cej8EJjJJlnBAfGQ+oHtAhjUYg5OPx0h2k06Os3xRWQsVLjY5VHtdPBwJETcJDyUoOkZEJz8B0f74 +EQ8kZl6EkTFZjVOLlyknMVm1pmCiaTq4lX6CSCknTVRmmR8wPdYnQzxuSWVGAAIAHQAAAncCsgAHA +AoAACUjByMTMxMjATMDAcj+UVz4dO5d/sjPZPT0ArL9TgE6ATQAAAADAGQAAAJMArIAEAAbACcAAA +EeARUUBgcGKwERMzIXFhUUJRUzMjc2NTQnJiMTPgE1NCcmKwEVMzIBvkdHZkwiNt7LOSGq/oeFHBt +hahIlSTM+cB8Yj5UWAW8QT0VYYgwFArIEF5Fv1eMED2NfDAL93AU+N24PBP0AAAAAAQAv//ICjwLA +ABsAAAEyFh8BIycmIyIGFRQWMzI/ATMHDgEjIiY1NDYBdX+PCwFWAiKiaHx5ZaIiAlYBCpWBk6a0A +sCAagoKpqN/gaOmCgplhcicn8sAAAIAZAAAAp8CsgAMABkAAAEeARUUBgcGKwERMzITPgE1NCYnJi +sBETMyAY59lJp8IzXN0jUVWmdjWRs5d3I4Aq4QqJWUug8EArL9mQ+PeHGHDgX92gAAAAABAGQAAAI +vArIACwAAJRUhESEVIRUhFSEVAi/+NQHB/pUBTf6zRkYCskbwRvAAAAABAGQAAAIlArIACQAAExUh +FSERIxEhFboBQ/69VgHBAmzwRv7KArJGAAAAAAEAL//yAo8CwAAfAAABMxEjNQcGIyImNTQ2MzIWH +wEjJyYjIgYVFBYzMjY1IwGP90wfPnWTprSSf48LAVYCIqJofHllVG+hAU3+s3hARsicn8uAagoKpq +N/gaN1XAAAAAEAZAAAAowCsgALAAABESMRIREjETMRIRECjFb+hFZWAXwCsv1OAS7+0gKy/sQBPAA +AAAABAGQAAAC6ArIAAwAAMyMRM7pWVgKyAAABADf/8gHoArIAEwAAAREUBw4BIyImLwEzFxYzMjc2 +NREB6AIFcGpgbQIBVgIHfXQKAQKy/lYxIltob2EpKYyEFD0BpwAAAAABAGQAAAJ0ArIACwAACQEjA +wcVIxEzEQEzATsBJ3ntQlZWAVVlAWH+nwEnR+ACsv6RAW8AAQBkAAACLwKyAAUAACUVIREzEQIv/j +VWRkYCsv2UAAABAGQAAAMUArIAFAAAAREjETQ3BgcDIwMmJxYVESMRMxsBAxRWAiMxemx8NxsCVo7 +MywKy/U4BY7ZLco7+nAFmoFxLtP6dArL9lwJpAAAAAAEAZAAAAoACsgANAAAhIwEWFREjETMBJjUR +MwKAhP67A1aEAUUDVAJeeov+pwKy/aJ5jAFZAAAAAgAv//ICuwLAAAkAEwAAEiAWFRQGICY1NBIyN +jU0JiIGFRTbATSsrP7MrNrYenrYegLAxaKhxsahov47nIeIm5uIhwACAGQAAAJHArIADgAYAAABHg +EVFAYHBisBESMRMzITNjQnJisBETMyAZRUX2VOHzuAVtY7GlxcGDWIiDUCrgtnVlVpCgT+5gKy/rU +V1BUF/vgAAAACAC//zAK9AsAAEgAcAAAlFhcHJiMiBwYjIiY1NDYgFhUUJRQWMjY1NCYiBgI9PUMx +UDcfKh8omqysATSs/dR62Hp62HpICTg7NgkHxqGixcWitbWHnJyHiJubAAIAZAAAAlgCsgAXACMAA +CUWFyMmJyYnJisBESMRMzIXHgEVFAYHFiUzMjc+ATU0JyYrAQIqDCJfGQwNWhAhglbiOx9QXEY1Tv +6bhDATMj1lGSyMtYgtOXR0BwH+1wKyBApbU0BSESRAAgVAOGoQBAABADT/8gIoAsAAJQAAATIWFyM +uASMiBhUUFhceARUUBiMiJiczHgEzMjY1NCYnLgE1NDYBOmd2ClwGS0E6SUNRdW+HZnKKC1wPWkQ9 +Uk1cZGuEAsBwXUJHNjQ3OhIbZVZZbm5kREo+NT5DFRdYUFdrAAAAAAEAIgAAAmQCsgAHAAABIxEjE +SM1IQJk9lb2AkICbP2UAmxGAAEAXv/yAmQCsgAXAAABERQHDgEiJicmNREzERQXHgEyNjc2NRECZA +IIgfCBCAJWAgZYmlgGAgKy/k0qFFxzc1wUKgGz/lUrEkRQUEQSKwGrAAAAAAEAIAAAAnoCsgAGAAA +hIwMzGwEzAYJ07l3N1FwCsv2PAnEAAAEAGgAAA7ECsgAMAAABAyMLASMDMxsBMxsBA7HAcZyicrZi +kaB0nJkCsv1OAlP9rQKy/ZsCW/2kAmYAAAEAGQAAAm8CsgALAAAhCwEjEwMzGwEzAxMCCsrEY/bkY +re+Y/D6AST+3AFcAVb+5gEa/q3+oQAAAQATAAACUQKyAAgAAAERIxEDMxsBMwFdVvRjwLphARD+8A +EQAaL+sQFPAAABAC4AAAI5ArIACQAAJRUhNQEhNSEVAQI5/fUBof57Aen+YUZGQgIqRkX92QAAAAA +BAGL/sAEFAwwABwAAARUjETMVIxEBBWlpowMMOP0UOANcAAAB//v/4gE0AtAAAwAABSMDMwE0Pvs+ +HgLuAAAAAQAi/7AAxQMMAAcAABcjNTMRIzUzxaNpaaNQOALsOAABAFAA1wH0AmgABgAAJQsBIxMzE +wGwjY1GsESw1wFZ/qcBkf5vAAAAAQAy/6oBwv/iAAMAAAUhNSEBwv5wAZBWOAAAAAEAKQJEALYCsg +ADAAATIycztjhVUAJEbgAAAAACACT/8gHQAiAAHQAlAAAhJwcGIyImNTQ2OwE1NCcmIyIHIz4BMzI +XFh0BFBcnMjY9ASYVFAF6CR0wVUtgkJoiAgdgaQlaBm1Zrg4DCuQ9R+5MOSFQR1tbDiwUUXBUXowf +J8c9SjRORzYSgVwAAAAAAgBK//ICRQLfABEAHgAAATIWFRQGIyImLwEVIxEzETc2EzI2NTQmIyIGH +QEUFgFUcYCVbiNJEyNWVigySElcU01JXmECIJd4i5QTEDRJAt/+3jkq/hRuZV55ZWsdX14AAQAe// +IB9wIgABgAAAEyFhcjJiMiBhUUFjMyNjczDgEjIiY1NDYBF152DFocbEJXU0A1Rw1aE3pbaoKQAiB +oWH5qZm1tPDlaXYuLgZcAAAACAB7/8gIZAt8AEQAeAAABESM1BwYjIiY1NDYzMhYfAREDMjY9ATQm +IyIGFRQWAhlWKDJacYCVbiNJEyOnSV5hQUlcUwLf/SFVOSqXeIuUExA0ARb9VWVrHV9ebmVeeQACA +B7/8gH9AiAAFQAbAAABFAchHgEzMjY3Mw4BIyImNTQ2MzIWJyIGByEmAf0C/oAGUkA1SwlaD4FXbI +WObmt45UBVBwEqDQEYFhNjWD84W16Oh3+akU9aU60AAAEAFQAAARoC8gAWAAATBh0BMxUjESMRIzU +zNTQ3PgEzMhcVJqcDbW1WOTkDB0k8Hx5oAngVITRC/jQBzEIsJRs5PwVHEwAAAAIAHv8uAhkCIAAi +AC8AAAERFAcOASMiLwEzFx4BMzI2NzY9AQcGIyImNTQ2MzIWHwE1AzI2PQE0JiMiBhUUFgIZAQSEd +NwRAVcBBU5DTlUDASgyWnGAlW4jSRMjp0leYUFJXFMCEv5wSh1zeq8KCTI8VU0ZIQk5Kpd4i5QTED +RJ/iJlax1fXm5lXnkAAQBKAAACCgLkABcAAAEWFREjETQnLgEHDgEdASMRMxE3NjMyFgIIAlYCBDs +6RVRWViE5UVViAYUbQP7WASQxGzI7AQJyf+kC5P7TPSxUAAACAD4AAACsAsAABwALAAASMhYUBiIm +NBMjETNeLiAgLiBiVlYCwCAuICAu/WACEgAC//P/LgCnAsAABwAVAAASMhYUBiImNBcRFAcGIyInN +RY3NjURWS4gIC4gYgMLcRwNSgYCAsAgLiAgLo79wCUbZAJGBzMOHgJEAAAAAQBKAAACCALfAAsAAC +EnBxUjETMREzMHEwGTwTJWVvdu9/rgN6kC3/4oAQv6/ugAAQBG//wA3gLfAA8AABMRFBceATcVBiM +iJicmNRGcAQIcIxkkKi4CAQLf/bkhERoSBD4EJC8SNAJKAAAAAQBKAAADEAIgACQAAAEWFREjETQn +JiMiFREjETQnJiMiFREjETMVNzYzMhYXNzYzMhYDCwVWBAxedFYEDF50VlYiJko7ThAvJkpEVAGfI +jn+vAEcQyRZ1v76ARxDJFnW/voCEk08HzYtRB9HAAAAAAEASgAAAgoCIAAWAAABFhURIxE0JyYjIg +YdASMRMxU3NjMyFgIIAlYCCXBEVVZWITlRVWIBhRtA/tYBJDEbbHR/6QISWz0sVAAAAAACAB7/8gI +sAiAABwARAAASIBYUBiAmNBIyNjU0JiIGFRSlAQCHh/8Ah7ieWlqeWgIgn/Cfn/D+s3ZfYHV1YF8A +AgBK/zwCRQIgABEAHgAAATIWFRQGIyImLwERIxEzFTc2EzI2NTQmIyIGHQEUFgFUcYCVbiNJEyNWV +igySElcU01JXmECIJd4i5QTEDT+8wLWVTkq/hRuZV55ZWsdX14AAgAe/zwCGQIgABEAHgAAAREjEQ +cGIyImNTQ2MzIWHwE1AzI2PQE0JiMiBhUUFgIZVigyWnGAlW4jSRMjp0leYUFJXFMCEv0qARk5Kpd +4i5QTEDRJ/iJlax1fXm5lXnkAAQBKAAABPgIeAA0AAAEyFxUmBhURIxEzFTc2ARoWDkdXVlYwIwIe +B0EFVlf+0gISU0cYAAEAGP/yAa0CIAAjAAATMhYXIyYjIgYVFBYXHgEVFAYjIiYnMxYzMjY1NCYnL +gE1NDbkV2MJWhNdKy04PF1XbVhWbgxaE2ktOjlEUllkAiBaS2MrJCUoEBlPQkhOVFZoKCUmLhIWSE +BIUwAAAAEAFP/4ARQCiQAXAAATERQXHgE3FQYjIiYnJjURIzUzNTMVMxWxAQMmMx8qMjMEAUdHVmM +BzP7PGw4mFgY/BSwxDjQBNUJ7e0IAAAABAEL/8gICAhIAFwAAAREjNQcGIyImJyY1ETMRFBceATMy +Nj0BAgJWITlRT2EKBVYEBkA1RFECEv3uWj4qTToiOQE+/tIlJC43c4DpAAAAAAEAAQAAAfwCEgAGA +AABAyMDMxsBAfzJaclfop8CEv3uAhL+LQHTAAABAAEAAAMLAhIADAAAAQMjCwEjAzMbATMbAQMLqW +Z2dmapY3t0a3Z7AhL97gG+/kICEv5AAcD+QwG9AAAB//oAAAHWAhIACwAAARMjJwcjEwMzFzczARq +8ZIuKY763ZoWFYwEO/vLV1QEMAQbNzQAAAQAB/y4B+wISABEAAAEDDgEjIic1FjMyNj8BAzMbAQH7 +2iFZQB8NDRIpNhQH02GenQIS/cFVUAJGASozEwIt/i4B0gABABQAAAGxAg4ACQAAJRUhNQEhNSEVA +QGx/mMBNP7iAYL+zkREQgGIREX+ewAAAAABAED/sAEOAwwALAAAASMiBhUUFxYVFAYHHgEVFAcGFR +QWOwEVIyImNTQ3NjU0JzU2NTQnJjU0NjsBAQ4MKiMLDS4pKS4NCyMqDAtERAwLUlILDERECwLUGBk +WTlsgKzUFBTcrIFtOFhkYOC87GFVMIkUIOAhFIkxVGDsvAAAAAAEAYP84AJoDIAADAAAXIxEzmjo6 +yAPoAAEAIf+wAO8DDAAsAAATFQYVFBcWFRQGKwE1MzI2NTQnJjU0NjcuATU0NzY1NCYrATUzMhYVF +AcGFRTvUgsMREQLDCojCw0uKSkuDQsjKgwLREQMCwF6OAhFIkxVGDsvOBgZFk5bICs1BQU3KyBbTh +YZGDgvOxhVTCJFAAABAE0A3wH2AWQAEwAAATMUIyImJyYjIhUjNDMyFhcWMzIBvjhuGywtQR0xOG4 +bLC1BHTEBZIURGCNMhREYIwAAAwAk/94DIgLoAAcAEQApAAAAIBYQBiAmECQgBhUUFiA2NTQlMhYX +IyYjIgYUFjMyNjczDgEjIiY1NDYBAQFE3d3+vN0CB/7wubkBELn+xVBnD1wSWDo+QTcqOQZcEmZWX +HN2Aujg/rbg4AFKpr+Mjb6+jYxbWEldV5ZZNShLVn5na34AAgB4AFIB9AGeAAUACwAAAQcXIyc3Mw +cXIyc3AUqJiUmJifOJiUmJiQGepqampqampqYAAAIAHAHSAQ4CwAAHAA8AABIyFhQGIiY0NiIGFBY +yNjRgakREakSTNCEhNCECwEJqQkJqCiM4IyM4AAAAAAIAUAAAAfQCCwALAA8AAAEzFSMVIzUjNTM1 +MxMhNSEBP7W1OrW1OrX+XAGkAVs4tLQ4sP31OAAAAQB0AkQBAQKyAAMAABMjNzOsOD1QAkRuAAAAA +AEAIADsAKoBdgAHAAASMhYUBiImNEg6KCg6KAF2KDooKDoAAAIAOQBSAbUBngAFAAsAACUHIzcnMw +UHIzcnMwELiUmJiUkBM4lJiYlJ+KampqampqYAAAABADYB5QDhAt8ABAAAEzczByM2Xk1OXQHv8Po +AAQAWAeUAwQLfAAQAABMHIzczwV5NTl0C1fD6AAIANgHlAYsC3wAEAAkAABM3MwcjPwEzByM2Xk1O +XapeTU5dAe/w+grw+gAAAgAWAeUBawLfAAQACQAAEwcjNzMXByM3M8FeTU5dql5NTl0C1fD6CvD6A +AADACX/8gI1AHIABwAPABcAADYyFhQGIiY0NjIWFAYiJjQ2MhYUBiImNEk4JCQ4JOw4JCQ4JOw4JC +Q4JHIkOCQkOCQkOCQkOCQkOCQkOAAAAAEAeABSAUoBngAFAAABBxcjJzcBSomJSYmJAZ6mpqamAAA +AAAEAOQBSAQsBngAFAAAlByM3JzMBC4lJiYlJ+KampgAAAf9qAAABgQKyAAMAACsBATM/VwHAVwKy +AAAAAAIAFAHIAdwClAAHABQAABMVIxUjNSM1BRUjNwcjJxcjNTMXN9pKMkoByDICKzQqATJLKysCl +CmjoykBy46KiY3Lm5sAAQAVAAABvALyABgAAAERIxEjESMRIzUzNTQ3NjMyFxUmBgcGHQEBvFbCVj +k5AxHHHx5iVgcDAg798gHM/jQBzEIOJRuWBUcIJDAVIRYAAAABABX//AHkAvIAJQAAJR4BNxUGIyI +mJyY1ESYjIgcGHQEzFSMRIxEjNTM1NDc2MzIXERQBowIcIxkkKi4CAR4nXgwDbW1WLy8DEbNdOmYa +EQQ/BCQvEjQCFQZWFSEWQv40AcxCDiUblhP9uSEAAAAAAAAWAQ4AAQAAAAAAAAATACgAAQAAAAAAA +QAHAEwAAQAAAAAAAgAHAGQAAQAAAAAAAwAaAKIAAQAAAAAABAAHAM0AAQAAAAAABQA8AU8AAQAAAA +AABgAPAawAAQAAAAAACAALAdQAAQAAAAAACQALAfgAAQAAAAAACwAXAjQAAQAAAAAADAAXAnwAAwA +BBAkAAAAmAAAAAwABBAkAAQAOADwAAwABBAkAAgAOAFQAAwABBAkAAwA0AGwAAwABBAkABAAOAL0A +AwABBAkABQB4ANUAAwABBAkABgAeAYwAAwABBAkACAAWAbwAAwABBAkACQAWAeAAAwABBAkACwAuA +gQAAwABBAkADAAuAkwATgBvACAAUgBpAGcAaAB0AHMAIABSAGUAcwBlAHIAdgBlAGQALgAATm8gUm +lnaHRzIFJlc2VydmVkLgAAQQBpAGwAZQByAG8AbgAAQWlsZXJvbgAAUgBlAGcAdQBsAGEAcgAAUmV +ndWxhcgAAMQAuADEAMAAyADsAVQBLAFcATgA7AEEAaQBsAGUAcgBvAG4ALQBSAGUAZwB1AGwAYQBy +AAAxLjEwMjtVS1dOO0FpbGVyb24tUmVndWxhcgAAQQBpAGwAZQByAG8AbgAAQWlsZXJvbgAAVgBlA +HIAcwBpAG8AbgAgADEALgAxADAAMgA7AFAAUwAgADAAMAAxAC4AMQAwADIAOwBoAG8AdABjAG8Abg +B2ACAAMQAuADAALgA3ADAAOwBtAGEAawBlAG8AdABmAC4AbABpAGIAMgAuADUALgA1ADgAMwAyADk +AAFZlcnNpb24gMS4xMDI7UFMgMDAxLjEwMjtob3Rjb252IDEuMC43MDttYWtlb3RmLmxpYjIuNS41 +ODMyOQAAQQBpAGwAZQByAG8AbgAtAFIAZQBnAHUAbABhAHIAAEFpbGVyb24tUmVndWxhcgAAUwBvA +HIAYQAgAFMAYQBnAGEAbgBvAABTb3JhIFNhZ2FubwAAUwBvAHIAYQAgAFMAYQBnAGEAbgBvAABTb3 +JhIFNhZ2FubwAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGQAbwB0AGMAbwBsAG8AbgAuAG4AZQB0AAB +odHRwOi8vd3d3LmRvdGNvbG9uLm5ldAAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGQAbwB0AGMAbwBs +AG8AbgAuAG4AZQB0AABodHRwOi8vd3d3LmRvdGNvbG9uLm5ldAAAAAACAAAAAAAA/4MAMgAAAAAAA +AAAAAAAAAAAAAAAAAAAAHQAAAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATAB +QAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAJAAlACYAJwAoACkAKgArACwALQAuAC8AMAA +xADIAMwA0ADUANgA3ADgAOQA6ADsAPAA9AD4APwBAAEEAQgBDAEQARQBGAEcASABJAEoASwBMAE0A +TgBPAFAAUQBSAFMAVABVAFYAVwBYAFkAWgBbAFwAXQBeAF8AYABhAIsAqQCDAJMAjQDDAKoAtgC3A +LQAtQCrAL4AvwC8AIwAwADBAAAAAAAB//8AAgABAAAADAAAABwAAAACAAIAAwBxAAEAcgBzAAIABA +AAAAIAAAABAAAACgBMAGYAAkRGTFQADmxhdG4AGgAEAAAAAP//AAEAAAAWAANDQVQgAB5NT0wgABZ +ST00gABYAAP//AAEAAAAA//8AAgAAAAEAAmxpZ2EADmxvY2wAFAAAAAEAAQAAAAEAAAACAAYAEAAG +AAAAAgASADQABAAAAAEATAADAAAAAgAQABYAAQAcAAAAAQABAE8AAQABAGcAAQABAE8AAwAAAAIAE +AAWAAEAHAAAAAEAAQAvAAEAAQBnAAEAAQAvAAEAGgABAAgAAgAGAAwAcwACAE8AcgACAEwAAQABAE +kAAAABAAAACgBGAGAAAkRGTFQADmxhdG4AHAAEAAAAAP//AAIAAAABABYAA0NBVCAAFk1PTCAAFlJ +PTSAAFgAA//8AAgAAAAEAAmNwc3AADmtlcm4AFAAAAAEAAAAAAAEAAQACAAYADgABAAAAAQASAAIA +AAACAB4ANgABAAoABQAFAAoAAgABACQAPQAAAAEAEgAEAAAAAQAMAAEAOP/nAAEAAQAkAAIGigAEA +AAFJAXKABoAGQAA//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAD/sv+4/+z/7v/MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAD/xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/9T/6AAAAAD/8QAA +ABD/vQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/7gAAAAAAAAAAAAAAAAAA//MAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIAAAAAAAAAAP/5AAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAD/4AAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//L/9AAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAA/+gAAAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/zAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/mAAAAAAAAAAAAAAAAAAD +/4gAA//AAAAAA//YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+AAAAAAAAP/OAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/zv/qAAAAAP/0AAAACAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/ZAAD/egAA/1kAAAAA/5D/rgAAAAAAAAAAAA +AAAAAAAAAAAAAAAAD/9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAD/8AAA/7b/8P+wAAD/8P/E/98AAAAA/8P/+P/0//oAAAAAAAAAAAAA//gA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/w//C/9MAAP/SAAD/9wAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAD/yAAA/+kAAAAA//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/9wAAAAD//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAP/2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAP/cAAAAAAAAAAAAAAAA/7YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAP/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/6AAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAkAFAAEAAAAAQACwAAABcA +BgAAAAAAAAAIAA4AAAAAAAsAEgAAAAAAAAATABkAAwANAAAAAQAJAAAAAAAAAAAAAAAAAAAAGAAAA +AAABwAAAAAAAAAAAAAAFQAFAAAAAAAYABgAAAAUAAAACgAAAAwAAgAPABEAFgAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAEAEQBdAAYAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAcAAAAAAAAABwAAAAAACAAAAAAAAAAAAAcAAAAHAAAAEwAJ +ABUADgAPAAAACwAQAAAAAAAAAAAAAAAAAAUAGAACAAIAAgAAAAIAGAAXAAAAGAAAABYAFgACABYAA +gAWAAAAEQADAAoAFAAMAA0ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAAAAEgAGAAEAHgAkAC +YAJwApACoALQAuAC8AMgAzADcAOAA5ADoAPAA9AEUASABOAE8AUgBTAFUAVwBZAFoAWwBcAF0AcwA +AAAAAAQAAAADa3tfFAAAAANAan9kAAAAA4QodoQ== +""" + ) + ), + 10 if size is None else size, + layout_engine=Layout.BASIC, + ) + return load_default_imagefont() diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageGrab.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageGrab.py new file mode 100644 index 0000000000000000000000000000000000000000..4228078b11b097fe0423aaf757158cbd1b96dd26 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageGrab.py @@ -0,0 +1,224 @@ +# +# The Python Imaging Library +# $Id$ +# +# screen grabber +# +# History: +# 2001-04-26 fl created +# 2001-09-17 fl use builtin driver, if present +# 2002-11-19 fl added grabclipboard support +# +# Copyright (c) 2001-2002 by Secret Labs AB +# Copyright (c) 2001-2002 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +import os +import shutil +import subprocess +import sys +import tempfile + +from . import Image + +TYPE_CHECKING = False +if TYPE_CHECKING: + from . import ImageWin + + +def grab( + bbox: tuple[int, int, int, int] | None = None, + include_layered_windows: bool = False, + all_screens: bool = False, + xdisplay: str | None = None, + window: int | ImageWin.HWND | None = None, +) -> Image.Image: + im: Image.Image + if xdisplay is None: + if sys.platform == "darwin": + fh, filepath = tempfile.mkstemp(".png") + os.close(fh) + args = ["screencapture"] + if window: + args += ["-l", str(window)] + elif bbox: + left, top, right, bottom = bbox + args += ["-R", f"{left},{top},{right-left},{bottom-top}"] + subprocess.call(args + ["-x", filepath]) + im = Image.open(filepath) + im.load() + os.unlink(filepath) + if bbox: + if window: + # Determine if the window was in Retina mode or not + # by capturing it without the shadow, + # and checking how different the width is + fh, filepath = tempfile.mkstemp(".png") + os.close(fh) + subprocess.call( + ["screencapture", "-l", str(window), "-o", "-x", filepath] + ) + with Image.open(filepath) as im_no_shadow: + retina = im.width - im_no_shadow.width > 100 + os.unlink(filepath) + + # Since screencapture's -R does not work with -l, + # crop the image manually + if retina: + left, top, right, bottom = bbox + im_cropped = im.resize( + (right - left, bottom - top), + box=tuple(coord * 2 for coord in bbox), + ) + else: + im_cropped = im.crop(bbox) + im.close() + return im_cropped + else: + im_resized = im.resize((right - left, bottom - top)) + im.close() + return im_resized + return im + elif sys.platform == "win32": + if window is not None: + all_screens = -1 + offset, size, data = Image.core.grabscreen_win32( + include_layered_windows, + all_screens, + int(window) if window is not None else 0, + ) + im = Image.frombytes( + "RGB", + size, + data, + # RGB, 32-bit line padding, origin lower left corner + "raw", + "BGR", + (size[0] * 3 + 3) & -4, + -1, + ) + if bbox: + x0, y0 = offset + left, top, right, bottom = bbox + im = im.crop((left - x0, top - y0, right - x0, bottom - y0)) + return im + # Cast to Optional[str] needed for Windows and macOS. + display_name: str | None = xdisplay + try: + if not Image.core.HAVE_XCB: + msg = "Pillow was built without XCB support" + raise OSError(msg) + size, data = Image.core.grabscreen_x11(display_name) + except OSError: + if display_name is None and sys.platform not in ("darwin", "win32"): + if shutil.which("gnome-screenshot"): + args = ["gnome-screenshot", "-f"] + elif shutil.which("grim"): + args = ["grim"] + elif shutil.which("spectacle"): + args = ["spectacle", "-n", "-b", "-f", "-o"] + else: + raise + fh, filepath = tempfile.mkstemp(".png") + os.close(fh) + subprocess.call(args + [filepath]) + im = Image.open(filepath) + im.load() + os.unlink(filepath) + if bbox: + im_cropped = im.crop(bbox) + im.close() + return im_cropped + return im + else: + raise + else: + im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1) + if bbox: + im = im.crop(bbox) + return im + + +def grabclipboard() -> Image.Image | list[str] | None: + if sys.platform == "darwin": + p = subprocess.run( + ["osascript", "-e", "get the clipboard as «class PNGf»"], + capture_output=True, + ) + if p.returncode != 0: + return None + + import binascii + + data = io.BytesIO(binascii.unhexlify(p.stdout[11:-3])) + return Image.open(data) + elif sys.platform == "win32": + fmt, data = Image.core.grabclipboard_win32() + if fmt == "file": # CF_HDROP + import struct + + o = struct.unpack_from("I", data)[0] + if data[16] == 0: + files = data[o:].decode("mbcs").split("\0") + else: + files = data[o:].decode("utf-16le").split("\0") + return files[: files.index("")] + if isinstance(data, bytes): + data = io.BytesIO(data) + if fmt == "png": + from . import PngImagePlugin + + return PngImagePlugin.PngImageFile(data) + elif fmt == "DIB": + from . import BmpImagePlugin + + return BmpImagePlugin.DibImageFile(data) + return None + else: + if os.getenv("WAYLAND_DISPLAY"): + session_type = "wayland" + elif os.getenv("DISPLAY"): + session_type = "x11" + else: # Session type check failed + session_type = None + + if shutil.which("wl-paste") and session_type in ("wayland", None): + args = ["wl-paste", "-t", "image"] + elif shutil.which("xclip") and session_type in ("x11", None): + args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"] + else: + msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux" + raise NotImplementedError(msg) + + p = subprocess.run(args, capture_output=True) + if p.returncode != 0: + err = p.stderr + for silent_error in [ + # wl-paste, when the clipboard is empty + b"Nothing is copied", + # Ubuntu/Debian wl-paste, when the clipboard is empty + b"No selection", + # Ubuntu/Debian wl-paste, when an image isn't available + b"No suitable type of content copied", + # wl-paste or Ubuntu/Debian xclip, when an image isn't available + b" not available", + # xclip, when an image isn't available + b"cannot convert ", + # xclip, when the clipboard isn't initialized + b"xclip: Error: There is no owner for the ", + ]: + if silent_error in err: + return None + msg = f"{args[0]} error" + if err: + msg += f": {err.strip().decode()}" + raise ChildProcessError(msg) + + data = io.BytesIO(p.stdout) + im = Image.open(data) + im.load() + return im diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageMath.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageMath.py new file mode 100644 index 0000000000000000000000000000000000000000..dfdc50c0552cc0a3d325aa6b5479acd90893c2de --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageMath.py @@ -0,0 +1,314 @@ +# +# The Python Imaging Library +# $Id$ +# +# a simple math add-on for the Python Imaging Library +# +# History: +# 1999-02-15 fl Original PIL Plus release +# 2005-05-05 fl Simplified and cleaned up for PIL 1.1.6 +# 2005-09-12 fl Fixed int() and float() for Python 2.4.1 +# +# Copyright (c) 1999-2005 by Secret Labs AB +# Copyright (c) 2005 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import builtins + +from . import Image, _imagingmath + +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Callable + from types import CodeType + from typing import Any + + +class _Operand: + """Wraps an image operand, providing standard operators""" + + def __init__(self, im: Image.Image): + self.im = im + + def __fixup(self, im1: _Operand | float) -> Image.Image: + # convert image to suitable mode + if isinstance(im1, _Operand): + # argument was an image. + if im1.im.mode in ("1", "L"): + return im1.im.convert("I") + elif im1.im.mode in ("I", "F"): + return im1.im + else: + msg = f"unsupported mode: {im1.im.mode}" + raise ValueError(msg) + else: + # argument was a constant + if isinstance(im1, (int, float)) and self.im.mode in ("1", "L", "I"): + return Image.new("I", self.im.size, im1) + else: + return Image.new("F", self.im.size, im1) + + def apply( + self, + op: str, + im1: _Operand | float, + im2: _Operand | float | None = None, + mode: str | None = None, + ) -> _Operand: + im_1 = self.__fixup(im1) + if im2 is None: + # unary operation + out = Image.new(mode or im_1.mode, im_1.size, None) + try: + op = getattr(_imagingmath, f"{op}_{im_1.mode}") + except AttributeError as e: + msg = f"bad operand type for '{op}'" + raise TypeError(msg) from e + _imagingmath.unop(op, out.getim(), im_1.getim()) + else: + # binary operation + im_2 = self.__fixup(im2) + if im_1.mode != im_2.mode: + # convert both arguments to floating point + if im_1.mode != "F": + im_1 = im_1.convert("F") + if im_2.mode != "F": + im_2 = im_2.convert("F") + if im_1.size != im_2.size: + # crop both arguments to a common size + size = ( + min(im_1.size[0], im_2.size[0]), + min(im_1.size[1], im_2.size[1]), + ) + if im_1.size != size: + im_1 = im_1.crop((0, 0) + size) + if im_2.size != size: + im_2 = im_2.crop((0, 0) + size) + out = Image.new(mode or im_1.mode, im_1.size, None) + try: + op = getattr(_imagingmath, f"{op}_{im_1.mode}") + except AttributeError as e: + msg = f"bad operand type for '{op}'" + raise TypeError(msg) from e + _imagingmath.binop(op, out.getim(), im_1.getim(), im_2.getim()) + return _Operand(out) + + # unary operators + def __bool__(self) -> bool: + # an image is "true" if it contains at least one non-zero pixel + return self.im.getbbox() is not None + + def __abs__(self) -> _Operand: + return self.apply("abs", self) + + def __pos__(self) -> _Operand: + return self + + def __neg__(self) -> _Operand: + return self.apply("neg", self) + + # binary operators + def __add__(self, other: _Operand | float) -> _Operand: + return self.apply("add", self, other) + + def __radd__(self, other: _Operand | float) -> _Operand: + return self.apply("add", other, self) + + def __sub__(self, other: _Operand | float) -> _Operand: + return self.apply("sub", self, other) + + def __rsub__(self, other: _Operand | float) -> _Operand: + return self.apply("sub", other, self) + + def __mul__(self, other: _Operand | float) -> _Operand: + return self.apply("mul", self, other) + + def __rmul__(self, other: _Operand | float) -> _Operand: + return self.apply("mul", other, self) + + def __truediv__(self, other: _Operand | float) -> _Operand: + return self.apply("div", self, other) + + def __rtruediv__(self, other: _Operand | float) -> _Operand: + return self.apply("div", other, self) + + def __mod__(self, other: _Operand | float) -> _Operand: + return self.apply("mod", self, other) + + def __rmod__(self, other: _Operand | float) -> _Operand: + return self.apply("mod", other, self) + + def __pow__(self, other: _Operand | float) -> _Operand: + return self.apply("pow", self, other) + + def __rpow__(self, other: _Operand | float) -> _Operand: + return self.apply("pow", other, self) + + # bitwise + def __invert__(self) -> _Operand: + return self.apply("invert", self) + + def __and__(self, other: _Operand | float) -> _Operand: + return self.apply("and", self, other) + + def __rand__(self, other: _Operand | float) -> _Operand: + return self.apply("and", other, self) + + def __or__(self, other: _Operand | float) -> _Operand: + return self.apply("or", self, other) + + def __ror__(self, other: _Operand | float) -> _Operand: + return self.apply("or", other, self) + + def __xor__(self, other: _Operand | float) -> _Operand: + return self.apply("xor", self, other) + + def __rxor__(self, other: _Operand | float) -> _Operand: + return self.apply("xor", other, self) + + def __lshift__(self, other: _Operand | float) -> _Operand: + return self.apply("lshift", self, other) + + def __rshift__(self, other: _Operand | float) -> _Operand: + return self.apply("rshift", self, other) + + # logical + def __eq__(self, other: _Operand | float) -> _Operand: # type: ignore[override] + return self.apply("eq", self, other) + + def __ne__(self, other: _Operand | float) -> _Operand: # type: ignore[override] + return self.apply("ne", self, other) + + def __lt__(self, other: _Operand | float) -> _Operand: + return self.apply("lt", self, other) + + def __le__(self, other: _Operand | float) -> _Operand: + return self.apply("le", self, other) + + def __gt__(self, other: _Operand | float) -> _Operand: + return self.apply("gt", self, other) + + def __ge__(self, other: _Operand | float) -> _Operand: + return self.apply("ge", self, other) + + +# conversions +def imagemath_int(self: _Operand) -> _Operand: + return _Operand(self.im.convert("I")) + + +def imagemath_float(self: _Operand) -> _Operand: + return _Operand(self.im.convert("F")) + + +# logical +def imagemath_equal(self: _Operand, other: _Operand | float | None) -> _Operand: + return self.apply("eq", self, other, mode="I") + + +def imagemath_notequal(self: _Operand, other: _Operand | float | None) -> _Operand: + return self.apply("ne", self, other, mode="I") + + +def imagemath_min(self: _Operand, other: _Operand | float | None) -> _Operand: + return self.apply("min", self, other) + + +def imagemath_max(self: _Operand, other: _Operand | float | None) -> _Operand: + return self.apply("max", self, other) + + +def imagemath_convert(self: _Operand, mode: str) -> _Operand: + return _Operand(self.im.convert(mode)) + + +ops = { + "int": imagemath_int, + "float": imagemath_float, + "equal": imagemath_equal, + "notequal": imagemath_notequal, + "min": imagemath_min, + "max": imagemath_max, + "convert": imagemath_convert, +} + + +def lambda_eval(expression: Callable[[dict[str, Any]], Any], **kw: Any) -> Any: + """ + Returns the result of an image function. + + :py:mod:`~PIL.ImageMath` only supports single-layer images. To process multi-band + images, use the :py:meth:`~PIL.Image.Image.split` method or + :py:func:`~PIL.Image.merge` function. + + :param expression: A function that receives a dictionary. + :param **kw: Values to add to the function's dictionary. + :return: The expression result. This is usually an image object, but can + also be an integer, a floating point value, or a pixel tuple, + depending on the expression. + """ + + args: dict[str, Any] = ops.copy() + args.update(kw) + for k, v in args.items(): + if isinstance(v, Image.Image): + args[k] = _Operand(v) + + out = expression(args) + try: + return out.im + except AttributeError: + return out + + +def unsafe_eval(expression: str, **kw: Any) -> Any: + """ + Evaluates an image expression. This uses Python's ``eval()`` function to process + the expression string, and carries the security risks of doing so. It is not + recommended to process expressions without considering this. + :py:meth:`~lambda_eval` is a more secure alternative. + + :py:mod:`~PIL.ImageMath` only supports single-layer images. To process multi-band + images, use the :py:meth:`~PIL.Image.Image.split` method or + :py:func:`~PIL.Image.merge` function. + + :param expression: A string containing a Python-style expression. + :param **kw: Values to add to the evaluation context. + :return: The evaluated expression. This is usually an image object, but can + also be an integer, a floating point value, or a pixel tuple, + depending on the expression. + """ + + # build execution namespace + args: dict[str, Any] = ops.copy() + for k in kw: + if "__" in k or hasattr(builtins, k): + msg = f"'{k}' not allowed" + raise ValueError(msg) + + args.update(kw) + for k, v in args.items(): + if isinstance(v, Image.Image): + args[k] = _Operand(v) + + compiled_code = compile(expression, "", "eval") + + def scan(code: CodeType) -> None: + for const in code.co_consts: + if type(const) is type(compiled_code): + scan(const) + + for name in code.co_names: + if name not in args and name != "abs": + msg = f"'{name}' not allowed" + raise ValueError(msg) + + scan(compiled_code) + out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args) + try: + return out.im + except AttributeError: + return out diff --git a/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageMode.py b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageMode.py new file mode 100644 index 0000000000000000000000000000000000000000..b7c6c863659b93416cac4acd8d8d31587566a9e6 --- /dev/null +++ b/workspace/outputs/audit_venv/lib/python3.11/site-packages/PIL/ImageMode.py @@ -0,0 +1,85 @@ +# +# The Python Imaging Library. +# $Id$ +# +# standard mode descriptors +# +# History: +# 2006-03-20 fl Added +# +# Copyright (c) 2006 by Secret Labs AB. +# Copyright (c) 2006 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import sys +from functools import lru_cache +from typing import NamedTuple + + +class ModeDescriptor(NamedTuple): + """Wrapper for mode strings.""" + + mode: str + bands: tuple[str, ...] + basemode: str + basetype: str + typestr: str + + def __str__(self) -> str: + return self.mode + + +@lru_cache +def getmode(mode: str) -> ModeDescriptor: + """Gets a mode descriptor for the given mode.""" + endian = "<" if sys.byteorder == "little" else ">" + + modes = { + # core modes + # Bits need to be extended to bytes + "1": ("L", "L", ("1",), "|b1"), + "L": ("L", "L", ("L",), "|u1"), + "I": ("L", "I", ("I",), f"{endian}i4"), + "F": ("L", "F", ("F",), f"{endian}f4"), + "P": ("P", "L", ("P",), "|u1"), + "RGB": ("RGB", "L", ("R", "G", "B"), "|u1"), + "RGBX": ("RGB", "L", ("R", "G", "B", "X"), "|u1"), + "RGBA": ("RGB", "L", ("R", "G", "B", "A"), "|u1"), + "CMYK": ("RGB", "L", ("C", "M", "Y", "K"), "|u1"), + "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr"), "|u1"), + # UNDONE - unsigned |u1i1i1 + "LAB": ("RGB", "L", ("L", "A", "B"), "|u1"), + "HSV": ("RGB", "L", ("H", "S", "V"), "|u1"), + # extra experimental modes + "RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"), + "LA": ("L", "L", ("L", "A"), "|u1"), + "La": ("L", "L", ("L", "a"), "|u1"), + "PA": ("RGB", "L", ("P", "A"), "|u1"), + } + if mode in modes: + base_mode, base_type, bands, type_str = modes[mode] + return ModeDescriptor(mode, bands, base_mode, base_type, type_str) + + mapping_modes = { + # I;16 == I;16L, and I;32 == I;32L + "I;16": "u2", + "I;16BS": ">i2", + "I;16N": f"{endian}u2", + "I;16NS": f"{endian}i2", + "I;32": "u4", + "I;32L": "i4", + "I;32LS": " +from __future__ import annotations + +import re + +from . import Image, _imagingmorph + +LUT_SIZE = 1 << 9 + +# fmt: off +ROTATION_MATRIX = [ + 6, 3, 0, + 7, 4, 1, + 8, 5, 2, +] +MIRROR_MATRIX = [ + 2, 1, 0, + 5, 4, 3, + 8, 7, 6, +] +# fmt: on + + +class LutBuilder: + """A class for building a MorphLut from a descriptive language + + The input patterns is a list of a strings sequences like these:: + + 4:(... + .1. + 111)->1 + + (whitespaces including linebreaks are ignored). The option 4 + describes a series of symmetry operations (in this case a + 4-rotation), the pattern is described by: + + - . or X - Ignore + - 1 - Pixel is on + - 0 - Pixel is off + + The result of the operation is described after "->" string. + + The default is to return the current pixel value, which is + returned if no other match is found. + + Operations: + + - 4 - 4 way rotation + - N - Negate + - 1 - Dummy op for no other operation (an op must always be given) + - M - Mirroring + + Example:: + + lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) + lut = lb.build_lut() + + """ + + def __init__( + self, patterns: list[str] | None = None, op_name: str | None = None + ) -> None: + """ + :param patterns: A list of input patterns, or None. + :param op_name: The name of a known pattern. One of "corner", "dilation4", + "dilation8", "erosion4", "erosion8" or "edge". + :exception Exception: If the op_name is not recognized. + """ + self.lut: bytearray | None = None + if op_name is not None: + known_patterns = { + "corner": ["1:(... ... ...)->0", "4:(00. 01. ...)->1"], + "dilation4": ["4:(... .0. .1.)->1"], + "dilation8": ["4:(... .0. .1.)->1", "4:(... .0. ..1)->1"], + "erosion4": ["4:(... .1. .0.)->0"], + "erosion8": ["4:(... .1. .0.)->0", "4:(... .1. ..0)->0"], + "edge": [ + "1:(... ... ...)->0", + "4:(.0. .1. ...)->1", + "4:(01. .1. ...)->1", + ], + } + if op_name not in known_patterns: + msg = f"Unknown pattern {op_name}!" + raise Exception(msg) + + self.patterns = known_patterns[op_name] + elif patterns is not None: + self.patterns = patterns + else: + self.patterns = [] + + def add_patterns(self, patterns: list[str]) -> None: + """ + Append to list of patterns. + + :param patterns: Additional patterns. + """ + self.patterns += patterns + + def build_default_lut(self) -> bytearray: + """ + Set the current LUT, and return it. + + This is the default LUT that patterns will be applied against when building. + """ + symbols = [0, 1] + m = 1 << 4 # pos of current pixel + self.lut = bytearray(symbols[(i & m) > 0] for i in range(LUT_SIZE)) + return self.lut + + def get_lut(self) -> bytearray | None: + """ + Returns the current LUT + """ + return self.lut + + def _string_permute(self, pattern: str, permutation: list[int]) -> str: + """Takes a pattern and a permutation and returns the + string permuted according to the permutation list. + """ + assert len(permutation) == 9 + return "".join(pattern[p] for p in permutation) + + def _pattern_permute( + self, basic_pattern: str, options: str, basic_result: int + ) -> list[tuple[str, int]]: + """Takes a basic pattern and its result and clones + the pattern according to the modifications described in the $options + parameter. It returns a list of all cloned patterns.""" + patterns = [(basic_pattern, basic_result)] + + # rotations + if "4" in options: + res = patterns[-1][1] + for i in range(4): + patterns.append( + (self._string_permute(patterns[-1][0], ROTATION_MATRIX), res) + ) + # mirror + if "M" in options: + n = len(patterns) + for pattern, res in patterns[:n]: + patterns.append((self._string_permute(pattern, MIRROR_MATRIX), res)) + + # negate + if "N" in options: + n = len(patterns) + for pattern, res in patterns[:n]: + # Swap 0 and 1 + pattern = pattern.replace("0", "Z").replace("1", "0").replace("Z", "1") + res = 1 - int(res) + patterns.append((pattern, res)) + + return patterns + + def build_lut(self) -> bytearray: + """Compile all patterns into a morphology LUT, and return it. + + This is the data to be passed into MorphOp.""" + self.build_default_lut() + assert self.lut is not None + patterns = [] + + # Parse and create symmetries of the patterns strings + for p in self.patterns: + m = re.search(r"(\w):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", "")) + if not m: + msg = 'Syntax error in pattern "' + p + '"' + raise Exception(msg) + options = m.group(1) + pattern = m.group(2) + result = int(m.group(3)) + + # Get rid of spaces + pattern = pattern.replace(" ", "").replace("\n", "") + + patterns += self._pattern_permute(pattern, options, result) + + # Compile the patterns into regular expressions for speed + compiled_patterns = [] + for pattern in patterns: + p = pattern[0].replace(".", "X").replace("X", "[01]") + compiled_patterns.append((re.compile(p), pattern[1])) + + # Step through table and find patterns that match. + # Note that all the patterns are searched. The last one found takes priority + for i in range(LUT_SIZE): + # Build the bit pattern + bitpattern = bin(i)[2:] + bitpattern = ("0" * (9 - len(bitpattern)) + bitpattern)[::-1] + + for pattern, r in compiled_patterns: + if pattern.match(bitpattern): + self.lut[i] = [0, 1][r] + + return self.lut + + +class MorphOp: + """A class for binary morphological operators""" + + def __init__( + self, + lut: bytearray | None = None, + op_name: str | None = None, + patterns: list[str] | None = None, + ) -> None: + """Create a binary morphological operator. + + If the LUT is not provided, then it is built using LutBuilder from the op_name + or the patterns. + + :param lut: The LUT data. + :param patterns: A list of input patterns, or None. + :param op_name: The name of a known pattern. One of "corner", "dilation4", + "dilation8", "erosion4", "erosion8", "edge". + :exception Exception: If the op_name is not recognized. + """ + if patterns is None and op_name is None: + self.lut = lut + else: + self.lut = LutBuilder(patterns, op_name).build_lut() + + def apply(self, image: Image.Image) -> tuple[int, Image.Image]: + """Run a single morphological operation on an image. + + Returns a tuple of the number of changed pixels and the + morphed image. + + :param image: A 1-mode or L-mode image. + :exception Exception: If the current operator is None. + :exception ValueError: If the image is not 1 or L mode.""" + if self.lut is None: + msg = "No operator loaded" + raise Exception(msg) + + if image.mode not in ("1", "L"): + msg = "Image mode must be 1 or L" + raise ValueError(msg) + outimage = Image.new(image.mode, image.size) + count = _imagingmorph.apply(bytes(self.lut), image.getim(), outimage.getim()) + return count, outimage + + def match(self, image: Image.Image) -> list[tuple[int, int]]: + """Get a list of coordinates matching the morphological operation on + an image. + + Returns a list of tuples of (x,y) coordinates of all matching pixels. See + :ref:`coordinate-system`. + + :param image: A 1-mode or L-mode image. + :exception Exception: If the current operator is None. + :exception ValueError: If the image is not 1 or L mode.""" + if self.lut is None: + msg = "No operator loaded" + raise Exception(msg) + + if image.mode not in ("1", "L"): + msg = "Image mode must be 1 or L" + raise ValueError(msg) + return _imagingmorph.match(bytes(self.lut), image.getim()) + + def get_on_pixels(self, image: Image.Image) -> list[tuple[int, int]]: + """Get a list of all turned on pixels in a 1 or L mode image. + + Returns a list of tuples of (x,y) coordinates of all non-empty pixels. See + :ref:`coordinate-system`. + + :param image: A 1-mode or L-mode image. + :exception ValueError: If the image is not 1 or L mode.""" + + if image.mode not in ("1", "L"): + msg = "Image mode must be 1 or L" + raise ValueError(msg) + return _imagingmorph.get_on_pixels(image.getim()) + + def load_lut(self, filename: str) -> None: + """ + Load an operator from an mrl file + + :param filename: The file to read from. + :exception Exception: If the length of the file data is not 512. + """ + with open(filename, "rb") as f: + self.lut = bytearray(f.read()) + + if len(self.lut) != LUT_SIZE: + self.lut = None + msg = "Wrong size operator file!" + raise Exception(msg) + + def save_lut(self, filename: str) -> None: + """ + Save an operator to an mrl file. + + :param filename: The destination file. + :exception Exception: If the current operator is None. + """ + if self.lut is None: + msg = "No operator loaded" + raise Exception(msg) + with open(filename, "wb") as f: + f.write(self.lut) + + def set_lut(self, lut: bytearray | None) -> None: + """ + Set the LUT from an external source + + :param lut: A new LUT. + """ + self.lut = lut