stereoid commited on
Commit
3f3265f
·
verified ·
1 Parent(s): 285b75b

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. .env.example +31 -0
  2. .gitattributes +1 -35
  3. .gitignore +69 -0
  4. CITATION.cff +69 -0
  5. PIPELINE_README.md +224 -0
  6. README.md +245 -0
  7. approach/__init__.py +0 -0
  8. approach/app_metadata.py +95 -0
  9. approach/config.py +156 -0
  10. approach/method_gemini.py +655 -0
  11. approach/providers.py +149 -0
  12. approach/reflection.py +227 -0
  13. approach/requirements.txt +79 -0
  14. approach/run_vlm.py +164 -0
  15. dataset/2detection.py +21 -0
  16. dataset/2polyseg.py +52 -0
  17. dataset/clean.py +55 -0
  18. dataset/create_test_set_folder.py +39 -0
  19. dataset/dataset_process.sh +32 -0
  20. dataset/fold_app.csv +1553 -0
  21. dataset/fold_cat.csv +1553 -0
  22. dataset/fold_genre.csv +1553 -0
  23. dataset/gen_cat_fold.py +58 -0
  24. dataset/get_union_test.py +52 -0
  25. dataset/get_val_gt.py +66 -0
  26. dataset/interactable_coco.py +19 -0
  27. dataset/list-union3.txt +1 -0
  28. dataset/list_union3.txt +1 -0
  29. dataset/split_coco.py +166 -0
  30. environment.reference.yml +35 -0
  31. requirements.txt +5 -0
  32. tests/test_ape_stage.py +176 -0
  33. tests/test_app_metadata_cache.py +124 -0
  34. tests/test_cli_entrypoints.py +147 -0
  35. tests/test_config_providers.py +131 -0
  36. tests/test_evaluate_temp_paths.py +109 -0
  37. tests/test_evaluation_config.py +26 -0
  38. tests/test_evaluation_tools.py +145 -0
  39. tests/test_merge_predictions.py +62 -0
  40. tests/test_paired_bootstrap.py +233 -0
  41. tests/test_pipeline_utils.py +123 -0
  42. tests/test_reflection.py +147 -0
  43. tests/test_release_hygiene.py +151 -0
  44. tests/test_release_scripts.py +153 -0
  45. tests/test_run_ape.py +105 -0
  46. tests/test_run_vlm.py +83 -0
  47. tests/test_semantic_matching_cache.py +121 -0
  48. tests/test_vlm_adapter.py +52 -0
  49. util/llm_gemini.py +14 -0
  50. util/llm_third_party.py +60 -0
.env.example ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Example variable names only. Do not put real credentials in this file.
2
+ # In this workspace, use openrouter-run for OpenRouter operations.
3
+
4
+ # Main/legacy provider paths
5
+ # OPENROUTER_API_KEY=<inject through a secret manager or process environment>
6
+ OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
7
+ OPENROUTER_MODEL=openai/gpt-5.6-sol
8
+ ORIENT_MODEL_PROFILE=default
9
+ # OPENAI_API_KEY=
10
+ # GOOGLE_API_KEY=
11
+ # ANTHROPIC_API_KEY=
12
+ # ZHIPU_API_KEY=
13
+ ORIENTER_APP_METADATA_CACHE=
14
+
15
+ # Semantic evaluation cache. Keep offline/read-only for historical reproduction.
16
+ # Set ZHIPU_API_KEY only when intentionally expanding the cache for new labels.
17
+ ORIENTER_EMBEDDING_CACHE=
18
+ ORIENTER_EMBEDDING_OFFLINE=1
19
+ ORIENTER_EMBEDDING_READONLY=1
20
+
21
+ # Optional baseline/provider paths
22
+ # ARK_API_KEY=
23
+ # DASHSCOPE_API_KEY=
24
+ # DEEPSEEK_API_KEY=
25
+ # THEB_API_KEY=
26
+ # INTERNVL_API_KEY=
27
+ CLAUDE_BASELINE_MODEL=anthropic/claude-sonnet-4.5
28
+ GPT4O_BASELINE_MODEL=openai/gpt-4o-2024-08-06
29
+ GPT5_BASELINE_MODEL=openai/gpt-5.6-sol
30
+ GEMINI_BASELINE_MODEL=google/gemini-2.5-pro
31
+ D3_DATASET_ROOT=
.gitattributes CHANGED
@@ -1,35 +1 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ approach/ovod/APE/ape_d_model_final.pth filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Credentials and local configuration
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+ AGENTS.md
6
+ *.pem
7
+ *.key
8
+
9
+ # Python/editor state
10
+ __pycache__/
11
+ *.py[cod]
12
+ .pytest_cache/
13
+ .mypy_cache/
14
+ .ipynb_checkpoints/
15
+ .vscode/
16
+ .idea/
17
+ .DS_Store
18
+ .nfs*
19
+
20
+ # Virtual environments and package/build output
21
+ .venv/
22
+ venv/
23
+ env/
24
+ build/
25
+ dist/
26
+ *.egg-info/
27
+
28
+ # Logs, caches, and scratch output
29
+ *.log
30
+ *.tmp
31
+ *.bak
32
+ .cache/
33
+ log/
34
+ logs/
35
+ fastimg/
36
+ wandb/
37
+ work_dirs/
38
+ **/output/
39
+ **/outputs/
40
+ **/runs/
41
+
42
+ # Model and archive payloads
43
+ *.pt
44
+ *.pth
45
+ !approach/ovod/APE/ape_d_model_final.pth
46
+ *.ckpt
47
+ *.onnx
48
+ *.bin
49
+ *.pkl
50
+ *.zip
51
+ *.7z
52
+ *.tar
53
+ *.tar.gz
54
+
55
+ # Data payloads (keep scripts and small split metadata in Git)
56
+ /data/
57
+ dataset/data/
58
+
59
+ # Generated model/API and evaluation artifacts
60
+ approach/vlm/*.jsonl
61
+ approach/llm/*.json
62
+ approach/ovod/*.json
63
+ evaluation/embedding_dict*.json
64
+ evaluation/results/
65
+ evaluation/eval_results/
66
+ evaluation/autotest_study/results*/
67
+ evaluation/eval_context/results/
68
+ evaluation/eval_latex/**/*.csv
69
+ evaluation/eval_latex/**/*.tex
CITATION.cff ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cff-version: 1.2.0
2
+ message: "If you use Orienter, please cite the accompanying paper."
3
+ title: "Orienter: Context-Sensitive GUI Grounding for Automated XR Testing"
4
+ type: software
5
+ authors:
6
+ - family-names: Li
7
+ given-names: Shuqing
8
+ orcid: "https://orcid.org/0000-0001-6323-1402"
9
+ - family-names: Li
10
+ given-names: Binchang
11
+ orcid: "https://orcid.org/0009-0008-5995-4040"
12
+ - family-names: Liu
13
+ given-names: Yepang
14
+ orcid: "https://orcid.org/0000-0001-8147-8126"
15
+ - family-names: Gao
16
+ given-names: Cuiyun
17
+ orcid: "https://orcid.org/0000-0003-4774-2434"
18
+ - family-names: Zhang
19
+ given-names: Jianping
20
+ orcid: "https://orcid.org/0009-0005-5978-8888"
21
+ - family-names: Cheung
22
+ given-names: Shing-Chi
23
+ orcid: "https://orcid.org/0000-0002-3508-7172"
24
+ - family-names: Lyu
25
+ given-names: Michael R.
26
+ orcid: "https://orcid.org/0000-0002-3666-5798"
27
+ abstract: >-
28
+ Orienter grounds context-sensitive, user-interactable GUI elements in
29
+ extended-reality screenshots for automated XR testing.
30
+ keywords:
31
+ - extended reality
32
+ - GUI grounding
33
+ - software testing
34
+ - vision-language models
35
+ preferred-citation:
36
+ type: article
37
+ title: "Look Before You Leap: Context-Sensitive GUI Grounding for Boosting Automated Extended Reality (XR) Testing"
38
+ authors:
39
+ - family-names: Li
40
+ given-names: Shuqing
41
+ orcid: "https://orcid.org/0000-0001-6323-1402"
42
+ - family-names: Li
43
+ given-names: Binchang
44
+ orcid: "https://orcid.org/0009-0008-5995-4040"
45
+ - family-names: Liu
46
+ given-names: Yepang
47
+ orcid: "https://orcid.org/0000-0001-8147-8126"
48
+ - family-names: Gao
49
+ given-names: Cuiyun
50
+ orcid: "https://orcid.org/0000-0003-4774-2434"
51
+ - family-names: Zhang
52
+ given-names: Jianping
53
+ orcid: "https://orcid.org/0009-0005-5978-8888"
54
+ - family-names: Cheung
55
+ given-names: Shing-Chi
56
+ orcid: "https://orcid.org/0000-0002-3508-7172"
57
+ - family-names: Lyu
58
+ given-names: Michael R.
59
+ orcid: "https://orcid.org/0000-0002-3666-5798"
60
+ journal: "Proceedings of the ACM on Software Engineering"
61
+ volume: 3
62
+ issue: FSE
63
+ number: FSE127
64
+ start: 2858
65
+ end: 2881
66
+ month: 6
67
+ year: 2026
68
+ doi: "10.1145/3808134"
69
+ url: "https://doi.org/10.1145/3808134"
PIPELINE_README.md ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Orienter pipeline guide
2
+
3
+ This guide maps the public commands to the method described in the paper. It distinguishes the configurable release path from historical experiment scripts and precomputed artifacts.
4
+
5
+ ## Data contracts
6
+
7
+ The pipeline uses three small JSON formats.
8
+
9
+ Question manifest (`questions.jsonl`):
10
+
11
+ ```json
12
+ {"question_id": 0, "image": "123_4.jpg", "image_id": 123004, "text": "Identify interactable elements."}
13
+ ```
14
+
15
+ Candidate output (`candidates.jsonl`):
16
+
17
+ ```json
18
+ {"question_id": 0, "text": {"objects": {"red button": "round red button with white text"}}, "model_id": "openai/gpt-5.6-sol"}
19
+ ```
20
+
21
+ Detection output (`predictions.json`):
22
+
23
+ ```json
24
+ [{"image_id": 123004, "category_id": "red button", "category_name": "red button", "bbox": [10, 20, 30, 40], "score": 0.91}]
25
+ ```
26
+
27
+ Bounding boxes use COCO `x, y, width, height`. `image_id` is derived from `<app_id>_<frame>.ext` by zero-padding a frame in `[0, 999]` to three digits. Filenames must contain exactly one underscore between numeric app ID and numeric frame ID.
28
+
29
+ Keep all mounted inputs and generated artifacts outside the repository, then create the question manifest:
30
+
31
+ ```bash
32
+ export ORIENTER_DATA_DIR=/absolute/path/to/orienter_data
33
+ export ORIENTER_RUN_DIR=/absolute/path/to/orienter_run
34
+ mkdir -p "$ORIENTER_RUN_DIR"
35
+
36
+ python -B scripts/generate_questions.py \
37
+ --images-dir "$ORIENTER_DATA_DIR/images" \
38
+ --output "$ORIENTER_RUN_DIR/questions.jsonl" \
39
+ --prompt "Identify interactable elements." \
40
+ --metadata-template "$ORIENTER_RUN_DIR/app_metadata.json"
41
+ ```
42
+
43
+ Screenshot filenames must match `<numeric_app_id>_<numeric_frame>.<supported_ext>` and use a supported image extension. The manifest is sorted by derived `image_id` so repeated generation is deterministic. Fill the generated metadata template with a non-empty app name and description for every app ID before Stage 1.
44
+
45
+ ## Stage 0: external assets
46
+
47
+ Before a live run, provide:
48
+
49
+ - XR screenshots and a matching question manifest;
50
+ - application metadata reachable from Steam, or a pinned JSON/JSONL metadata cache;
51
+ - the APE-L\(_D\) repository environment (the checkpoint itself is bundled);
52
+ - an OpenRouter credential for the selected LMM profile;
53
+ - the bundled ground-truth split matching the experiment, plus a reviewed semantic-embedding cache. Credentials are needed only when intentionally adding embeddings for previously unseen labels.
54
+
55
+ The repository includes the main checkpoint and selected evaluation ground truth. It intentionally excludes the screenshot payload, private context data, generated predictions, experiment results, and the semantic embedding cache. Exact requirements are listed in `docs/ASSETS.md`.
56
+
57
+ ## Stage 1: context-sensitive candidate mining
58
+
59
+ Run:
60
+
61
+ ```bash
62
+ openrouter-run python -B -m approach.run_vlm \
63
+ --questions "$ORIENTER_RUN_DIR/questions.jsonl" \
64
+ --images-dir "$ORIENTER_DATA_DIR/images" \
65
+ --output "$ORIENTER_RUN_DIR/candidates.jsonl" \
66
+ --profile default \
67
+ --app-metadata-cache "$ORIENTER_RUN_DIR/app_metadata.json"
68
+ ```
69
+
70
+ `approach/run_vlm.py` selects the configured model profile, loads each screenshot, combines screenshot and application context with the research prompt, and atomically checkpoints JSONL responses. It accepts:
71
+
72
+ - `--start-index/--end-index` for a contiguous range;
73
+ - `--shard-index/--num-shards` for deterministic modulo sharding;
74
+ - `--resume` for completed `question_id` values;
75
+ - `--app-metadata-cache` for a pinned JSON/JSONL app metadata cache;
76
+ - `--ablation` for the historical no-interactability prompt.
77
+
78
+ Range and shard modes cannot be combined. Every selected output path receives a range/shard suffix.
79
+
80
+ The default profile uses OpenRouter. The optional paper profiles preserve the evaluated identifiers for GPT-4o-2024-08-06, Claude 3.5 Sonnet, and Gemini 1.5 Pro. Because provider catalogs can retire historical routes, archive the resolved provider/model metadata with every reproduction; if a paper route is unavailable, supply a custom YAML/JSON profile or direct vendor adapter and document the substitution.
81
+
82
+ For reproducible runs, provide `--app-metadata-cache` or set `ORIENTER_APP_METADATA_CACHE`. The cache may be a JSON object keyed by app ID or JSONL records with `app_id`, `app_name`/`name`/`title`, and `app_description`/`description`. When this option is set, duplicate or missing app IDs fail before the provider call and the runner does not fall back to live Steam pages.
83
+
84
+ ## Stage 2: APE-L_D visual grounding
85
+
86
+ Run:
87
+
88
+ ```bash
89
+ python -B -m approach.run_ape \
90
+ --questions "$ORIENTER_RUN_DIR/questions.jsonl" \
91
+ --candidates "$ORIENTER_RUN_DIR/candidates.jsonl" \
92
+ --images-dir "$ORIENTER_DATA_DIR/images" \
93
+ --output "$ORIENTER_RUN_DIR/predictions.json" \
94
+ --visualization-dir "$ORIENTER_RUN_DIR/ape_visualizations" \
95
+ --ape-root approach/ovod/APE \
96
+ --ape-config configs/LVISCOCOCOCOSTUFF_O365_OID_VGR_SA1B_REFCOCO_GQA_PhraseCut_Flickr30k/ape_deta/ape_deta_vitl_eva02_clip_vlf_lsj1024_cp_16x4_1080k.py \
97
+ --ape-checkpoint ape_d_model_final.pth \
98
+ --threshold 0.15
99
+ ```
100
+
101
+ The runner:
102
+
103
+ 1. accepts candidate `text` as a JSON object or JSON string;
104
+ 2. converts object descriptions to APE referring expressions;
105
+ 3. invokes APE once per selected screenshot;
106
+ 4. enriches every result with `image_id` and `category_id`;
107
+ 5. writes one atomic JSON document plus a progress sidecar;
108
+ 6. records per-image failures separately without corrupting successful predictions.
109
+
110
+ This fixes the historical failure mode where `all_ape_res` was cleared inside the loop, multiple JSON documents were dumped into one file, and the raw `ape_item` was appended instead of the enriched item.
111
+
112
+ ## PII.5–PII.7 reflection
113
+
114
+ Enable reflection with:
115
+
116
+ ```bash
117
+ openrouter-run python -B -m approach.run_ape ... \
118
+ --enable-reflection \
119
+ --reflection-profile default \
120
+ --max-reflection-iterations 10
121
+ ```
122
+
123
+ The implementation follows the paper:
124
+
125
+ - **PII.5, mirroring-based regional verification:** crop each detected element inside its bbox and send the original scene plus crop as two separate images. The crop keeps its original pixel dimensions and orientation; it is not enlarged, resized, or flipped.
126
+ - **PII.6, unsuccessful-detection reflection:** send the full scene with current boxes so the advisor can identify missed elements and incorrect regions.
127
+ - **PII.7, advisor feedback:** require structured `verified`, `needs_refinement`, and `feedback` fields. False positives listed in `needs_refinement` are removed; verified detections are retained; feedback descriptions are grounded again by APE.
128
+
129
+ The loop stops when no refinement or feedback remains, or after at most 10 advisor rounds by default. Reflection is opt-in because it incurs additional API cost. If the advisor call fails in the configurable runner, the original APE detections are preserved and the reflection error is recorded.
130
+
131
+ ## Parallel shards and validated merge
132
+
133
+ Example four-way detection:
134
+
135
+ ```bash
136
+ for shard in 0 1 2 3; do
137
+ python -B -m approach.run_ape ... --num-shards 4 --shard-index "$shard"
138
+ done
139
+ ```
140
+
141
+ Each output is named `.shardNN-of-NN.json`; each progress sidecar also includes screenshots with zero detections. Merge with:
142
+
143
+ ```bash
144
+ python -B -m evaluation.tools.merge_predictions \
145
+ --inputs "$ORIENTER_RUN_DIR/predictions.shard00-of-04.json" \
146
+ "$ORIENTER_RUN_DIR/predictions.shard01-of-04.json" \
147
+ "$ORIENTER_RUN_DIR/predictions.shard02-of-04.json" \
148
+ "$ORIENTER_RUN_DIR/predictions.shard03-of-04.json" \
149
+ --questions "$ORIENTER_RUN_DIR/questions.jsonl" \
150
+ --output "$ORIENTER_RUN_DIR/predictions.merged.json"
151
+ ```
152
+
153
+ The merge fails closed on overlapping completed images, duplicate detections, missing progress files, and missing expected images. `--allow-incomplete` exists for deliberate partial diagnostics.
154
+
155
+ ## Stage 3: task conversion and evaluation
156
+
157
+ Convert the merged list explicitly:
158
+
159
+ ```bash
160
+ python -B -m evaluation.tools.to_pred \
161
+ --task all \
162
+ --input "$ORIENTER_RUN_DIR/predictions.merged.json" \
163
+ --output "$ORIENTER_RUN_DIR/predictions.json"
164
+ ```
165
+
166
+ This produces separate `semantics`, `interactable`, and `interaction` files. The task can no longer be changed accidentally by two consecutive assignments in source code.
167
+
168
+ The remaining evaluation sequence is:
169
+
170
+ 1. `evaluation/filter_box.py` applies NMS and geometric filtering;
171
+ 2. `evaluation/filter_test_prediction.py` selects the requested split;
172
+ 3. `evaluation/evaluate_coco.py` computes modified COCO metrics;
173
+ 4. `evaluation/context_eval.py` performs context-sensitive semantic matching;
174
+ 5. `evaluation/autotest_study/` performs the RQ3 interaction simulation.
175
+
176
+ The historical shell scripts retain paper experiment names, but their data stems depend on external prediction/GT assets. Prefer explicit Python tool arguments for new runs.
177
+
178
+ ### Offline semantic evaluation
179
+
180
+ Semantic evaluation can run without an API call by mounting the reviewed historical cache described in `evaluation/cache_manifest.json`:
181
+
182
+ ```bash
183
+ export ORIENTER_EMBEDDING_CACHE=/absolute/path/to/embedding_dict.json
184
+ export ORIENTER_EMBEDDING_OFFLINE=1
185
+ export ORIENTER_EMBEDDING_READONLY=1
186
+ unset ZHIPU_API_KEY
187
+
188
+ python -B evaluation/evaluate_coco.py \
189
+ -gt /absolute/path/to/semantics_test.json \
190
+ -dt /absolute/path/to/predictions.json \
191
+ -d s -s -l "$ORIENTER_RUN_DIR/semantic_eval.csv"
192
+ ```
193
+
194
+ Offline mode fails before any provider call when a category is absent from the cache. Keep the reviewed cache read-only. If a genuinely new model introduces an uncached label, expand a separate working copy deliberately with `ZHIPU_API_KEY`, review it, record a new checksum, and then freeze that new version.
195
+
196
+ Verify the mounted historical cache against the frozen manifest before evaluation:
197
+
198
+ ```bash
199
+ python -B scripts/verify_assets.py \
200
+ --repo-root . \
201
+ --skip-checkpoint \
202
+ --embedding-cache /absolute/path/to/embedding_dict.json
203
+ ```
204
+
205
+ The legacy `make_sample_gt.py` path keeps only image IDs present in a method's prediction file. It is retained for historical protocol compatibility, but evaluates different methods on different GT subsets. New comparisons must score every method against the same complete GT and report prediction coverage. Historical predictions, generated CSVs, and validation summaries are not bundled.
206
+
207
+ ## Legacy experiment provenance
208
+
209
+ `approach/method*.py` contains commented branches for earlier datasets, ablations, machines, and model variants. Those comments were retained because a disabled block may represent another pipeline stage rather than dead code.
210
+
211
+ For release use:
212
+
213
+ - `run_vlm.py` replaces source-comment switching for screenshot-context generation;
214
+ - `run_ape.py` replaces source-comment switching for detector paths, ranges, shards, and reflection;
215
+ - explicit model profiles replace provider/model edits;
216
+ - the merge and conversion tools replace hand-edited evaluation state.
217
+
218
+ The legacy entrypoints remain available for provenance and now contain the APE accumulation fix and CLI range/shard controls, but they are not the recommended fresh-run interface.
219
+
220
+ ## Verification boundary
221
+
222
+ Offline tests cover provider payloads, model aliases, PII.5 crop fidelity, false-positive removal, loop termination, APE accumulation/resume, shard isolation/merge, evaluation conversion, fail-closed semantic-cache access, and process-isolated evaluator temporary files.
223
+
224
+ The bundled tests verify evaluation mechanics without retaining prior prediction or metric artifacts. Paper-scale validation still requires fresh model inference, fixed-GT comparisons, and separately archived provenance. Selected evaluation ground truth is bundled, but the screenshot payload and authoritative public asset manifest are external.
README.md ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ tags:
3
+ - computer-vision
4
+ - object-detection
5
+ - multimodal
6
+ - extended-reality
7
+ - graphical-user-interface
8
+ ---
9
+
10
+ # Orienter
11
+
12
+ Official code release for **“Look Before You Leap: Context-Sensitive GUI Grounding for Boosting Automated Extended Reality (XR) Testing.”**
13
+
14
+ [Paper](https://doi.org/10.1145/3808134) · [Project page](https://sites.google.com/view/gui-orienter) · [Pipeline guide](PIPELINE_README.md) · [Environment](docs/ENVIRONMENT.md) · [Required assets](docs/ASSETS.md) · [Model manifest](docs/MODEL_MANIFEST.md) · [Release checklist](docs/RELEASE_CHECKLIST.md)
15
+
16
+ Orienter detects context-sensitive, user-interactable GUI elements in XR screenshots. The public pipeline has three explicit stages:
17
+
18
+ 1. an LMM combines application context and the current screenshot to mine interactable element descriptions;
19
+ 2. APE-L\(_D\) grounds those descriptions, with the optional PII.5–PII.7 reflection loop described in the paper;
20
+ 3. the evaluation tools convert, merge, filter, and score predictions.
21
+
22
+ The repository contains source code, selected evaluation ground truth, and the APE-L\(_D\) checkpoint needed by the main detector. Dataset image payloads, private data, API credentials, experiment logs, generated predictions, and previous experimental results are deliberately excluded.
23
+
24
+ ## Release status
25
+
26
+ The offline control path and utilities are unit-tested. Maintainer validation has exercised one-image live paths for the default OpenRouter VLM, APE-L\(_D\), and the PII.5–PII.7 advisor loop without retaining generated artifacts in this public copy. Historical experiment outputs and validation result tables are not bundled.
27
+
28
+ A fresh-clone paper-scale inference reproduction still requires the external assets in [docs/ASSETS.md](docs/ASSETS.md), especially the dataset image payload, provider access, and a validated CUDA/APE environment.
29
+
30
+ The paper is published in PACMSE, Volume 3, Issue FSE, Article FSE127 ([DOI 10.1145/3808134](https://doi.org/10.1145/3808134)). Do not publish this directory until the repository license, third-party revision audit, and final public model/data links are complete. Machine-readable citation metadata is available in [`CITATION.cff`](CITATION.cff).
31
+
32
+ ## Repository layout
33
+
34
+ | Path | Purpose |
35
+ | --- | --- |
36
+ | `approach/run_vlm.py` | configurable screenshot-context and candidate generation |
37
+ | `approach/run_ape.py` | configurable APE detection and optional reflection |
38
+ | `approach/reflection.py` | paper-aligned PII.5, PII.6, and PII.7 loop |
39
+ | `approach/config.py` | OpenRouter and paper model profiles |
40
+ | `approach/method*.py` | legacy experiment entrypoints retained for provenance |
41
+ | `approach/ovod/` | vendored APE, Grounding DINO, and related detector code |
42
+ | `evaluation/tools/` | prediction conversion and validated shard merge |
43
+ | `evaluation/` | detection, semantic, context, and interaction evaluation |
44
+ | `dataset/` | dataset conversion and split-generation scripts |
45
+ | `tests/` | offline regression tests for release-critical logic |
46
+
47
+ ## Installation
48
+
49
+ The supported public environment targets Python 3.10 or later so it can use a currently supported Pillow release with the latest security fixes:
50
+
51
+ ```bash
52
+ python -B -m venv .venv
53
+ source .venv/bin/activate
54
+ python -B -m pip install --upgrade pip
55
+ python -B -m pip install -r requirements.txt
56
+ ```
57
+
58
+ `requirements.txt` covers the release utilities and provider adapters. APE-L\(_D\) has a separate CUDA/PyTorch stack under `approach/ovod/APE`; the checkpoint is bundled at `approach/ovod/APE/ape_d_model_final.pth` and recorded in [docs/MODEL_MANIFEST.md](docs/MODEL_MANIFEST.md). The public tree intentionally excludes compiled APE extensions, build directories, and egg-info metadata, so build/install the APE extension inside the target CUDA environment before running Stage 2. See [docs/ENVIRONMENT.md](docs/ENVIRONMENT.md) and `environment.reference.yml` for the tested historical environment, a public compatibility starting point, build commands, and verification boundary. The reference YAML is not a cross-platform lockfile or a security attestation. The historical combined dependency snapshot remains at `approach/requirements.txt`, but it is not a clean lockfile.
59
+
60
+ ## Model configuration
61
+
62
+ The default provider is OpenRouter. In this project workspace, use `openrouter-run` so the key is injected by the local credential wrapper and never written to source files, logs, or shell history:
63
+
64
+ ```bash
65
+ openrouter-run python -B -m approach.run_vlm --help
66
+ ```
67
+
68
+ For a public fresh clone outside this workspace, inject `OPENROUTER_API_KEY` through the runner's secret manager or process environment. Do not commit `.env`, shell history, logs, or generated manifests containing credential values.
69
+
70
+ The default profile uses `openai/gpt-5.6-sol`, the current flagship model configured for this release. Override it without editing code:
71
+
72
+ ```bash
73
+ export OPENROUTER_MODEL=openai/gpt-5.6-sol
74
+ ```
75
+
76
+ Paper-oriented profiles remain available through OpenRouter:
77
+
78
+ | Profile | Model |
79
+ | --- | --- |
80
+ | `paper_openai` / `gpt4v` | `openai/gpt-4o-2024-08-06` |
81
+ | `paper_claude` / `claude35sonnet` | `anthropic/claude-3.5-sonnet` |
82
+ | `paper_gemini` / `gemini15pro` | `google/gemini-pro-1.5` |
83
+
84
+ Profiles are defined in `approach/config.py`. Library callers can override the catalog with a YAML or JSON profile file; CLI users select a named profile and may override the default model through `OPENROUTER_MODEL`. The paper profiles preserve the evaluated identifiers for GPT-4o-2024-08-06, Claude 3.5 Sonnet, and Gemini 1.5 Pro. Provider catalogs may retire historical routes; if one is unavailable, use a custom profile or direct vendor access and record the substitution instead of silently changing the paper profile. The client records only the credential variable name in run manifests; it never writes the credential value.
85
+
86
+ ## Quick start
87
+
88
+ The commands below show the complete public interface. They require the external files listed in [docs/ASSETS.md](docs/ASSETS.md). Keep screenshots, metadata, prompts, predictions, visualizations, and evaluation files outside the repository:
89
+
90
+ ```bash
91
+ export ORIENTER_DATA_DIR=/absolute/path/to/orienter_data
92
+ export ORIENTER_RUN_DIR=/absolute/path/to/orienter_run
93
+ mkdir -p "$ORIENTER_RUN_DIR"
94
+ ```
95
+
96
+ Build a question manifest from mounted screenshots:
97
+
98
+ ```bash
99
+ python -B scripts/generate_questions.py \
100
+ --images-dir "$ORIENTER_DATA_DIR/images" \
101
+ --output "$ORIENTER_RUN_DIR/questions.jsonl" \
102
+ --prompt "Identify interactable elements." \
103
+ --metadata-template "$ORIENTER_RUN_DIR/app_metadata.json"
104
+ ```
105
+
106
+ Screenshot filenames must match `<numeric_app_id>_<numeric_frame>.<supported_ext>`, for example `123_4.jpg`; additional underscores are not supported. The frame must be in `[0, 999]` and is zero-padded to three digits when deriving `image_id`. Fill every generated `app_name` and `app_description` field in `$ORIENTER_RUN_DIR/app_metadata.json` before Stage 1; blank or missing records fail closed.
107
+
108
+ Validate the mounted inputs and the metadata coverage:
109
+
110
+ ```bash
111
+ python -B scripts/verify_assets.py \
112
+ --repo-root . \
113
+ --questions "$ORIENTER_RUN_DIR/questions.jsonl" \
114
+ --images-dir "$ORIENTER_DATA_DIR/images" \
115
+ --app-metadata-cache "$ORIENTER_RUN_DIR/app_metadata.json"
116
+ ```
117
+
118
+ Generate candidate descriptions:
119
+
120
+ ```bash
121
+ openrouter-run python -B -m approach.run_vlm \
122
+ --questions "$ORIENTER_RUN_DIR/questions.jsonl" \
123
+ --images-dir "$ORIENTER_DATA_DIR/images" \
124
+ --output "$ORIENTER_RUN_DIR/candidates.jsonl" \
125
+ --profile default \
126
+ --app-metadata-cache "$ORIENTER_RUN_DIR/app_metadata.json"
127
+ ```
128
+
129
+ `--app-metadata-cache` accepts JSON or JSONL records keyed by `app_id` and avoids live Steam page drift. When supplied, missing app IDs fail closed. Omit it only for exploratory runs where live Steam lookup is acceptable.
130
+
131
+ ### Data privacy
132
+
133
+ Stage 1 sends each source screenshot to the selected OpenRouter/provider model. When reflection is enabled, PII.5 additionally sends the bounding-box crop and PII.6 sends the annotated full scene. Do not run private, personally identifying, confidential, or otherwise unauthorized screenshots through a remote provider. Review the selected provider's retention and data-processing terms, obtain the necessary permission, and use an approved local/provider deployment when screenshots cannot leave your environment. Generated candidates, predictions, traces, and error files may also reveal image labels or local filenames; keep them outside the release repository.
134
+
135
+ Ground candidates with APE-L\(_D\):
136
+
137
+ ```bash
138
+ python -B -m approach.run_ape \
139
+ --questions "$ORIENTER_RUN_DIR/questions.jsonl" \
140
+ --candidates "$ORIENTER_RUN_DIR/candidates.jsonl" \
141
+ --images-dir "$ORIENTER_DATA_DIR/images" \
142
+ --output "$ORIENTER_RUN_DIR/predictions.json" \
143
+ --visualization-dir "$ORIENTER_RUN_DIR/ape_visualizations" \
144
+ --ape-root approach/ovod/APE \
145
+ --ape-checkpoint ape_d_model_final.pth
146
+ ```
147
+
148
+ The released checkpoint is SHA-256 verified before loading. A different checkpoint is rejected unless `--trust-custom-checkpoint` is supplied explicitly after independently verifying its source; PyTorch checkpoints are executable pickle-based artifacts and must not be loaded from an untrusted source.
149
+
150
+ Enable the paper reflection loop explicitly because it incurs additional LMM calls:
151
+
152
+ ```bash
153
+ openrouter-run python -B -m approach.run_ape \
154
+ --questions "$ORIENTER_RUN_DIR/questions.jsonl" \
155
+ --candidates "$ORIENTER_RUN_DIR/candidates.jsonl" \
156
+ --images-dir "$ORIENTER_DATA_DIR/images" \
157
+ --output "$ORIENTER_RUN_DIR/predictions.json" \
158
+ --visualization-dir "$ORIENTER_RUN_DIR/ape_visualizations" \
159
+ --ape-checkpoint ape_d_model_final.pth \
160
+ --enable-reflection \
161
+ --reflection-profile default \
162
+ --max-reflection-iterations 10
163
+ ```
164
+
165
+ PII.5 sends the original scene and the bbox crop as separate images. The crop is neither enlarged nor flipped. PII.6 sends the full scene with current boxes, and PII.7 returns structured verification/refinement feedback. Rejected false positives are removed before the next detector call; verified detections are retained.
166
+
167
+ ## Ranges, shards, resume, and merge
168
+
169
+ Use either a range or deterministic modulo shards; the two selection modes are intentionally mutually exclusive. Output filenames receive a selection suffix automatically, so parallel jobs cannot overwrite each other.
170
+
171
+ ```bash
172
+ python -B -m approach.run_ape ... --start-index 0 --end-index 100
173
+ python -B -m approach.run_ape ... --num-shards 4 --shard-index 0
174
+ python -B -m approach.run_ape ... --num-shards 4 --shard-index 1
175
+ ```
176
+
177
+ Add `--resume` to continue from an existing output. Progress sidecars record completed images even when a valid inference produces zero detections.
178
+
179
+ Merge shards only after validating overlap and coverage:
180
+
181
+ ```bash
182
+ python -B -m evaluation.tools.merge_predictions \
183
+ --inputs "$ORIENTER_RUN_DIR/predictions.shard00-of-04.json" \
184
+ "$ORIENTER_RUN_DIR/predictions.shard01-of-04.json" \
185
+ "$ORIENTER_RUN_DIR/predictions.shard02-of-04.json" \
186
+ "$ORIENTER_RUN_DIR/predictions.shard03-of-04.json" \
187
+ --questions "$ORIENTER_RUN_DIR/questions.jsonl" \
188
+ --output "$ORIENTER_RUN_DIR/predictions.merged.json"
189
+ ```
190
+
191
+ The merge command fails on overlapping shard progress, duplicate detections, missing sidecars, or incomplete expected image coverage.
192
+
193
+ ## Evaluation
194
+
195
+ Convert prediction fields explicitly for each task:
196
+
197
+ ```bash
198
+ python -B -m evaluation.tools.to_pred \
199
+ --task semantics \
200
+ --input "$ORIENTER_RUN_DIR/predictions.merged.json" \
201
+ --output "$ORIENTER_RUN_DIR/predictions.semantics.json"
202
+ ```
203
+
204
+ `--task` accepts `semantics`, `interactable`, `interaction`, or `all`; `all` writes separate outputs and never overwrites one task with another. The remaining metric scripts and expected ground-truth layouts are documented in [PIPELINE_README.md](PIPELINE_README.md).
205
+
206
+ ## Tests
207
+
208
+ Run the release-critical offline suite from the repository root:
209
+
210
+ ```bash
211
+ python -B scripts/smoke_control_path.py
212
+ python scripts/run_tests.py
213
+ ```
214
+
215
+ The deterministic control-path smoke uses a generated image plus injected provider/detector functions; it does not call a paid API or load APE weights. The unit tests are also offline. The final release gate additionally requires a real one-image inference, reflection smoke test, and evaluation smoke test after the external materials are supplied.
216
+
217
+ ## Legacy experiment scripts
218
+
219
+ `approach/method.py`, `method_fastuse.py`, `method_claude.py`, and `method_gemini.py` preserve historical branches and commented stage variants so experiment provenance is not lost. Their active APE paths now produce valid accumulated JSON and accept range/shard controls; `method.py` and `method_fastuse.py` also accept explicit data/checkpoint/output paths and reflection flags.
220
+
221
+ For new runs, prefer `run_vlm.py` and `run_ape.py`. They make stage selection, paths, model profiles, app metadata caches, ranges, and shards configurable instead of requiring source edits.
222
+
223
+ ## Data, licenses, and citation
224
+
225
+ Dataset images, annotations, bundled weights, and vendored third-party code may use different licenses. Publish every external asset with its stable URL, version, SHA-256, license, and expected local path. See [docs/THIRD_PARTY.md](docs/THIRD_PARTY.md) and [docs/MODEL_MANIFEST.md](docs/MODEL_MANIFEST.md).
226
+
227
+ The paper is published in *Proceedings of the ACM on Software Engineering*, Volume 3, Issue FSE, Article FSE127, pages 2858–2881, in 2026:
228
+
229
+ ```bibtex
230
+ @article{li2026look,
231
+ author = {Shuqing Li and Binchang Li and Yepang Liu and Cuiyun Gao and
232
+ Jianping Zhang and Shing-Chi Cheung and Michael R. Lyu},
233
+ title = {Look Before You Leap: Context-Sensitive GUI Grounding for
234
+ Boosting Automated Extended Reality (XR) Testing},
235
+ journal = {Proceedings of the ACM on Software Engineering},
236
+ year = {2026},
237
+ volume = {3},
238
+ number = {FSE},
239
+ pages = {2858--2881},
240
+ articleno = {FSE127},
241
+ doi = {10.1145/3808134}
242
+ }
243
+ ```
244
+
245
+ `CITATION.cff` contains the same preferred paper citation. The top-level code license and final Hugging Face repository links remain publication decisions; licenses for the paper, data, model, and vendored source are separate and must not be inferred from one another.
approach/__init__.py ADDED
File without changes
approach/app_metadata.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Load and validate pinned application metadata used by Orienter prompts."""
2
+
3
+ import json
4
+ from collections.abc import Mapping
5
+ from pathlib import Path
6
+
7
+
8
+ def _reject_duplicate_keys(pairs):
9
+ result = {}
10
+ for key, value in pairs:
11
+ if key in result:
12
+ raise ValueError(f"Duplicate key in metadata cache: {key}")
13
+ result[key] = value
14
+ return result
15
+
16
+
17
+ def _loads_json(text: str):
18
+ return json.loads(text, object_pairs_hook=_reject_duplicate_keys)
19
+
20
+
21
+ def _validated_record(app_id, record):
22
+ normalized_id = str(app_id)
23
+ if not isinstance(record, Mapping):
24
+ raise ValueError(
25
+ f"metadata cache record for app_id {normalized_id} must be a JSON object"
26
+ )
27
+ if "app_id" in record and str(record["app_id"]) != normalized_id:
28
+ raise ValueError(
29
+ f"metadata cache key {normalized_id} conflicts with record app_id {record['app_id']}"
30
+ )
31
+ app_name = record.get("app_name") or record.get("name") or record.get("title")
32
+ app_description = record.get("app_description") or record.get("description")
33
+ if not isinstance(app_name, str) or not app_name.strip():
34
+ raise ValueError(f"metadata cache record for app_id {normalized_id} is missing app_name")
35
+ if not isinstance(app_description, str) or not app_description.strip():
36
+ raise ValueError(
37
+ f"metadata cache record for app_id {normalized_id} is missing app_description"
38
+ )
39
+ return dict(record)
40
+
41
+
42
+ def _add_record(cache, app_id, record):
43
+ normalized_id = str(app_id)
44
+ if normalized_id in cache:
45
+ raise ValueError(f"Duplicate app_id in metadata cache: {normalized_id}")
46
+ cache[normalized_id] = _validated_record(normalized_id, record)
47
+
48
+
49
+ def load_app_metadata_cache(path):
50
+ """Return a validated app-id mapping from JSON, JSON-list, or JSONL input."""
51
+ if path is None:
52
+ return None
53
+
54
+ path = Path(path)
55
+ if path.suffix.lower() == ".jsonl":
56
+ cache = {}
57
+ with path.open(encoding="utf-8") as file:
58
+ for line_number, line in enumerate(file, start=1):
59
+ if not line.strip():
60
+ continue
61
+ record = _loads_json(line)
62
+ if not isinstance(record, Mapping) or "app_id" not in record:
63
+ raise ValueError(
64
+ f"metadata JSONL record on line {line_number} must include app_id"
65
+ )
66
+ _add_record(cache, record["app_id"], record)
67
+ return cache
68
+
69
+ loaded = _loads_json(path.read_text(encoding="utf-8"))
70
+ if isinstance(loaded, list):
71
+ cache = {}
72
+ for index, record in enumerate(loaded):
73
+ if not isinstance(record, Mapping) or "app_id" not in record:
74
+ raise ValueError(f"metadata JSON list record {index} must include app_id")
75
+ _add_record(cache, record["app_id"], record)
76
+ return cache
77
+ if not isinstance(loaded, Mapping):
78
+ raise ValueError(f"Metadata cache must be a JSON object, list, or JSONL: {path}")
79
+
80
+ cache = {}
81
+ for app_id, record in loaded.items():
82
+ _add_record(cache, app_id, record)
83
+ return cache
84
+
85
+
86
+ def get_app_metadata(app_id, metadata_cache):
87
+ """Return the validated app name and description for one app ID."""
88
+ normalized_id = str(app_id)
89
+ record = metadata_cache.get(normalized_id)
90
+ if record is None:
91
+ raise KeyError(f"app_id {normalized_id} is missing from the app metadata cache")
92
+ record = _validated_record(normalized_id, record)
93
+ app_name = record.get("app_name") or record.get("name") or record.get("title")
94
+ app_description = record.get("app_description") or record.get("description")
95
+ return app_name, app_description
approach/config.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import Any, Dict, Mapping, Optional
6
+
7
+
8
+ OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class ProviderConfig:
13
+ name: str
14
+ provider: str
15
+ model: str
16
+ base_url: str
17
+ api_key_env: str
18
+ router_options: Dict[str, Any] = field(default_factory=dict)
19
+
20
+ @property
21
+ def api_key(self) -> str:
22
+ return os.environ.get(self.api_key_env, "")
23
+
24
+
25
+ DEFAULT_PROFILES: Dict[str, ProviderConfig] = {
26
+ "default": ProviderConfig(
27
+ name="default",
28
+ provider="openrouter",
29
+ model=os.environ.get("OPENROUTER_MODEL", "openai/gpt-5.6-sol"),
30
+ base_url=OPENROUTER_BASE_URL,
31
+ api_key_env="OPENROUTER_API_KEY",
32
+ ),
33
+ "best_value": ProviderConfig(
34
+ name="best_value",
35
+ provider="openrouter",
36
+ model=os.environ.get("OPENROUTER_BEST_VALUE_MODEL", "openai/gpt-5.6-terra"),
37
+ base_url=OPENROUTER_BASE_URL,
38
+ api_key_env="OPENROUTER_API_KEY",
39
+ ),
40
+ "paper_openai": ProviderConfig(
41
+ name="paper_openai",
42
+ provider="openrouter",
43
+ model="openai/gpt-4o-2024-08-06",
44
+ base_url=OPENROUTER_BASE_URL,
45
+ api_key_env="OPENROUTER_API_KEY",
46
+ ),
47
+ "paper_claude": ProviderConfig(
48
+ name="paper_claude",
49
+ provider="openrouter",
50
+ model="anthropic/claude-3.5-sonnet",
51
+ base_url=OPENROUTER_BASE_URL,
52
+ api_key_env="OPENROUTER_API_KEY",
53
+ ),
54
+ "paper_gemini": ProviderConfig(
55
+ name="paper_gemini",
56
+ provider="openrouter",
57
+ model="google/gemini-pro-1.5",
58
+ base_url=OPENROUTER_BASE_URL,
59
+ api_key_env="OPENROUTER_API_KEY",
60
+ ),
61
+ "fastuse_experimental": ProviderConfig(
62
+ name="fastuse_experimental",
63
+ provider="openrouter",
64
+ model=os.environ.get("OPENROUTER_FASTUSE_MODEL", "google/gemini-3.1-pro-preview"),
65
+ base_url=OPENROUTER_BASE_URL,
66
+ api_key_env="OPENROUTER_API_KEY",
67
+ ),
68
+ }
69
+
70
+
71
+ PROFILE_ALIASES = {
72
+ "openrouter": "default",
73
+ "gpt4v": "paper_openai",
74
+ "gpt4v_abl": "paper_openai",
75
+ "claude35sonnet": "paper_claude",
76
+ "gemini15pro": "paper_gemini",
77
+ "gemini31pro": "fastuse_experimental",
78
+ "gemini-3-flash-preview-nothinking": "default",
79
+ "gemini-3-flash-preview-nothinking_abl": "default",
80
+ }
81
+
82
+
83
+ def _load_yaml_or_json(path: Path) -> Mapping[str, Any]:
84
+ text = path.read_text()
85
+ if path.suffix.lower() == ".json":
86
+ return json.loads(text)
87
+ try:
88
+ import yaml
89
+ except ImportError as exc:
90
+ raise RuntimeError("Install PyYAML or use a JSON config file.") from exc
91
+ loaded = yaml.safe_load(text)
92
+ return loaded or {}
93
+
94
+
95
+ def _profile_from_mapping(name: str, values: Mapping[str, Any]) -> ProviderConfig:
96
+ return ProviderConfig(
97
+ name=name,
98
+ provider=values["provider"],
99
+ model=values["model"],
100
+ base_url=values.get("base_url", OPENROUTER_BASE_URL),
101
+ api_key_env=values.get("api_key_env", "OPENROUTER_API_KEY"),
102
+ router_options=dict(values.get("router_options", {})),
103
+ )
104
+
105
+
106
+ def load_profiles(path: Optional[str] = None) -> Dict[str, ProviderConfig]:
107
+ profiles = dict(DEFAULT_PROFILES)
108
+ if not path:
109
+ return profiles
110
+
111
+ raw = _load_yaml_or_json(Path(path))
112
+ raw_profiles = raw.get("profiles", raw)
113
+ for name, values in raw_profiles.items():
114
+ profiles[name] = _profile_from_mapping(name, values)
115
+ return profiles
116
+
117
+
118
+ def get_model_profile(name: str = "default", config_path: Optional[str] = None) -> ProviderConfig:
119
+ profile_name = PROFILE_ALIASES.get(name, name)
120
+ profiles = load_profiles(config_path)
121
+ if profile_name not in profiles:
122
+ valid = ", ".join(sorted(profiles))
123
+ raise ValueError(f"Unknown model profile '{name}'. Valid profiles: {valid}")
124
+ return profiles[profile_name]
125
+
126
+
127
+ def validate_model_available(profile: ProviderConfig, models_catalog: Optional[Any] = None) -> bool:
128
+ if models_catalog is None:
129
+ return True
130
+ if isinstance(models_catalog, Mapping):
131
+ models_catalog = models_catalog.get("data", models_catalog.keys())
132
+ model_ids = {
133
+ entry.get("id") if isinstance(entry, Mapping) else str(entry)
134
+ for entry in models_catalog
135
+ }
136
+ if profile.model not in model_ids:
137
+ raise ValueError(f"Model {profile.model!r} was not found in the supplied catalog.")
138
+ return True
139
+
140
+
141
+ def sanitize_manifest(
142
+ profile: ProviderConfig,
143
+ request_payload: Mapping[str, Any],
144
+ response_payload: Optional[Mapping[str, Any]] = None,
145
+ ) -> Dict[str, Any]:
146
+ response_payload = response_payload or {}
147
+ return {
148
+ "provider": profile.provider,
149
+ "profile": profile.name,
150
+ "request_model": request_payload.get("model"),
151
+ "response_model": response_payload.get("model"),
152
+ "base_url": profile.base_url,
153
+ "api_key_env": profile.api_key_env,
154
+ "router_options": dict(profile.router_options),
155
+ "usage": response_payload.get("usage", {}),
156
+ }
approach/method_gemini.py ADDED
@@ -0,0 +1,655 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import sys
4
+ import json
5
+ import time
6
+ import random
7
+ import string
8
+ import argparse
9
+ import pandas as pd
10
+ import copy
11
+ import logging
12
+ from tqdm import tqdm
13
+ import PIL
14
+ from PIL import ImageFile
15
+ PIL.ImageFile.LOAD_TRUNCATED_IMAGES = True
16
+
17
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18
+ sys.path.append(BASE_DIR)
19
+ from approach.pipeline_utils import (
20
+ enrich_ape_results,
21
+ output_path_for_selection,
22
+ select_jsonl_lines,
23
+ write_json_atomic,
24
+ )
25
+
26
+ os.environ['TORCH_HOME'] = './.cache'
27
+ os.environ['HF_HOME'] = './.cache'
28
+
29
+
30
+ vlm = 'llava7b'
31
+ llm = 'gpt_3.5_turbo'
32
+ ovod = 'grounding_dino'
33
+
34
+ vlm_prompt = ''
35
+
36
+ ### Configurable
37
+ host_device = os.getenv('ORIENTER_LEGACY_HOST', 'local')
38
+ perspective = 'all_perspective'
39
+ # perspective = 'direct_back'
40
+ # perspective = 'direct_front'
41
+ # perspective = 'direct_side'
42
+ # perspective = 'direct_top'
43
+ # perspective = 'eyelevel'
44
+ # perspective = 'overlook'
45
+ ### Configurable
46
+
47
+ llava_path = os.path.join(BASE_DIR, 'approach/vlm/LLaVA/llava/eval')
48
+ grounding_dino_path = os.path.join(BASE_DIR, 'approach/ovod/GroundingDINO')
49
+ ape_path = os.path.join(BASE_DIR, 'approach/ovod/APE')
50
+ # TODO: data on the CUHK server
51
+ # images_dir = os.path.join(BASE_DIR, 'dataset/data/coco_merged/images/interactable')
52
+ # icse
53
+ # images_dir = os.path.join(BASE_DIR, 'dataset/data/coco_merged/images/test_set_merged')
54
+ # icse_rebuttal
55
+ # images_dir = os.path.join(BASE_DIR, 'dataset/data/coco_angleview/images/all_perspective')
56
+ # fse
57
+ images_dir = os.path.join(BASE_DIR, 'dataset/data/coco_det/images/union3')
58
+
59
+
60
+ # def generate_grounding_dino_command(ovod_image_path, ovod_output_dir, ovod_candidates):
61
+ # gdino_object_str = ' . '.join(ovod_candidates)
62
+
63
+ # gdino_command = f'''
64
+ # python demo/inference_on_a_image.py \
65
+ # -c groundingdino/config/GroundingDINO_SwinT_OGC.py \
66
+ # -p weights/groundingdino_swint_ogc.pth \
67
+ # -i {ovod_image_path} \
68
+ # -o "{ovod_output_dir}" \
69
+ # -t "{gdino_object_str}"
70
+ # '''
71
+ # return gdino_command
72
+
73
+
74
+ def generate_question_file(img_folder, dst_path):
75
+ # Ensure the directory exists
76
+ if not os.path.exists(img_folder):
77
+ print(f"Error: Directory {img_folder} does not exist.")
78
+ return
79
+
80
+ # List all files in the directory
81
+ all_files = os.listdir(img_folder)
82
+
83
+ # Filter out files that are not images (based on extension). You can add more if needed.
84
+ image_extensions = [".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff"]
85
+ image_files = [f for f in all_files if any(f.lower().endswith(ext) for ext in image_extensions)]
86
+
87
+ # Open the output file for writing
88
+ with open(dst_path, 'w') as out_file:
89
+ for index, img_file in enumerate(image_files):
90
+ data = {
91
+ "question_id": index,
92
+ "image": img_file,
93
+ "text": vlm_prompt,
94
+ "category": "detail"
95
+ }
96
+ out_file.write(json.dumps(data) + '\n')
97
+
98
+ print(f"Processed {len(image_files)} images. Output saved to {dst_path}.")
99
+
100
+
101
+ def generate_answer_id(length=20):
102
+ # Define the characters that can be used in the string
103
+ characters = string.ascii_letters + string.digits
104
+ # Generate a random string of the specified length
105
+ answer_id = ''.join(random.choice(characters) for _ in range(length))
106
+ return answer_id
107
+
108
+
109
+ def method(vlm=vlm, llm=llm, ovod=ovod, start_index=None, end_index=None, shard_index=None, num_shards=None):
110
+ # vlm_question_path = os.path.join(BASE_DIR, 'approach/vlm/sampled_vlm_questions.jsonl')
111
+ # vlm_question_path = os.path.join(BASE_DIR, f'approach/vlm/icse_test_set_merged_vlm_questions_from671.jsonl')
112
+ # icse
113
+ # complete_vlm_question_abl_i_path = os.path.join(BASE_DIR, f'approach/vlm/icse_test_set_merged_complete_vlm_questions_ablation_interactability.jsonl')
114
+
115
+ # icse
116
+ # complete_vlm_question_path = os.path.join(BASE_DIR, f'approach/vlm/icse_test_set_merged_complete_vlm_questions.jsonl')
117
+ # vlm_question_path = os.path.join(BASE_DIR, f'approach/vlm/gpt4v_failure_questions.jsonl')
118
+ # icse_rebuttal
119
+ # vlm_question_path = os.path.join(BASE_DIR, f'approach/vlm/icse_rbt_{perspective}_vlm_questions.jsonl')
120
+ # complete_vlm_question_path = os.path.join(BASE_DIR, f'approach/vlm/icse_rbt_{perspective}_complete_vlm_questions.jsonl')
121
+ # fse
122
+ # vlm_question_path = os.path.join(BASE_DIR, f'approach/vlm/fse_union3_{vlm}_questions.jsonl')
123
+ complete_vlm_question_path = os.path.join(BASE_DIR, f'approach/vlm/realfse_union3_complete_{vlm}_questions.jsonl')
124
+ vlm_question_path = complete_vlm_question_path
125
+ # vlm_question_path = os.path.join(BASE_DIR, f'approach/vlm/gpt4v_failure_questions.jsonl')
126
+ # playground/data/coco2014_val_qa_eval/qa90_questions.jsonl
127
+ # vlm_answer_path = os.path.join(BASE_DIR, f'approach/vlm/{host_device}_icse_test_set_merged_{vlm}_answer_slowerspeed_1.jsonl')
128
+ # vlm_answer_path = os.path.join(BASE_DIR, f'approach/vlm/{host_device}_icse_test_set_merged_{vlm}_answer_higherspeed_r3_1_from195.jsonl')
129
+
130
+ # icse
131
+ # vlm_answer_path = os.path.join(BASE_DIR, f'approach/vlm/cuhk_icse_test_set_merged_{vlm}_answer.jsonl')
132
+ # llava_vlm_answer_path = os.path.join(BASE_DIR, f'approach/vlm/{host_device}_icse_test_set_merged_{vlm}_answer.jsonl')
133
+ # gemini_vlm_answer_path = os.path.join(BASE_DIR, f'approach/vlm/{host_device}_icse_test_set_merged_{vlm}_answer_ablation_no_interactability_r1_0.jsonl')
134
+ # gpt4_results_answer_path = os.path.join(BASE_DIR, f'approach/vlm/aaa_cuhk_icse_test_set_merged_gpt4v_answer_higherspeed_r3_0.jsonl')
135
+ # interactability_abl_results_answer_path = os.path.join(BASE_DIR, f'approach/vlm/aaa_hit_icse_test_set_merged_gemini_answer_ablation_no_interactability_r1_0.jsonl')
136
+ # icse_rebuttal
137
+ # vlm_answer_path = os.path.join(BASE_DIR, f'approach/vlm/icse_rbt_{perspective}_{vlm}_answer.jsonl')
138
+ # llava_vlm_answer_path = os.path.join(BASE_DIR, f'approach/vlm/{host_device}_icse_rbt_{perspective}_{vlm}_answer.jsonl')
139
+ # gemini_vlm_answer_path = os.path.join(BASE_DIR, f'approach/vlm/{host_device}_icse_rbt_{perspective}_{vlm}_answer.jsonl')
140
+ # fse
141
+ vlm_answer_path = os.path.join(BASE_DIR, f'approach/vlm/realfse_uninon3_{vlm}_answer.jsonl')
142
+ llava_vlm_answer_path = os.path.join(BASE_DIR, f'approach/vlm/{host_device}_fse_uninon3_{vlm}_answer.jsonl')
143
+ gemini_vlm_answer_path = os.path.join(BASE_DIR, f'approach/vlm/realfse_union3_{vlm}_answer.jsonl')
144
+
145
+ # icse
146
+ # gpt4_results_answer_path = os.path.join(BASE_DIR, f'approach/vlm/aaa_icse_rbt_{perspective}_gpt4v_answer.jsonl')
147
+ # icse_rebuttal
148
+ # gpt4_results_answer_path = os.path.join(BASE_DIR, f'approach/vlm/icse_rbt_{perspective}_{vlm}_answer_265_528.jsonl')
149
+ # fse
150
+ gpt4_results_answer_path = os.path.join(BASE_DIR, f'approach/vlm/realfse_uninon3_gpt4v_answer.jsonl')
151
+ # print(gpt4_results_answer_path)
152
+ # interactability_abl_results_answer_path = os.path.join(BASE_DIR, f'approach/vlm/aaa_icse_rbt_{perspective}_answer.jsonl')
153
+
154
+ # icse
155
+ # # /path/to/answer-file-our.jsonl
156
+ # # llm_candidate_path = os.path.join(BASE_DIR, f'approach/llm/{vlm}_{llm}_cancidate_objects.json')
157
+ # interactable_object_path = os.path.join(BASE_DIR, f'approach/llm/icse_test_set_merged_{vlm}_{llm}_interactable_objects.json')
158
+ # ovod_output_dir = os.path.join(BASE_DIR, f'approach/ovod/output/icse_test_set_merged_{ovod}')
159
+ # # oovd_object_bbox_path = os.path.join(BASE_DIR, f'approach/ovod/icse_test_set_merged_{vlm}_{llm}_{ovod}_object_bbox_gpu3_2.json')
160
+ # # oovd_object_bbox_path = os.path.join(BASE_DIR, f'approach/ovod/icse_test_set_merged_{vlm}_{llm}_{ovod}_ablation_no_interactability_object_bbox_gpu0.json')
161
+ # oovd_object_bbox_path = os.path.join(BASE_DIR, f'approach/ovod/icse_test_set_merged_{vlm}_{llm}_{ovod}_ablation_no_feedback_object_bbox_gpu2.json')
162
+
163
+ # icse_rebuttal
164
+ # interactable_object_path = os.path.join(BASE_DIR, f'approach/llm/icse_rbt_{perspective}_{vlm}_{llm}_interactable_objects.json')
165
+ # ovod_output_dir = os.path.join(BASE_DIR, f'approach/ovod/output/icse_rbt_{perspective}_{ovod}')
166
+ # # oovd_object_bbox_path = os.path.join(BASE_DIR, f'approach/ovod/icse_test_set_merged_{vlm}_{llm}_{ovod}_object_bbox_gpu3_2.json')
167
+ # # oovd_object_bbox_path = os.path.join(BASE_DIR, f'approach/ovod/icse_test_set_merged_{vlm}_{llm}_{ovod}_ablation_no_interactability_object_bbox_gpu0.json')
168
+ # oovd_object_bbox_path = os.path.join(BASE_DIR, f'approach/ovod/icse_rbt_{perspective}_{vlm}_{llm}_{ovod}_object_bbox_gpu3_265_528.json')
169
+
170
+ gpu = '3'
171
+ # fse
172
+ interactable_object_path = os.path.join(BASE_DIR, f'approach/llm/realfse_union3_{vlm}_{llm}_interactable_objects.json') # TODO: Tentatively unused
173
+ ovod_output_dir = os.path.join(BASE_DIR, f'approach/ovod/output/realfse_union3_{ovod}')
174
+ # GPU 0
175
+ oovd_object_bbox_path = os.path.join(BASE_DIR, f'approach/ovod/realfse_union3_{vlm}_{ovod}_object_bbox_gpu{gpu}.json')
176
+ oovd_object_bbox_path = output_path_for_selection(
177
+ oovd_object_bbox_path,
178
+ start_index=start_index,
179
+ end_index=end_index,
180
+ shard_index=shard_index,
181
+ num_shards=num_shards,
182
+ )
183
+
184
+ llava7b_model_path = os.path.join(BASE_DIR, 'approach/vlm/LLaVA/checkpoints/llava-v1.5-7b')
185
+ llava7b_cmd = f'''
186
+ python model_vqa.py \
187
+ --model-path {llava7b_model_path} \
188
+ --question-file \
189
+ {complete_vlm_question_path} \
190
+ --image-folder \
191
+ {images_dir} \
192
+ --answers-file \
193
+ {llava_vlm_answer_path}
194
+ '''
195
+
196
+ # STEP #0
197
+ # First time of running
198
+ # TODO: Check whether it is first-time running
199
+ # generate_question_file(images_dir, vlm_question_path)
200
+
201
+ # STEP #1
202
+ # VLM - Get local context
203
+ # print(f'VLM {vlm} analysis begins ...')
204
+ # vlm_start_time = time.time()
205
+ # logging.info(f'STEP #1 VLM {vlm} analysis started at %s', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
206
+ # if vlm == 'llava7b':
207
+ # original_path = os.path.dirname(__file__)
208
+ # os.chdir(llava_path)
209
+ # os.system(llava7b_cmd)
210
+ # os.chdir(original_path)
211
+ # elif vlm == 'generate_q_file':
212
+ # from approach.vlm.gpt4v.gpt4v import process_image_q
213
+ # with open(vlm_question_path, 'r') as q_file, open(complete_vlm_question_path, 'w') as cq_file: # Open answer file in append mode
214
+ # qfile_lines = q_file.readlines()
215
+ # key_idx = 0
216
+ # for line in tqdm(qfile_lines):
217
+ # key_idx = (key_idx + 1) % 4
218
+
219
+ # ans_item = {}
220
+
221
+ # line_data = json.loads(line)
222
+ # image_path = os.path.join(images_dir, line_data['image'])
223
+ # image_question = line_data['text']
224
+
225
+ # if vlm == 'gpt4v_abl':
226
+ # gpt4v_ablation = True
227
+ # elif vlm == 'gpt4v':
228
+ # gpt4v_ablation = False
229
+ # gpt4v_q = process_image_q(image_question, image_path, key_idx)
230
+ # data = {
231
+ # "question_id": line_data['question_id'],
232
+ # "image": line_data['image'],
233
+ # "text": gpt4v_q,
234
+ # "category": "detail"
235
+ # }
236
+ # cq_file.write(json.dumps(data) + '\n')
237
+ # elif vlm == 'gpt4v' or vlm == 'gpt4v_abl' or vlm == 'claude35sonnet' or vlm == 'gemini15pro':
238
+ # from approach.vlm.gpt4v.gpt4v import process_image
239
+ # with open(vlm_question_path, 'r') as q_file, open(vlm_answer_path, 'a') as a_file: # Open answer file in append mode
240
+ # qfile_lines = q_file.readlines()
241
+ # key_idx = 0
242
+ # for line in tqdm(qfile_lines):
243
+ # key_idx = (key_idx + 1) % 4
244
+
245
+ # ans_item = {}
246
+
247
+ # line_data = json.loads(line)
248
+ # image_path = os.path.join(images_dir, line_data['image'])
249
+ # image_question = line_data['text']
250
+
251
+ # gpt4v_ablation = False
252
+ # if vlm == 'gpt4v_abl':
253
+ # gpt4v_ablation = True
254
+ # elif vlm == 'gpt4v':
255
+ # gpt4v_ablation = False
256
+ # gpt4v_res = process_image(vlm, image_question, image_path, gpt4v_ablation, key_idx)
257
+ # print(gpt4v_res)
258
+ # # time.sleep(3)
259
+
260
+ # ans_item = {
261
+ # "question_id": line_data['question_id'],
262
+ # "prompt": '',
263
+ # "text": gpt4v_res,
264
+ # "answer_id": generate_answer_id(),
265
+ # "model_id": vlm,
266
+ # "metadata": {}
267
+ # }
268
+
269
+ # a_file.write(json.dumps(ans_item) + '\n')
270
+ # a_file.flush()
271
+ # elif vlm == 'bing':
272
+ # from approach.vlm.bing.bing import context_conversation
273
+ # with open(vlm_question_path, 'r') as q_file, open(vlm_answer_path, 'a') as a_file: # Open answer file in append mode
274
+ # for line in tqdm(q_file):
275
+ # ans_item = {}
276
+
277
+ # line_data = json.loads(line)
278
+ # image_path = os.path.join(images_dir, line_data['image'])
279
+ # image_question = line_data['text']
280
+
281
+ # bing_res = context_conversation(image_question, image_path)
282
+ # print(bing_res)
283
+ # time.sleep(15)
284
+
285
+ # ans_item = {
286
+ # "question_id": line_data['question_id'],
287
+ # "prompt": image_question,
288
+ # "text": bing_res,
289
+ # "answer_id": generate_answer_id(),
290
+ # "model_id": vlm,
291
+ # "metadata": {}
292
+ # }
293
+
294
+ # a_file.write(json.dumps(ans_item) + '\n')
295
+ # elif vlm == 'gemini':
296
+ # import pathlib
297
+ # import textwrap
298
+
299
+ # import google.generativeai as genai
300
+
301
+ # GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY", "[GOOGLE_API_KEY]")
302
+ # genai.configure(api_key=GOOGLE_API_KEY)
303
+
304
+ # safety_settings = [
305
+ # {
306
+ # "category": "HARM_CATEGORY_DANGEROUS",
307
+ # "threshold": "BLOCK_NONE",
308
+ # },
309
+ # {
310
+ # "category": "HARM_CATEGORY_HARASSMENT",
311
+ # "threshold": "BLOCK_NONE",
312
+ # },
313
+ # {
314
+ # "category": "HARM_CATEGORY_HATE_SPEECH",
315
+ # "threshold": "BLOCK_NONE",
316
+ # },
317
+ # {
318
+ # "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
319
+ # "threshold": "BLOCK_NONE",
320
+ # },
321
+ # {
322
+ # "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
323
+ # "threshold": "BLOCK_NONE",
324
+ # },
325
+ # ]
326
+
327
+ # # before fse
328
+ # # model = genai.GenerativeModel('gemini-pro-vision')
329
+ # # fse
330
+ # model = genai.GenerativeModel('gemini-1.5-pro')
331
+
332
+ # with open(vlm_question_path, 'r') as q_file, open(gemini_vlm_answer_path, 'a') as a_file: # Open answer file in append mode
333
+ # qfile_lines = q_file.readlines()[0:2]
334
+ # for line in tqdm(qfile_lines):
335
+
336
+ # ans_item = {}
337
+
338
+ # line_data = json.loads(line)
339
+ # image_path = os.path.join(images_dir, line_data['image'])
340
+ # image_question = line_data['text']
341
+
342
+ # gemini_response = model.generate_content([image_question, PIL.Image.open(image_path)], safety_settings=safety_settings)
343
+ # gemini_response.resolve()
344
+ # try:
345
+ # gemini_response = gemini_response.text
346
+ # ans_item = {
347
+ # "question_id": line_data['question_id'],
348
+ # "prompt": image_question,
349
+ # "text": gemini_response,
350
+ # "answer_id": generate_answer_id(),
351
+ # "model_id": vlm,
352
+ # "metadata": {}
353
+ # }
354
+
355
+ # a_file.write(json.dumps(ans_item) + '\n')
356
+ # a_file.flush()
357
+ # except Exception as e:
358
+ # print(f"Error for image {image_path}. Error: {e}")
359
+ # # print(gemini_response)
360
+ # time.sleep(30)
361
+
362
+
363
+
364
+ # elif vlm.endswith('_pass'):
365
+ # print(f'Passing VLM {vlm} ...')
366
+ # else:
367
+ # raise Exception('Unrecognized VLM!')
368
+
369
+ # logging.info(f'STEP #1 VLM {vlm} analysis completed at %s', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
370
+ # logging.info(f'STEP #1 VLM {vlm} analysis total time taken: {time.time() - vlm_start_time} seconds')
371
+
372
+
373
+
374
+
375
+
376
+ # Get label candidates
377
+ # if llm == 'gpt_3.5_turbo':
378
+ # from approach.llm.gpt_polling import infer_object_candidates
379
+ # elif llm == 'llama2':
380
+ # from approach.llm.llama import infer_object_candidates
381
+
382
+
383
+ # STEP #3
384
+ # if llm == 'gpt_3.5_turbo' or llm == 'gpt_3.5_turbo_abl':
385
+ # from approach.llm.gpt_polling import infer_objects
386
+
387
+ # if llm == 'gpt_3.5_turbo_abl':
388
+ # gpt35_ablation = True
389
+ # elif llm == 'gpt_3.5_turbo':
390
+ # gpt35_ablation = False
391
+ # infer_objects(vlm_question_path, vlm_answer_path, interactable_object_path, gpt35_ablation)
392
+ # elif llm == 'llama2':
393
+ # from approach.llm.llama import infer_objects
394
+ # exit(0)
395
+ # elif llm.endswith('_pass'):
396
+ # print(f'Passing LLM {llm} ...')
397
+ # else:
398
+ # raise Exception('Unrecognized LLM!')
399
+
400
+
401
+
402
+
403
+
404
+
405
+ # STEP #2
406
+ # Open-vocabulary object detection
407
+ ovod_start_time = time.time()
408
+ logging.info(f'STEP #2 OVOD {ovod} analysis started at %s', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
409
+ vlm_questions = {}
410
+ with open(complete_vlm_question_path, 'r') as q_file:
411
+ for line in q_file:
412
+ line_data = json.loads(line)
413
+ vlm_questions[line_data['question_id']] = line_data
414
+ # print(line_data['question_id'])
415
+
416
+ if ovod == 'grounding_dino':
417
+ from approach.ovod.GroundingDINO.demo.inference_on_a_image import process_grounding_dino
418
+
419
+ original_path = os.path.dirname(__file__)
420
+ os.chdir(grounding_dino_path)
421
+
422
+ # with open(llm_candidate_path, 'r') as llm_candidate_file, open(oovd_object_bbox_path, 'w') as oovd_file:
423
+ with open(interactable_object_path, 'r') as llm_candidate_file, open(oovd_object_bbox_path, 'w') as oovd_file:
424
+ llm_candidate = json.load(llm_candidate_file)
425
+ all_oovd_res = {}
426
+ for image_index in llm_candidate.keys():
427
+ print(image_index)
428
+ # TODO:
429
+ if (image_index != '1160'):
430
+ ovod_image_path = os.path.join(images_dir, vlm_questions[int(image_index)]["image"])
431
+ ovod_candidates = llm_candidate[image_index]['interactable_objects']
432
+ # grounding_dino_command = generate_grounding_dino_command(ovod_image_path, ovod_output_dir, ovod_candidates)
433
+ # print(grounding_dino_command)
434
+ # os.system(grounding_dino_command)
435
+
436
+ gdino_res = process_grounding_dino(
437
+ config_file='groundingdino/config/GroundingDINO_SwinT_OGC.py',
438
+ checkpoint_path='weights/groundingdino_swint_ogc.pth',
439
+ image_path=ovod_image_path,
440
+ ovod_candidates=ovod_candidates,
441
+ output_dir=ovod_output_dir,
442
+ box_threshold=0.3,
443
+ text_threshold=0.25,
444
+ token_spans=None,
445
+ cpu_only=False
446
+ )
447
+ # print(gdino_res)
448
+
449
+ object_oovd_item = copy.deepcopy(llm_candidate[image_index])
450
+ object_oovd_item['oovd_result'] = gdino_res
451
+ print(object_oovd_item)
452
+ all_oovd_res[image_index] = object_oovd_item
453
+
454
+ json.dump(all_oovd_res, oovd_file, indent=4)
455
+
456
+
457
+ os.chdir(original_path)
458
+
459
+ elif ovod == 'ape_d' or ovod == 'ape_d_abl':
460
+ from approach.ovod.APE.demo.ape_inference import run_ape_model_inference
461
+
462
+ original_path = os.path.dirname(__file__)
463
+ os.chdir(ape_path)
464
+
465
+ # with open(llm_candidate_path, 'r') as llm_candidate_file, open(oovd_object_bbox_path, 'w') as oovd_file:
466
+
467
+ all_ape_res = []
468
+ # General
469
+ with open(vlm_answer_path, 'r') as llm_candidate_file, open(oovd_object_bbox_path, 'w') as oovd_file:
470
+ # GPT-4 eval
471
+ # with open(gpt4_results_answer_path, 'r') as llm_candidate_file, open(oovd_object_bbox_path, 'w') as oovd_file:
472
+ # Gemini abl i eval
473
+ # with open(interactability_abl_results_answer_path, 'r') as llm_candidate_file, open(oovd_object_bbox_path, 'w') as oovd_file:
474
+
475
+ # llm_candidate = json.load(llm_candidate_file)
476
+ all_oovd_res = {}
477
+
478
+
479
+ # candidate_lines = llm_candidate_file.readlines()[265:528]
480
+ # GPU 0
481
+ # candidate_lines = llm_candidate_file.readlines()[0:230]
482
+ # candidate_lines = llm_candidate_file.readlines()[230:460]
483
+ # candidate_lines = llm_candidate_file.readlines()[460:690]
484
+ candidate_lines = select_jsonl_lines(
485
+ llm_candidate_file.readlines(),
486
+ start_index=start_index,
487
+ end_index=end_index,
488
+ shard_index=shard_index,
489
+ num_shards=num_shards,
490
+ )
491
+ # candidate_lines = llm_candidate_file.readlines()
492
+ for line in tqdm(candidate_lines):
493
+ ans_item = {}
494
+
495
+ line_data = json.loads(line)
496
+
497
+ # for image_index in llm_candidate.keys():
498
+ # print(image_index)
499
+ # TODO:
500
+ # if (image_index != '1160'):
501
+ # image_index = line_data['question_id']
502
+
503
+ image_name = vlm_questions[line_data['question_id']]["image"]
504
+ ovod_image_path = os.path.join(images_dir, image_name)
505
+
506
+ # General
507
+ # try:
508
+ # if line_data['text'].startswith(" ```json"):
509
+ # ovod_candidates = json.loads(line_data['text'][8:-4])['objects']
510
+ # elif line_data['text'].startswith(' {\"objects\"'):
511
+ # ovod_candidates = json.loads(line_data['text'])['objects']
512
+ # else:
513
+ # print(f"Error for decoding IVO json for {image_name}.")
514
+ # except Exception as e:
515
+ # print(f"Error for decoding IVO json for image {image_name}. Error: {e}")
516
+ # continue
517
+
518
+
519
+ # GPT-4v
520
+ if line_data['text']:
521
+ ovod_candidates = line_data['text']['objects']
522
+ else:
523
+ continue
524
+
525
+
526
+ all_res = []
527
+ for ocd in ovod_candidates.keys():
528
+ referring_expr_str = ovod_candidates[ocd]
529
+
530
+ translator = str.maketrans('', '', string.punctuation)
531
+ if type(referring_expr_str) is str:
532
+ referring_expr_str = referring_expr_str.translate(translator)
533
+ else:
534
+ # dict
535
+ referring_expr_str = ' '.join(referring_expr_str.values())
536
+ referring_expr_str = referring_expr_str.translate(translator)
537
+ referring_expr_str = f'{ocd}: {referring_expr_str}'
538
+ # print(image_name, referring_expr_str)
539
+ all_res.append(referring_expr_str)
540
+
541
+ if ovod == 'ape_d':
542
+ ape_threshold = 0.15
543
+ elif ovod == 'ape_d_abl':
544
+ ape_threshold = 0.1
545
+
546
+ ape_res = []
547
+ try:
548
+ ape_res = run_ape_model_inference(
549
+ config_file='configs/LVISCOCOCOCOSTUFF_O365_OID_VGR_SA1B_REFCOCO_GQA_PhraseCut_Flickr30k/ape_deta/ape_deta_vitl_eva02_clip_vlf_lsj1024_cp_16x4_1080k.py',
550
+ input_path=ovod_image_path,
551
+ # GPU 0
552
+ output_path=f'./realfse_{vlm}_{ovod}_gpu0123',
553
+ confidence_threshold=ape_threshold,
554
+ text_prompt=', '.join(all_res),
555
+ with_box=True,
556
+ with_mask=False,
557
+ with_sseg=False,
558
+ opts=[
559
+ "train.init_checkpoint='./ape_d_model_final.pth'",
560
+ "model.model_language.cache_dir=''",
561
+ "model.model_vision.select_box_nums_for_evaluation=500",
562
+ "model.model_vision.text_feature_bank_reset=True",
563
+ "model.model_vision.backbone.net.xattn=False",
564
+ "model.model_vision.transformer.encoder.pytorch_attn=True",
565
+ "model.model_vision.transformer.decoder.pytorch_attn=True"
566
+ ]
567
+ )
568
+ except Exception as e:
569
+ print(f"Error for image {image_name}. Error: {e}")
570
+
571
+ all_ape_res.extend(enrich_ape_results(ape_res, image_name, extract_image_id))
572
+ # object_oovd_item = copy.deepcopy(llm_candidate[image_index])
573
+ # object_oovd_item['ape_result'] = all_ape_res
574
+ # # print(object_oovd_item)
575
+ # all_oovd_res[image_index] = object_oovd_item
576
+
577
+ write_json_atomic(oovd_object_bbox_path, all_ape_res)
578
+
579
+
580
+ os.chdir(original_path)
581
+
582
+ elif ovod.endswith('_pass'):
583
+ print(f'Passing OVOD {ovod} ...')
584
+ else:
585
+ raise Exception('Unrecognized OVOD!')
586
+
587
+ logging.info(f'STEP #2 OVOD {ovod} analysis completed at %s', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
588
+ logging.info(f'STEP #2 OVOD {ovod} analysis total time taken: {time.time() - ovod_start_time} seconds')
589
+
590
+ # icse & fse
591
+ def extract_image_id(image_name):
592
+ # Split the image name into parts and form the image_id accordingly
593
+ parts = image_name.split('_')
594
+ print(image_name)
595
+ base, extension = parts[1].split('.')
596
+ return int(parts[0] + base.zfill(3))
597
+ # icse_rebuttal
598
+ # def extract_image_id(image_name):
599
+ # # Split the image name into parts by underscore
600
+ # parts = image_name.split('_')
601
+ # # Extracting the first part as the base and the numeric portion of the third part before the file extension
602
+ # base = parts[0] # This will give '625470'
603
+ # numeric_part = parts[2].split('.')[0] # This will give 'b3'
604
+ # # Removing non-numeric characters from 'b3'
605
+ # numeric_part = ''.join(filter(str.isdigit, numeric_part))
606
+ # # Zfill is used to ensure the numeric part has at least 3 digits, then combining with base
607
+ # print(int(base + numeric_part.zfill(3)))
608
+ # return int(base + numeric_part.zfill(3))
609
+
610
+
611
+ def main():
612
+ vlms = ['llava7b', 'bing', 'gpt4v']
613
+ llms = ['llama2', 'gpt_3.5_turbo']
614
+ ovods = ['grounding_dino', 'glip', 'ape_d']
615
+
616
+
617
+ # python method.py -v gpt4v -l gpt_3.5_turbo -o ape_d
618
+ # python method.py -v gpt4v -l gpt_3.5_turbo -o ape_d_abl
619
+ # python method.py -v claude35sonnet -l gpt_3.5_turbo -o ape_d
620
+ # python method.py -v gemini -l gpt_3.5_turbo -o ape_d
621
+ if __name__=='__main__':
622
+ # main()
623
+
624
+ parser = argparse.ArgumentParser("Method", add_help=True)
625
+ parser.add_argument("--vlm", "-v", type=str, required=True, help="vlm")
626
+ parser.add_argument("--llm", "-l", type=str, required=True, help="llm")
627
+ parser.add_argument("--ovod", "-o", type=str, required=True, help="ovod")
628
+ parser.add_argument("--start-index", type=int, default=None, help="first JSONL row to process")
629
+ parser.add_argument("--end-index", type=int, default=None, help="exclusive JSONL row end")
630
+ parser.add_argument("--shard-index", type=int, default=None, help="zero-based shard index")
631
+ parser.add_argument("--num-shards", type=int, default=None, help="total number of shards")
632
+ args = parser.parse_args()
633
+
634
+ gpu = '3'
635
+ stage = '2'
636
+ idx = '0001'
637
+ logging.basicConfig(format='%(asctime)s - %(levelname)s - %(name)s - %(message)s',
638
+ datefmt='%m/%d/%Y %H:%M:%S',
639
+ level=logging.INFO,
640
+ # gpu
641
+ filename=f'../log/realfse/240910_{args.vlm}_{stage}_g{gpu}_{idx}.log'
642
+ )
643
+
644
+ method(
645
+ args.vlm,
646
+ args.llm,
647
+ args.ovod,
648
+ start_index=args.start_index,
649
+ end_index=args.end_index,
650
+ shard_index=args.shard_index,
651
+ num_shards=args.num_shards,
652
+ )
653
+
654
+ # CUDA_VISIBLE_DEVICES=0 python method.py -v gpt4v -l gpt_3.5_turbo -o ape_d > ../log/realfse/240910_gpt4v_2_g0_0001.txt
655
+ # CUDA_VISIBLE_DEVICES=3 python method_gemini.py -v gemini15pro -l gpt_3.5_turbo -o ape_d > ../log/realfse/240910_gemini15pro_2_g3_0001.txt
approach/providers.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from dataclasses import dataclass
3
+ from urllib import request
4
+ from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Union
5
+
6
+ from approach.config import ProviderConfig
7
+
8
+
9
+ PostFn = Callable[..., Any]
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class EncodedImage:
14
+ data: str
15
+ media_type: str = "image/jpeg"
16
+
17
+
18
+ class UrlLibResponse:
19
+ def __init__(self, status: int, body: bytes):
20
+ self.status_code = status
21
+ self._body = body
22
+
23
+ def raise_for_status(self):
24
+ if self.status_code >= 400:
25
+ raise RuntimeError(f"HTTP request failed with status {self.status_code}")
26
+
27
+ def json(self):
28
+ return json.loads(self._body.decode("utf-8"))
29
+
30
+
31
+ def default_post(url: str, headers: Mapping[str, str], json: Mapping[str, Any], timeout: int):
32
+ req = request.Request(
33
+ url,
34
+ data=json_dumps_bytes(json),
35
+ headers=dict(headers),
36
+ method="POST",
37
+ )
38
+ with request.urlopen(req, timeout=timeout) as response:
39
+ return UrlLibResponse(response.status, response.read())
40
+
41
+
42
+ def json_dumps_bytes(payload: Mapping[str, Any]) -> bytes:
43
+ return json.dumps(payload).encode("utf-8")
44
+
45
+
46
+ def make_multimodal_content(
47
+ text: str,
48
+ image_b64s: Optional[Iterable[Union[str, EncodedImage]]] = None,
49
+ ) -> List[Dict[str, Any]]:
50
+ content = [{"type": "text", "text": text}]
51
+ for image in image_b64s or []:
52
+ encoded = image if isinstance(image, EncodedImage) else EncodedImage(data=image)
53
+ content.append(
54
+ {
55
+ "type": "image_url",
56
+ "image_url": {
57
+ "url": f"data:{encoded.media_type};base64,{encoded.data}"
58
+ },
59
+ }
60
+ )
61
+ return content
62
+
63
+
64
+ class OpenAICompatibleChatClient:
65
+ def __init__(self, profile: ProviderConfig, post: Optional[PostFn] = None):
66
+ self.profile = profile
67
+ self.post = post or default_post
68
+
69
+ def build_payload(
70
+ self,
71
+ prompt: str,
72
+ image_b64s: Optional[Iterable[Union[str, EncodedImage]]] = None,
73
+ response_format: Optional[Mapping[str, Any]] = None,
74
+ temperature: float = 0,
75
+ max_tokens: int = 4096,
76
+ ) -> Dict[str, Any]:
77
+ return self.build_messages_payload(
78
+ [
79
+ {
80
+ "role": "user",
81
+ "content": make_multimodal_content(prompt, image_b64s),
82
+ }
83
+ ],
84
+ response_format=response_format,
85
+ temperature=temperature,
86
+ max_tokens=max_tokens,
87
+ )
88
+
89
+ def build_messages_payload(
90
+ self,
91
+ messages: Iterable[Mapping[str, Any]],
92
+ response_format: Optional[Mapping[str, Any]] = None,
93
+ temperature: float = 0,
94
+ max_tokens: int = 4096,
95
+ ) -> Dict[str, Any]:
96
+ payload: Dict[str, Any] = {
97
+ "model": self.profile.model,
98
+ "temperature": temperature,
99
+ "messages": [dict(message) for message in messages],
100
+ "max_tokens": max_tokens,
101
+ }
102
+ if response_format:
103
+ payload["response_format"] = dict(response_format)
104
+ if self.profile.router_options:
105
+ payload["provider"] = dict(self.profile.router_options)
106
+ return payload
107
+
108
+ def chat_completion(self, payload: Mapping[str, Any]) -> Mapping[str, Any]:
109
+ if not self.profile.api_key:
110
+ raise RuntimeError(f"Missing API key in environment variable {self.profile.api_key_env}")
111
+ headers = {
112
+ "Content-Type": "application/json",
113
+ "Authorization": f"Bearer {self.profile.api_key}",
114
+ }
115
+ response = self.post(
116
+ f"{self.profile.base_url.rstrip('/')}/chat/completions",
117
+ headers=headers,
118
+ json=dict(payload),
119
+ timeout=120,
120
+ )
121
+ response.raise_for_status()
122
+ return response.json()
123
+
124
+ def complete_json(
125
+ self,
126
+ prompt: str,
127
+ image_b64s: Optional[Iterable[Union[str, EncodedImage]]] = None,
128
+ response_format: Optional[Mapping[str, Any]] = None,
129
+ ) -> Any:
130
+ payload = self.build_payload(prompt, image_b64s, response_format=response_format)
131
+ return self.complete_json_payload(payload)
132
+
133
+ def complete_json_messages(
134
+ self,
135
+ messages: Iterable[Mapping[str, Any]],
136
+ response_format: Optional[Mapping[str, Any]] = None,
137
+ ) -> Any:
138
+ payload = self.build_messages_payload(
139
+ messages,
140
+ response_format=response_format,
141
+ )
142
+ return self.complete_json_payload(payload)
143
+
144
+ def complete_json_payload(self, payload: Mapping[str, Any]) -> Any:
145
+ response = self.chat_completion(payload)
146
+ content = response["choices"][0]["message"]["content"].strip()
147
+ if content.startswith("```"):
148
+ content = content.strip("`").split("\n", 1)[-1]
149
+ return json.loads(content)
approach/reflection.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import base64
3
+ import json
4
+ import math
5
+ from dataclasses import dataclass
6
+ from typing import Callable, Dict, Iterable, List, Sequence
7
+
8
+ from PIL import Image, ImageDraw
9
+
10
+ from approach.config import get_model_profile
11
+ from approach.providers import EncodedImage, OpenAICompatibleChatClient
12
+
13
+
14
+ Detection = Dict[str, object]
15
+ Advisor = Callable[..., Dict[str, object]]
16
+ Miner = Callable[[List[Dict[str, object]]], Sequence[str]]
17
+ Detector = Callable[[Sequence[str], List[Detection]], List[Detection]]
18
+
19
+
20
+ PII5_PROMPT = (
21
+ "PII.5 Mirroring-Based Regional Verification. Compare the original VR scene "
22
+ "and the cropped detected element. Verify whether the crop is a correct "
23
+ "user-interactable game element and whether it violates XR spatial constraints."
24
+ )
25
+ PII6_PROMPT = (
26
+ "PII.6 Unsuccessful Detection Reflection. Inspect the original scene with "
27
+ "current detection boxes and identify missing user-interactable CDs."
28
+ )
29
+ PII7_PROMPT = (
30
+ "PII.7 Advisor Feedback. Return JSON with verified, needs_refinement, and feedback. "
31
+ "Each feedback item must be a concise new or revised CD referring expression that "
32
+ "the detector can ground in the original scene."
33
+ )
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class VerificationInput:
38
+ prompt: str
39
+ original_image: Image.Image
40
+ crop_image: Image.Image
41
+ detection: Detection
42
+
43
+
44
+ def crop_detection(image: Image.Image, bbox: Sequence[float]) -> Image.Image:
45
+ if len(bbox) != 4:
46
+ raise ValueError("bbox must contain [x, y, width, height]")
47
+ x, y, width, height = bbox
48
+ left = max(0, math.floor(x))
49
+ top = max(0, math.floor(y))
50
+ right = min(image.width, math.ceil(x + width))
51
+ bottom = min(image.height, math.ceil(y + height))
52
+ if right <= left or bottom <= top:
53
+ raise ValueError(f"invalid bbox after clipping: {bbox}")
54
+ return image.crop((left, top, right, bottom))
55
+
56
+
57
+ def make_successful_detection_request(image: Image.Image, detection: Detection) -> VerificationInput:
58
+ return VerificationInput(
59
+ prompt=PII5_PROMPT,
60
+ original_image=image,
61
+ crop_image=crop_detection(image, detection["bbox"]),
62
+ detection=detection,
63
+ )
64
+
65
+
66
+ def draw_detections(image: Image.Image, detections: Iterable[Detection]) -> Image.Image:
67
+ annotated = image.copy()
68
+ draw = ImageDraw.Draw(annotated)
69
+ for idx, detection in enumerate(detections, start=1):
70
+ x, y, width, height = detection["bbox"]
71
+ box = (x, y, x + width, y + height)
72
+ draw.rectangle(box, outline="red", width=2)
73
+ draw.text((x, y), str(detection.get("category_name", idx)), fill="red")
74
+ return annotated
75
+
76
+
77
+ def image_to_png_bytes(image: Image.Image) -> bytes:
78
+ buffer = io.BytesIO()
79
+ image.save(buffer, format="PNG")
80
+ return buffer.getvalue()
81
+
82
+
83
+ def image_to_base64(image: Image.Image) -> str:
84
+ return base64.b64encode(image_to_png_bytes(image)).decode("utf-8")
85
+
86
+
87
+ REFLECTION_RESPONSE_FORMAT = {
88
+ "type": "json_schema",
89
+ "json_schema": {
90
+ "name": "orienter_reflection",
91
+ "strict": True,
92
+ "schema": {
93
+ "type": "object",
94
+ "properties": {
95
+ "verified": {"type": "array", "items": {"type": "integer"}},
96
+ "needs_refinement": {"type": "array", "items": {"type": "integer"}},
97
+ "feedback": {"type": "array", "items": {"type": "string"}},
98
+ },
99
+ "required": ["verified", "needs_refinement", "feedback"],
100
+ "additionalProperties": False,
101
+ },
102
+ },
103
+ }
104
+
105
+
106
+ def build_reflection_prompt(
107
+ verification_requests: Sequence[VerificationInput],
108
+ pii5_prompt: str,
109
+ pii6_prompt: str,
110
+ pii7_prompt: str,
111
+ ) -> str:
112
+ detections = [
113
+ {
114
+ "index": idx,
115
+ "bbox": request.detection.get("bbox"),
116
+ "category_name": request.detection.get("category_name"),
117
+ "score": request.detection.get("score", request.detection.get("probability")),
118
+ }
119
+ for idx, request in enumerate(verification_requests)
120
+ ]
121
+ return (
122
+ f"{pii5_prompt}\n"
123
+ "For each detected CD, compare the original scene image with its cropped region image. "
124
+ "Mark false positives or boxes that do not match the intended interactable element as needs_refinement.\n\n"
125
+ f"{pii6_prompt}\n"
126
+ "Also inspect the annotated full-scene image for missing interactable CDs or wrong boxes.\n\n"
127
+ f"{pii7_prompt}\n"
128
+ "Return only JSON. Images are ordered as: (1) the boxed full scene for PII.6, "
129
+ "then (2) one original-scene and bbox-crop pair per detection, in detection-index order.\n\n"
130
+ f"Detections:\n{json.dumps(detections, ensure_ascii=True, indent=2)}"
131
+ )
132
+
133
+
134
+ def openai_compatible_advisor(model_profile: str = "default") -> Advisor:
135
+ profile = get_model_profile(model_profile)
136
+ client = OpenAICompatibleChatClient(profile)
137
+
138
+ def advisor(**kwargs) -> Dict[str, object]:
139
+ verification_requests = kwargs["verification_requests"]
140
+ unsuccessful_detection_image = kwargs["unsuccessful_detection_image"]
141
+ image_b64s = [
142
+ EncodedImage(image_to_base64(unsuccessful_detection_image), "image/png")
143
+ ]
144
+ for request in verification_requests:
145
+ image_b64s.append(
146
+ EncodedImage(image_to_base64(request.original_image), "image/png")
147
+ )
148
+ image_b64s.append(
149
+ EncodedImage(image_to_base64(request.crop_image), "image/png")
150
+ )
151
+ prompt = build_reflection_prompt(
152
+ verification_requests,
153
+ kwargs["pii5_prompt"],
154
+ kwargs["pii6_prompt"],
155
+ kwargs["pii7_prompt"],
156
+ )
157
+ return client.complete_json(
158
+ prompt,
159
+ image_b64s,
160
+ response_format=REFLECTION_RESPONSE_FORMAT,
161
+ )
162
+
163
+ return advisor
164
+
165
+
166
+ def run_reflection_loop(
167
+ image: Image.Image,
168
+ detections: List[Detection],
169
+ miner: Miner,
170
+ detector: Detector,
171
+ advisor: Advisor,
172
+ max_iterations: int = 10,
173
+ ) -> Dict[str, object]:
174
+ if max_iterations <= 0:
175
+ raise ValueError("max_iterations must be positive")
176
+
177
+ current_detections = list(detections)
178
+ trace = []
179
+ max_reached = False
180
+
181
+ for iteration in range(1, max_iterations + 1):
182
+ verification_requests = [
183
+ make_successful_detection_request(image, detection)
184
+ for detection in current_detections
185
+ ]
186
+ unsuccessful_scene = draw_detections(image, current_detections)
187
+ advice = advisor(
188
+ verification_requests=verification_requests,
189
+ unsuccessful_detection_image=unsuccessful_scene,
190
+ pii5_prompt=PII5_PROMPT,
191
+ pii6_prompt=PII6_PROMPT,
192
+ pii7_prompt=PII7_PROMPT,
193
+ )
194
+ trace.append(
195
+ {
196
+ "iteration": iteration,
197
+ "verified": advice.get("verified", []),
198
+ "needs_refinement": advice.get("needs_refinement", []),
199
+ "feedback": advice.get("feedback", []),
200
+ }
201
+ )
202
+
203
+ feedback = advice.get("feedback", [])
204
+ needs_refinement = advice.get("needs_refinement", [])
205
+ if not feedback and not needs_refinement:
206
+ break
207
+
208
+ rejected_indices = {
209
+ index
210
+ for index in needs_refinement
211
+ if isinstance(index, int) and 0 <= index < len(current_detections)
212
+ }
213
+ retained_detections = [
214
+ detection
215
+ for index, detection in enumerate(current_detections)
216
+ if index not in rejected_indices
217
+ ]
218
+ candidates = list(miner(trace))
219
+ current_detections = detector(candidates, retained_detections)
220
+ else:
221
+ max_reached = True
222
+
223
+ return {
224
+ "detections": current_detections,
225
+ "trace": trace,
226
+ "max_iterations_reached": max_reached,
227
+ }
approach/requirements.txt ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ addict==2.4.0
2
+ aiohttp==3.8.6
3
+ APScheduler==3.10.0
4
+ av==11.0.0
5
+ beautifulsoup4==4.12.2
6
+ BingImageCreator==0.5.0
7
+ black==23.12.1
8
+ caffe2==0.8.1
9
+ certifi==2023.7.22
10
+ cityscapesscripts==2.2.2
11
+ click==8.0.1
12
+ clip==0.2.0
13
+ cloudpickle==3.0.0
14
+ deepspeed==0.9.5
15
+ docutils==0.20.1
16
+ einops==0.7.0
17
+ emoji==2.9.0
18
+ fairscale==0.4.13
19
+ fastapi==0.108.0
20
+ fiftyone==0.23.2
21
+ fire==0.5.0
22
+ flash_attn==2.4.2
23
+ Flask==2.1.1
24
+ fvcore==0.1.5.post20221221
25
+ gradio==4.12.0
26
+ gunicorn==19.9.0
27
+ httpx==0.26.0
28
+ huggingface_hub==0.17.3
29
+ hydra-core==1.3.2
30
+ iopath==0.1.10
31
+ ipdb==0.13.13
32
+ lxml==4.9.2
33
+ matplotlib==3.8.0
34
+ mmcv==2.1.0
35
+ mmdet==3.2.0
36
+ nltk==3.8.1
37
+ numpy==1.26.2
38
+ omegaconf==2.3.0
39
+ onnx==1.15.0
40
+ openai==0.28.1
41
+ pandas==2.1.4
42
+ peft==0.4.0
43
+ Pillow==10.0.1
44
+ Pillow==10.1.0
45
+ prompt_toolkit==3.0.43
46
+ psutil==5.9.5
47
+ pycocotools==2.0.7
48
+ Pygments==2.16.1
49
+ pytorch_lightning==2.1.3
50
+ PyYAML==6.0.1
51
+ PyYAML==6.0.1
52
+ ray==2.9.0
53
+ recommonmark==0.7.1
54
+ redis==3.5.3
55
+ Requests==2.31.0
56
+ rich==13.7.0
57
+ scipy==1.11.4
58
+ selenium==4.16.0
59
+ setuptools==68.0.0
60
+ Shapely==2.0.2
61
+ shortuuid==1.0.11
62
+ slack_sdk==3.26.1
63
+ Sphinx==7.2.6
64
+ sphinx_rtd_theme==2.0.0
65
+ supervision==0.17.1
66
+ tabulate==0.9.0
67
+ termcolor==2.4.0
68
+ torch==2.0.1
69
+ torchvision==0.15.2
70
+ tqdm==4.66.1
71
+ transformers==4.31.0
72
+ typer==0.9.0
73
+ typing_extensions==4.9.0
74
+ uvicorn==0.25.0
75
+ websockets==11.0.3
76
+ Werkzeug==2.1.0
77
+ yapf==0.40.2
78
+ timm
79
+ opencv-python
approach/run_vlm.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configurable screenshot-context mining entrypoint for Orienter."""
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import sys
7
+ import tempfile
8
+ from pathlib import Path
9
+
10
+ if __package__ in {None, ""}:
11
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
12
+
13
+ from approach.config import get_model_profile
14
+ from approach.pipeline_utils import (
15
+ output_path_for_selection,
16
+ resolve_image_path,
17
+ select_jsonl_lines,
18
+ write_json_atomic,
19
+ )
20
+
21
+
22
+ def _load_jsonl(path: Path):
23
+ if not path.exists():
24
+ return []
25
+ with path.open(encoding="utf-8") as file:
26
+ return [json.loads(line) for line in file if line.strip()]
27
+
28
+
29
+ def _write_jsonl_atomic(path: Path, records):
30
+ path.parent.mkdir(parents=True, exist_ok=True)
31
+ descriptor, temporary = tempfile.mkstemp(
32
+ prefix=".orienter-",
33
+ suffix=".jsonl.tmp",
34
+ dir=path.parent,
35
+ )
36
+ try:
37
+ with os.fdopen(descriptor, "w", encoding="utf-8") as file:
38
+ for record in records:
39
+ file.write(json.dumps(record, ensure_ascii=False) + "\n")
40
+ os.replace(temporary, path)
41
+ except Exception:
42
+ if os.path.exists(temporary):
43
+ os.unlink(temporary)
44
+ raise
45
+
46
+
47
+ def build_parser():
48
+ parser = argparse.ArgumentParser(description=__doc__)
49
+ parser.add_argument("--questions", type=Path, required=True)
50
+ parser.add_argument("--images-dir", type=Path, required=True)
51
+ parser.add_argument("--output", type=Path, required=True)
52
+ parser.add_argument("--profile", default="default")
53
+ parser.add_argument(
54
+ "--app-metadata-cache",
55
+ type=Path,
56
+ default=(
57
+ Path(os.environ["ORIENTER_APP_METADATA_CACHE"])
58
+ if os.environ.get("ORIENTER_APP_METADATA_CACHE")
59
+ else None
60
+ ),
61
+ help="optional JSON/JSONL app metadata cache keyed by app_id; missing app IDs fail closed",
62
+ )
63
+ parser.add_argument("--ablation", action="store_true")
64
+ parser.add_argument("--start-index", type=int)
65
+ parser.add_argument("--end-index", type=int)
66
+ parser.add_argument("--shard-index", type=int)
67
+ parser.add_argument("--num-shards", type=int)
68
+ parser.add_argument("--resume", action="store_true")
69
+ return parser
70
+
71
+
72
+ def run(args, processor=None):
73
+ app_metadata_cache = None
74
+ uses_default_processor = processor is None
75
+ if processor is None:
76
+ from approach.vlm.gpt4v.gpt4v import load_app_metadata_cache, process_image
77
+
78
+ app_metadata_cache = load_app_metadata_cache(args.app_metadata_cache)
79
+ processor = process_image
80
+
81
+ questions = _load_jsonl(args.questions)
82
+ selected = select_jsonl_lines(
83
+ questions,
84
+ start_index=args.start_index,
85
+ end_index=args.end_index,
86
+ shard_index=args.shard_index,
87
+ num_shards=args.num_shards,
88
+ )
89
+ output = Path(
90
+ output_path_for_selection(
91
+ str(args.output),
92
+ start_index=args.start_index,
93
+ end_index=args.end_index,
94
+ shard_index=args.shard_index,
95
+ num_shards=args.num_shards,
96
+ )
97
+ )
98
+ answers = _load_jsonl(output) if args.resume else []
99
+ completed = {answer["question_id"] for answer in answers}
100
+ errors = []
101
+ profile = get_model_profile(args.profile)
102
+ _write_jsonl_atomic(output, answers)
103
+
104
+ for index, question in enumerate(selected):
105
+ question_id = question["question_id"]
106
+ if question_id in completed:
107
+ continue
108
+ image_path = resolve_image_path(args.images_dir, question["image"])
109
+ try:
110
+ processor_args = [
111
+ args.profile,
112
+ question.get("text", ""),
113
+ str(image_path),
114
+ args.ablation,
115
+ index,
116
+ ]
117
+ if uses_default_processor:
118
+ result = processor(*processor_args, metadata_cache=app_metadata_cache)
119
+ else:
120
+ result = processor(*processor_args)
121
+ if not result:
122
+ raise RuntimeError("provider returned an empty response")
123
+ answers.append(
124
+ {
125
+ "question_id": question_id,
126
+ "prompt": question.get("text", ""),
127
+ "text": result,
128
+ "model_id": profile.model,
129
+ "metadata": {"profile": profile.name, "provider": profile.provider},
130
+ }
131
+ )
132
+ completed.add(question_id)
133
+ _write_jsonl_atomic(output, answers)
134
+ except Exception as exc:
135
+ errors.append(
136
+ {
137
+ "question_id": question_id,
138
+ "image": question["image"],
139
+ "error_type": type(exc).__name__,
140
+ "message": str(exc),
141
+ }
142
+ )
143
+
144
+ error_path = output.with_suffix(".errors.json")
145
+ write_json_atomic(str(error_path), errors)
146
+ return {
147
+ "output": str(output),
148
+ "records_selected": len(selected),
149
+ "completed": len(answers),
150
+ "errors": len(errors),
151
+ "profile": profile.name,
152
+ "model": profile.model,
153
+ }
154
+
155
+
156
+ def main(argv=None):
157
+ args = build_parser().parse_args(argv)
158
+ report = run(args)
159
+ print(json.dumps(report, indent=2))
160
+ return 1 if report["errors"] else 0
161
+
162
+
163
+ if __name__ == "__main__":
164
+ raise SystemExit(main())
dataset/2detection.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import argparse
3
+ import os
4
+
5
+ parser = argparse.ArgumentParser()
6
+ parser.add_argument('--input', type=str, required=True)
7
+ parser.add_argument('--output', type=str, required=True)
8
+ args = parser.parse_args()
9
+
10
+
11
+ if __name__ == '__main__':
12
+ with open(args.input, 'r') as f:
13
+ data = json.load(f)
14
+
15
+ for ann in data['annotations']:
16
+ ann.pop('segmentation')
17
+ ann['iscrowd'] = 0
18
+
19
+ os.makedirs(os.path.dirname(args.output), exist_ok=True)
20
+ with open(args.output, 'w') as f:
21
+ json.dump(data, f)
dataset/2polyseg.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import argparse
3
+ import numpy as np
4
+ import cv2
5
+ import json
6
+ from tqdm import tqdm
7
+ import pycocotools.mask as mask_util
8
+
9
+ parser = argparse.ArgumentParser()
10
+ parser.add_argument('--input', type=str, required=True)
11
+ parser.add_argument('--output', type=str, required=True)
12
+ args = parser.parse_args()
13
+
14
+
15
+ def rle_to_polygon(rle) -> (list, list, float):
16
+ mask = mask_util.decode(rle)
17
+ mask = np.where(mask > 0.5, 1, 0)
18
+ mask = np.ascontiguousarray(mask, dtype=np.uint8)
19
+ contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)
20
+ polygon = []
21
+ segs = []
22
+ contours = [contour for contour in contours if contour.size >= 6]
23
+ for contour in contours:
24
+ _contour = contour.flatten().tolist()
25
+ _contour.append(_contour[0])
26
+ _contour.append(_contour[1])
27
+ polygon.extend(_contour)
28
+ segs.append(contour.flatten().tolist())
29
+ RLEs = mask_util.frPyObjects(segs, mask.shape[0], mask.shape[1])
30
+ RLE = mask_util.merge(RLEs)
31
+ area = float(mask_util.area(RLE))
32
+ return polygon, area
33
+
34
+
35
+ def main(args):
36
+ with open(args.input, 'r') as f:
37
+ ann_data = json.load(f)
38
+ for ann in tqdm(ann_data['annotations'], total=len(ann_data['annotations'])):
39
+ rle_data = ann['segmentation']
40
+ # encodedRLE = mask_util.frPyObjects(rle_data, rle_data['size'][0], rle_data['size'][1])
41
+ # polygon, area = rle_to_polygon(encodedRLE)
42
+ polygon, area = rle_to_polygon(rle_data)
43
+ ann['segmentation'] = [polygon]
44
+ ann['area'] = area
45
+ ann['iscrowd'] = 0
46
+ os.makedirs(os.path.dirname(args.output), exist_ok=True)
47
+ with open(args.output, 'w') as f:
48
+ json.dump(ann_data, f)
49
+
50
+
51
+ if __name__ == '__main__':
52
+ main(args)
dataset/clean.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # clean up the merged COCO dataset
2
+ from os.path import join as pjoin
3
+ import json
4
+ import pycocotools.mask as mask_util
5
+
6
+ ROOT = 'data/coco_merged/annotations'
7
+ JSONS = ['interaction.json', 'interactable.json', 'semantics.json']
8
+
9
+ # filter noise produced by labeling tool, denote mask in compressed RLE format
10
+ def fix_mask(data):
11
+ for anno in data['annotations']:
12
+ mask = mask_util.frPyObjects(anno['segmentation'], 540, 960)
13
+ mask = mask_util.decode(mask)
14
+ mask[:, -1] = mask[:, -2]
15
+ mask[-1:, :] = mask[-2, :]
16
+ RLE = mask_util.encode(mask)
17
+ bbox = mask_util.toBbox(RLE)
18
+ bbox = bbox.astype(int)
19
+ area = int(bbox[2] * bbox[3])
20
+ RLE['counts'] = RLE['counts'].decode('utf-8')
21
+ anno['segmentation'] = RLE
22
+ anno['bbox'] = bbox.tolist()
23
+ anno['area'] = area
24
+
25
+ # remove redundant annotations
26
+ def remove_redundant(data):
27
+ total = len(data['annotations'])
28
+ anno_list = []
29
+
30
+ for anno in data['annotations']:
31
+ anno['id']=0
32
+ if anno not in anno_list:
33
+ anno_list.append(anno)
34
+
35
+ for anno in anno_list:
36
+ anno['id'] = anno_list.index(anno) + 1
37
+
38
+ data['annotations'] = anno_list
39
+ print(f'{total - len(anno_list)} redundant annotations removed')
40
+
41
+
42
+ def main():
43
+ json_files = [pjoin(ROOT, j) for j in JSONS]
44
+ for j in json_files:
45
+ with open(j, 'r') as f:
46
+ data = json.load(f)
47
+ fix_mask(data)
48
+ remove_redundant(data)
49
+ new_j = j.replace('.json', '_cleaned.json')
50
+ with open(new_j, 'w') as f:
51
+ json.dump(data, f, indent=4)
52
+
53
+
54
+ if __name__ == '__main__':
55
+ main()
dataset/create_test_set_folder.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+
4
+ # Define the paths
5
+ image_directory = './data/coco_det/images/semantics' # The directory containing the images
6
+ txt_file_path = './list_union3.txt' # The text file with indexes
7
+ new_directory = './data/coco_det/images/union3' # The directory to copy matching images
8
+
9
+ # Create the new directory if it doesn't exist
10
+ if not os.path.exists(new_directory):
11
+ os.makedirs(new_directory)
12
+
13
+ # Load the comma-separated indexes from the text file
14
+ with open(txt_file_path, 'r') as file:
15
+ content = file.read().strip() # Read the entire file content
16
+ indexes = content.split(', ') # Split by commas to get a list of indexes
17
+
18
+ # Iterate over the files in the image directory
19
+ for image_file in os.listdir(image_directory):
20
+ if image_file.endswith(".jpg"):
21
+ # Parse the appid and index from the image filename (example: 269170_1.jpg)
22
+ image_appid, image_index = image_file.split("_")[0], image_file.split("_")[1].split(".")[0].zfill(3)
23
+
24
+ # Combine appid and index to match the format in the text file (example: 269170001)
25
+ image_key = f"{image_appid}{image_index}"
26
+
27
+ # If the constructed key matches any entry in the text file, copy the image
28
+ if image_key in indexes:
29
+ source_path = os.path.join(image_directory, image_file)
30
+ destination_path = os.path.join(new_directory, image_file)
31
+
32
+ # Copy the image
33
+ shutil.copy(source_path, destination_path)
34
+ print(f"Copied: {image_file}")
35
+
36
+ print("All matching images have been copied.")
37
+
38
+
39
+
dataset/dataset_process.sh ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # # obtain interactability dataset
2
+ # python interactable_coco.py --input ./data/coco_merged/annotations/semantics.json --output ./data/coco_merged/annotations/interactable.json
3
+ # ls ./data/coco_merged/images/interactable
4
+ # if [ $? -ne 0 ]; then
5
+ # cp -r ./data/coco_merged/images/semantics ./data/coco_merged/images/interactable
6
+ # fi
7
+
8
+ # # map interaction dataset from 61 to 54 classes
9
+ # python map_interaction.py
10
+
11
+ # # generate detection only annotations
12
+ # python 2detection.py --input ./data/coco_merged/annotations/interactable.json --output ./data/coco_det/annotations/interactable.json
13
+ # python 2detection.py --input ./data/coco_merged/annotations/interaction.json --output ./data/coco_det/annotations/interaction.json
14
+ # python 2detection.py --input ./data/coco_merged/annotations/semantics.json --output ./data/coco_det/annotations/semantics.json
15
+
16
+ # # generate correct segmentation annotations with polygon format
17
+ # python 2polyseg.py --input ./data/coco_merged/annotations/interactable.json --output ./data/coco_seg/annotations/interactable.json
18
+ # python 2polyseg.py --input ./data/coco_merged/annotations/interaction.json --output ./data/coco_seg/annotations/interaction.json
19
+ # python 2polyseg.py --input ./data/coco_merged/annotations/semantics.json --output ./data/coco_seg/annotations/semantics.json
20
+
21
+ # # split dataset into train
22
+ # export FORMAT=det
23
+ # export TASK=semantics
24
+ # python split_coco.py \
25
+ # --ann_file ./data/coco_$FORMAT/annotations/$TASK.json \
26
+ # --img_dir ./data/coco_merged/images/$TASK/ \
27
+ # --output_path ./data/split_$TASK/ \
28
+ # --fold_file './fold_app.csv' \
29
+ # --train_fold '0,1,4,5,7,9' \
30
+ # --val_fold '3' \
31
+ # --test_fold '2,6,8'
32
+ # # use --gen to generate fold_file
dataset/fold_app.csv ADDED
@@ -0,0 +1,1553 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ img_id,fold
2
+ 1337530001,0
3
+ 2025000010,0
4
+ 2025000009,0
5
+ 2025000008,0
6
+ 2025000007,0
7
+ 2025000006,0
8
+ 2025000005,0
9
+ 2025000004,0
10
+ 2025000003,0
11
+ 2025000002,0
12
+ 2025000001,0
13
+ 1960620016,0
14
+ 1960620015,0
15
+ 1960620014,0
16
+ 1960620013,0
17
+ 1960620012,0
18
+ 1960620011,0
19
+ 1960620010,0
20
+ 1960620008,0
21
+ 1960620002,0
22
+ 451980001,0
23
+ 451980002,0
24
+ 451980003,0
25
+ 1150310001,0
26
+ 451980018,0
27
+ 451980017,0
28
+ 1245640021,0
29
+ 451980015,0
30
+ 451980014,0
31
+ 451980013,0
32
+ 451980012,0
33
+ 451980011,0
34
+ 451980010,0
35
+ 1960620001,0
36
+ 451980009,0
37
+ 451980007,0
38
+ 451980006,0
39
+ 451980005,0
40
+ 451980004,0
41
+ 1150310002,0
42
+ 1150310003,0
43
+ 1150310004,0
44
+ 1150310005,0
45
+ 1150310006,0
46
+ 451980008,0
47
+ 1601810001,0
48
+ 1601810002,0
49
+ 1601810003,0
50
+ 1245640001,0
51
+ 1245640002,0
52
+ 1245640003,0
53
+ 1245640004,0
54
+ 1245640005,0
55
+ 1245640006,0
56
+ 1245640007,0
57
+ 1245640008,0
58
+ 1245640009,0
59
+ 1245640038,0
60
+ 1245640010,0
61
+ 1245640012,0
62
+ 1245640013,0
63
+ 1245640014,0
64
+ 1245640015,0
65
+ 1245640016,0
66
+ 1245640017,0
67
+ 1245640018,0
68
+ 1245640019,0
69
+ 1245640020,0
70
+ 1245640011,0
71
+ 1337530082,0
72
+ 1245640037,0
73
+ 1217930003,0
74
+ 1612820001,0
75
+ 1612820002,0
76
+ 1245640036,0
77
+ 1245640035,0
78
+ 1245640034,0
79
+ 1245640033,0
80
+ 1245640032,0
81
+ 1245640031,0
82
+ 1337530002,0
83
+ 1217930004,0
84
+ 1245640030,0
85
+ 1245640028,0
86
+ 1245640027,0
87
+ 1245640026,0
88
+ 1245640025,0
89
+ 1245640024,0
90
+ 1245640023,0
91
+ 1217930005,0
92
+ 1217930001,0
93
+ 1217930002,0
94
+ 1245640029,0
95
+ 1337530081,0
96
+ 451980016,0
97
+ 1337530079,0
98
+ 1337530033,0
99
+ 1337530034,0
100
+ 1337530035,0
101
+ 1337530036,0
102
+ 1337530037,0
103
+ 1337530038,0
104
+ 1337530039,0
105
+ 1337530040,0
106
+ 1337530041,0
107
+ 1337530042,0
108
+ 1337530043,0
109
+ 1337530044,0
110
+ 1337530045,0
111
+ 1337530046,0
112
+ 1337530047,0
113
+ 1337530048,0
114
+ 1337530049,0
115
+ 1337530050,0
116
+ 1337530051,0
117
+ 1337530052,0
118
+ 1337530053,0
119
+ 1337530054,0
120
+ 1337530055,0
121
+ 1337530056,0
122
+ 1337530080,0
123
+ 1337530032,0
124
+ 1337530031,0
125
+ 1337530030,0
126
+ 1337530029,0
127
+ 1337530003,0
128
+ 1337530004,0
129
+ 1337530005,0
130
+ 1337530006,0
131
+ 1337530007,0
132
+ 1337530008,0
133
+ 1337530009,0
134
+ 1337530010,0
135
+ 1337530011,0
136
+ 1337530012,0
137
+ 1337530013,0
138
+ 1337530014,0
139
+ 660520014,0
140
+ 1337530015,0
141
+ 1337530017,0
142
+ 1337530018,0
143
+ 1337530019,0
144
+ 1337530020,0
145
+ 1337530021,0
146
+ 1337530022,0
147
+ 1337530023,0
148
+ 1337530024,0
149
+ 1337530025,0
150
+ 1337530026,0
151
+ 1337530027,0
152
+ 1337530028,0
153
+ 1337530016,0
154
+ 660520013,0
155
+ 660520015,0
156
+ 1337530058,0
157
+ 1337530057,0
158
+ 1337530059,0
159
+ 1337530060,0
160
+ 1337530061,0
161
+ 1337530062,0
162
+ 1337530063,0
163
+ 1337530064,0
164
+ 1337530065,0
165
+ 1337530066,0
166
+ 1337530067,0
167
+ 1337530068,0
168
+ 1337530069,0
169
+ 1337530070,0
170
+ 1337530071,0
171
+ 1337530072,0
172
+ 1337530073,0
173
+ 1337530074,0
174
+ 1337530075,0
175
+ 1337530076,0
176
+ 1337530077,0
177
+ 1337530078,0
178
+ 660520001,0
179
+ 660520002,0
180
+ 1245640022,0
181
+ 660520006,0
182
+ 660520008,0
183
+ 660520009,0
184
+ 660520010,0
185
+ 660520005,0
186
+ 660520004,0
187
+ 660520011,0
188
+ 660520003,0
189
+ 660520012,0
190
+ 660520007,0
191
+ 1595490004,1
192
+ 1512840006,1
193
+ 1512840005,1
194
+ 1512840004,1
195
+ 1512840003,1
196
+ 1512840002,1
197
+ 1512840001,1
198
+ 1595490014,1
199
+ 1512840007,1
200
+ 1595490001,1
201
+ 1595490017,1
202
+ 1595490018,1
203
+ 1595490019,1
204
+ 1595490020,1
205
+ 1295570001,1
206
+ 1595490013,1
207
+ 1595490012,1
208
+ 1595490011,1
209
+ 1595490010,1
210
+ 1595490009,1
211
+ 1595490016,1
212
+ 1595490008,1
213
+ 1595490007,1
214
+ 1595490006,1
215
+ 1595490003,1
216
+ 1595490005,1
217
+ 1595490015,1
218
+ 1931980002,1
219
+ 1730290004,1
220
+ 457380018,1
221
+ 457380017,1
222
+ 457380016,1
223
+ 457380015,1
224
+ 457380014,1
225
+ 457380013,1
226
+ 457380012,1
227
+ 457380011,1
228
+ 457380010,1
229
+ 457380009,1
230
+ 812460003,1
231
+ 812460004,1
232
+ 812460005,1
233
+ 812460006,1
234
+ 812460007,1
235
+ 457380008,1
236
+ 457380007,1
237
+ 457380006,1
238
+ 457380005,1
239
+ 457380019,1
240
+ 457380004,1
241
+ 812460002,1
242
+ 716260021,1
243
+ 716260006,1
244
+ 716260005,1
245
+ 716260004,1
246
+ 716260003,1
247
+ 716260002,1
248
+ 716260001,1
249
+ 631660001,1
250
+ 716260009,1
251
+ 716260010,1
252
+ 716260011,1
253
+ 716260012,1
254
+ 716260013,1
255
+ 716260014,1
256
+ 716260015,1
257
+ 716260016,1
258
+ 716260017,1
259
+ 716260018,1
260
+ 716260019,1
261
+ 716260020,1
262
+ 812460001,1
263
+ 457380003,1
264
+ 457380002,1
265
+ 457380001,1
266
+ 1931980009,1
267
+ 1931980008,1
268
+ 716260007,1
269
+ 1931980006,1
270
+ 954160002,1
271
+ 1931980005,1
272
+ 1931980010,1
273
+ 1931980004,1
274
+ 954160001,1
275
+ 1931980001,1
276
+ 1730290005,1
277
+ 1730290003,1
278
+ 1730290002,1
279
+ 1730290001,1
280
+ 1931980003,1
281
+ 1931980011,1
282
+ 1931980007,1
283
+ 1931980013,1
284
+ 812460008,1
285
+ 812460009,1
286
+ 812460010,1
287
+ 1931980012,1
288
+ 716260008,1
289
+ 1931980014,1
290
+ 815280012,2
291
+ 998660003,2
292
+ 998660002,2
293
+ 998660001,2
294
+ 815280011,2
295
+ 815280010,2
296
+ 815280001,2
297
+ 815280002,2
298
+ 815280003,2
299
+ 815280004,2
300
+ 815280005,2
301
+ 815280006,2
302
+ 815280009,2
303
+ 815280018,2
304
+ 815280017,2
305
+ 815280016,2
306
+ 815280015,2
307
+ 815280014,2
308
+ 815280013,2
309
+ 815280008,2
310
+ 815280007,2
311
+ 1652800010,2
312
+ 622310002,2
313
+ 438100004,2
314
+ 438100003,2
315
+ 438100002,2
316
+ 438100001,2
317
+ 2193270001,2
318
+ 2020950003,2
319
+ 2020950002,2
320
+ 2020950001,2
321
+ 1652800001,2
322
+ 438100005,2
323
+ 1652800002,2
324
+ 1652800004,2
325
+ 1652800005,2
326
+ 1652800006,2
327
+ 1652800007,2
328
+ 1652800008,2
329
+ 998660004,2
330
+ 1652800013,2
331
+ 1652800012,2
332
+ 1652800011,2
333
+ 1652800003,2
334
+ 622310001,2
335
+ 438100006,2
336
+ 438100008,2
337
+ 591680013,2
338
+ 591680012,2
339
+ 591680011,2
340
+ 591680010,2
341
+ 591680009,2
342
+ 591680008,2
343
+ 591680007,2
344
+ 591680006,2
345
+ 591680005,2
346
+ 438100007,2
347
+ 591680004,2
348
+ 1652800009,2
349
+ 591680001,2
350
+ 1394410001,2
351
+ 1394410002,2
352
+ 1394410003,2
353
+ 1394410004,2
354
+ 1394410005,2
355
+ 438100010,2
356
+ 438100009,2
357
+ 591680003,2
358
+ 591680002,2
359
+ 998660005,2
360
+ 1157070006,2
361
+ 1157070001,2
362
+ 1157070002,2
363
+ 1157070003,2
364
+ 1157070008,2
365
+ 1157070007,2
366
+ 1157070004,2
367
+ 1157070005,2
368
+ 898080011,3
369
+ 898080010,3
370
+ 898080009,3
371
+ 898080008,3
372
+ 898080007,3
373
+ 898080006,3
374
+ 898080005,3
375
+ 898080004,3
376
+ 1337060040,3
377
+ 898080003,3
378
+ 898080002,3
379
+ 898080001,3
380
+ 1485260001,3
381
+ 1485260002,3
382
+ 1485260003,3
383
+ 1485260004,3
384
+ 1485260005,3
385
+ 1486660001,3
386
+ 1486660002,3
387
+ 1486660003,3
388
+ 1404360029,3
389
+ 1404360027,3
390
+ 1404360022,3
391
+ 1404360025,3
392
+ 898080013,3
393
+ 898080012,3
394
+ 1404360001,3
395
+ 1404360002,3
396
+ 1404360003,3
397
+ 1404360004,3
398
+ 1404360005,3
399
+ 1404360006,3
400
+ 1404360007,3
401
+ 1404360026,3
402
+ 1404360009,3
403
+ 1404360011,3
404
+ 1404360013,3
405
+ 1404360015,3
406
+ 1404360016,3
407
+ 1404360018,3
408
+ 1404360019,3
409
+ 1486660004,3
410
+ 1404360023,3
411
+ 1404360024,3
412
+ 1404360010,3
413
+ 1486660005,3
414
+ 1486660010,3
415
+ 1486660008,3
416
+ 1825460004,3
417
+ 1825460003,3
418
+ 1825460002,3
419
+ 1825460001,3
420
+ 528580004,3
421
+ 528580003,3
422
+ 528580002,3
423
+ 528580001,3
424
+ 1801560011,3
425
+ 1801560010,3
426
+ 1801560009,3
427
+ 1801560008,3
428
+ 1801560007,3
429
+ 1801560006,3
430
+ 1801560005,3
431
+ 1801560004,3
432
+ 1801560003,3
433
+ 1801560002,3
434
+ 1801560001,3
435
+ 1825460005,3
436
+ 1825460006,3
437
+ 1825460007,3
438
+ 1825460008,3
439
+ 1486660009,3
440
+ 898080014,3
441
+ 1486660011,3
442
+ 1486660012,3
443
+ 1486660013,3
444
+ 1486660014,3
445
+ 1825460012,3
446
+ 1825460011,3
447
+ 731790010,3
448
+ 1486660007,3
449
+ 731790009,3
450
+ 731790007,3
451
+ 731790006,3
452
+ 731790005,3
453
+ 731790004,3
454
+ 731790003,3
455
+ 731790002,3
456
+ 731790001,3
457
+ 1825460010,3
458
+ 1825460009,3
459
+ 731790008,3
460
+ 898080015,3
461
+ 1486660006,3
462
+ 1394210010,3
463
+ 1337060006,3
464
+ 1337060005,3
465
+ 1337060004,3
466
+ 1337060003,3
467
+ 1337060002,3
468
+ 1337060001,3
469
+ 1337060041,3
470
+ 1337060007,3
471
+ 1337060027,3
472
+ 1337060030,3
473
+ 1337060031,3
474
+ 1337060032,3
475
+ 1337060033,3
476
+ 898080036,3
477
+ 1337060034,3
478
+ 1337060035,3
479
+ 1337060029,3
480
+ 1337060008,3
481
+ 1337060009,3
482
+ 1337060010,3
483
+ 1337060019,3
484
+ 1337060020,3
485
+ 1337060021,3
486
+ 1337060018,3
487
+ 1337060017,3
488
+ 1337060016,3
489
+ 1337060015,3
490
+ 1337060022,3
491
+ 1337060023,3
492
+ 1337060024,3
493
+ 898080016,3
494
+ 1337060025,3
495
+ 1337060026,3
496
+ 1337060014,3
497
+ 1337060013,3
498
+ 1337060012,3
499
+ 1337060011,3
500
+ 1337060036,3
501
+ 1337060039,3
502
+ 1337060028,3
503
+ 898080029,3
504
+ 898080025,3
505
+ 898080024,3
506
+ 898080023,3
507
+ 898080022,3
508
+ 898080021,3
509
+ 898080020,3
510
+ 898080019,3
511
+ 898080018,3
512
+ 898080028,3
513
+ 898080026,3
514
+ 898080030,3
515
+ 898080031,3
516
+ 898080032,3
517
+ 898080033,3
518
+ 898080017,3
519
+ 898080027,3
520
+ 1394210002,3
521
+ 1394210001,3
522
+ 1394210008,3
523
+ 1394210009,3
524
+ 898080035,3
525
+ 1394210006,3
526
+ 1394210007,3
527
+ 1394210004,3
528
+ 1394210003,3
529
+ 898080034,3
530
+ 1394210005,3
531
+ 457550003,4
532
+ 1705030007,4
533
+ 457550002,4
534
+ 457550001,4
535
+ 457550004,4
536
+ 1705030006,4
537
+ 457550005,4
538
+ 1705030008,4
539
+ 1805510025,4
540
+ 1705030010,4
541
+ 1805510024,4
542
+ 1805510026,4
543
+ 1172280004,4
544
+ 1172280003,4
545
+ 1172280002,4
546
+ 1172280001,4
547
+ 1172280009,4
548
+ 1705030011,4
549
+ 1705030012,4
550
+ 1705030013,4
551
+ 1705030009,4
552
+ 1705030001,4
553
+ 1805510020,4
554
+ 1805510022,4
555
+ 1248270013,4
556
+ 1248270012,4
557
+ 1248270011,4
558
+ 1248270010,4
559
+ 1248270009,4
560
+ 1248270008,4
561
+ 1248270007,4
562
+ 1248270006,4
563
+ 1248270005,4
564
+ 1248270004,4
565
+ 1248270003,4
566
+ 1248270002,4
567
+ 1248270001,4
568
+ 1705030005,4
569
+ 1705030004,4
570
+ 1705030003,4
571
+ 1705030016,4
572
+ 463290001,4
573
+ 1805510001,4
574
+ 1805510002,4
575
+ 1805510003,4
576
+ 1805510021,4
577
+ 1705030002,4
578
+ 1805510019,4
579
+ 1805510018,4
580
+ 1805510017,4
581
+ 1805510016,4
582
+ 1805510015,4
583
+ 1805510014,4
584
+ 1805510023,4
585
+ 1805510013,4
586
+ 1805510011,4
587
+ 1805510010,4
588
+ 1805510009,4
589
+ 1805510008,4
590
+ 1805510007,4
591
+ 1805510006,4
592
+ 1805510005,4
593
+ 1805510004,4
594
+ 1805510012,4
595
+ 463290002,4
596
+ 667010003,4
597
+ 463290004,4
598
+ 1178780007,4
599
+ 1178780008,4
600
+ 1178780009,4
601
+ 1178780010,4
602
+ 1178780011,4
603
+ 1178780012,4
604
+ 1705030014,4
605
+ 463290003,4
606
+ 1178780014,4
607
+ 910190007,4
608
+ 910190006,4
609
+ 910190005,4
610
+ 910190004,4
611
+ 910190003,4
612
+ 910190002,4
613
+ 910190001,4
614
+ 1172280005,4
615
+ 1178780016,4
616
+ 1178780015,4
617
+ 1382350005,4
618
+ 1382350004,4
619
+ 1382350003,4
620
+ 1382350002,4
621
+ 1382350001,4
622
+ 1382350008,4
623
+ 1382350007,4
624
+ 1382350006,4
625
+ 1178780006,4
626
+ 1178780005,4
627
+ 1178780013,4
628
+ 1705030015,4
629
+ 667010001,4
630
+ 1805510032,4
631
+ 1805510031,4
632
+ 667010002,4
633
+ 667010004,4
634
+ 667010005,4
635
+ 667010006,4
636
+ 667010007,4
637
+ 667010008,4
638
+ 667010010,4
639
+ 667010011,4
640
+ 667010012,4
641
+ 1805510033,4
642
+ 1172280006,4
643
+ 667010009,4
644
+ 1172280008,4
645
+ 463290005,4
646
+ 1172280007,4
647
+ 463290007,4
648
+ 463290008,4
649
+ 1805510027,4
650
+ 1805510028,4
651
+ 463290006,4
652
+ 1805510030,4
653
+ 1178780004,4
654
+ 1178780003,4
655
+ 1178780002,4
656
+ 1178780001,4
657
+ 1805510029,4
658
+ 1707840007,5
659
+ 1707840006,5
660
+ 1707840005,5
661
+ 1707840004,5
662
+ 1707840003,5
663
+ 2224020003,5
664
+ 1707840001,5
665
+ 2224020002,5
666
+ 2224020001,5
667
+ 1707840008,5
668
+ 1707840002,5
669
+ 2224020030,5
670
+ 2224020024,5
671
+ 2224020028,5
672
+ 2224020023,5
673
+ 2224020022,5
674
+ 2224020021,5
675
+ 2224020020,5
676
+ 2224020019,5
677
+ 2224020018,5
678
+ 2224020017,5
679
+ 2224020016,5
680
+ 2224020015,5
681
+ 2224020014,5
682
+ 2224020029,5
683
+ 2224020013,5
684
+ 2224020011,5
685
+ 2224020010,5
686
+ 2224020025,5
687
+ 2224020008,5
688
+ 2224020007,5
689
+ 2224020006,5
690
+ 2224020005,5
691
+ 2224020004,5
692
+ 2224020026,5
693
+ 2224020027,5
694
+ 2224020012,5
695
+ 2224020009,5
696
+ 2308940007,5
697
+ 1695870005,5
698
+ 726910008,5
699
+ 726910007,5
700
+ 726910006,5
701
+ 726910005,5
702
+ 726910004,5
703
+ 726910003,5
704
+ 726910002,5
705
+ 726910001,5
706
+ 726910009,5
707
+ 605850001,5
708
+ 513490001,5
709
+ 497820003,5
710
+ 497820002,5
711
+ 497820001,5
712
+ 2308940001,5
713
+ 2308940004,5
714
+ 2308940003,5
715
+ 2308940002,5
716
+ 513490002,5
717
+ 726910010,5
718
+ 726910011,5
719
+ 758210001,5
720
+ 1334900005,5
721
+ 1334900004,5
722
+ 1334900003,5
723
+ 1334900002,5
724
+ 1334900001,5
725
+ 1334900006,5
726
+ 1695870006,5
727
+ 758210014,5
728
+ 758210013,5
729
+ 758210012,5
730
+ 758210011,5
731
+ 758210010,5
732
+ 758210009,5
733
+ 758210008,5
734
+ 758210007,5
735
+ 758210005,5
736
+ 758210004,5
737
+ 758210003,5
738
+ 758210002,5
739
+ 2308940017,5
740
+ 2308940016,5
741
+ 758210006,5
742
+ 2308940014,5
743
+ 2224020045,5
744
+ 2224020044,5
745
+ 2224020043,5
746
+ 2224020042,5
747
+ 2224020041,5
748
+ 2224020040,5
749
+ 2224020039,5
750
+ 2224020038,5
751
+ 2224020046,5
752
+ 2224020037,5
753
+ 2224020035,5
754
+ 2224020034,5
755
+ 1695870004,5
756
+ 2308940015,5
757
+ 2224020033,5
758
+ 1695870003,5
759
+ 1695870002,5
760
+ 1695870001,5
761
+ 2224020032,5
762
+ 2224020047,5
763
+ 2224020036,5
764
+ 2224020049,5
765
+ 2308940013,5
766
+ 2308940012,5
767
+ 2308940011,5
768
+ 2308940010,5
769
+ 2308940009,5
770
+ 2224020048,5
771
+ 2308940008,5
772
+ 2308940006,5
773
+ 2308940005,5
774
+ 2224020031,5
775
+ 2224020057,5
776
+ 2224020056,5
777
+ 2224020050,5
778
+ 2224020051,5
779
+ 2224020052,5
780
+ 2224020055,5
781
+ 2224020058,5
782
+ 2224020054,5
783
+ 2224020053,5
784
+ 2057000131,6
785
+ 2057000130,6
786
+ 2057000129,6
787
+ 2057000128,6
788
+ 2057000126,6
789
+ 2057000125,6
790
+ 2057000124,6
791
+ 2057000123,6
792
+ 2057000127,6
793
+ 2057000132,6
794
+ 2057000134,6
795
+ 2057000148,6
796
+ 2057000135,6
797
+ 2057000136,6
798
+ 2057000137,6
799
+ 2057000138,6
800
+ 2057000140,6
801
+ 2057000141,6
802
+ 2057000142,6
803
+ 2057000143,6
804
+ 2057000145,6
805
+ 2057000146,6
806
+ 2057000147,6
807
+ 2057000149,6
808
+ 2057000133,6
809
+ 2057000139,6
810
+ 2057000107,6
811
+ 2057000121,6
812
+ 2057000093,6
813
+ 2057000150,6
814
+ 2057000094,6
815
+ 2057000095,6
816
+ 2057000096,6
817
+ 2057000097,6
818
+ 2057000098,6
819
+ 2057000099,6
820
+ 2057000100,6
821
+ 2057000101,6
822
+ 2057000102,6
823
+ 2057000103,6
824
+ 2057000104,6
825
+ 2057000122,6
826
+ 2057000105,6
827
+ 2057000108,6
828
+ 2057000109,6
829
+ 2057000110,6
830
+ 2057000111,6
831
+ 2057000112,6
832
+ 2057000113,6
833
+ 2057000114,6
834
+ 2057000115,6
835
+ 2057000116,6
836
+ 2057000117,6
837
+ 2057000118,6
838
+ 2057000119,6
839
+ 2057000120,6
840
+ 2057000106,6
841
+ 2057000151,6
842
+ 997760004,6
843
+ 2057000153,6
844
+ 997760008,6
845
+ 997760007,6
846
+ 997760006,6
847
+ 997760005,6
848
+ 997760003,6
849
+ 997760002,6
850
+ 997760001,6
851
+ 891960010,6
852
+ 891960009,6
853
+ 2089520003,6
854
+ 2089520004,6
855
+ 891960008,6
856
+ 997760009,6
857
+ 891960007,6
858
+ 891960005,6
859
+ 891960004,6
860
+ 891960003,6
861
+ 891960002,6
862
+ 891960001,6
863
+ 790750006,6
864
+ 790750005,6
865
+ 790750004,6
866
+ 790750003,6
867
+ 790750002,6
868
+ 790750001,6
869
+ 2057000092,6
870
+ 891960006,6
871
+ 997760010,6
872
+ 997760011,6
873
+ 997760012,6
874
+ 2057000154,6
875
+ 2057000155,6
876
+ 2057000156,6
877
+ 2057000157,6
878
+ 2057000158,6
879
+ 2057000159,6
880
+ 2057000160,6
881
+ 2057000161,6
882
+ 2057000162,6
883
+ 2057000163,6
884
+ 2057000164,6
885
+ 2057000165,6
886
+ 2057000166,6
887
+ 2057000167,6
888
+ 2057000168,6
889
+ 2057000169,6
890
+ 2057000170,6
891
+ 2057000171,6
892
+ 2057000172,6
893
+ 2057000173,6
894
+ 2057000174,6
895
+ 2057000175,6
896
+ 2057000176,6
897
+ 2057000177,6
898
+ 2057000178,6
899
+ 2089520001,6
900
+ 2089520002,6
901
+ 2057000152,6
902
+ 2057000091,6
903
+ 2057000144,6
904
+ 2057000089,6
905
+ 1269890008,6
906
+ 1269890007,6
907
+ 1269890006,6
908
+ 1269890005,6
909
+ 1269890004,6
910
+ 1269890003,6
911
+ 1269890009,6
912
+ 1388030001,6
913
+ 1269890001,6
914
+ 1456280001,6
915
+ 1456280002,6
916
+ 1456280003,6
917
+ 1456280004,6
918
+ 1480650001,6
919
+ 1269890002,6
920
+ 1269890010,6
921
+ 2057000001,6
922
+ 2057000002,6
923
+ 2057000012,6
924
+ 2057000011,6
925
+ 2057000010,6
926
+ 2057000009,6
927
+ 2057000008,6
928
+ 2057000007,6
929
+ 2057000006,6
930
+ 1561560011,6
931
+ 2057000004,6
932
+ 2057000003,6
933
+ 1269890015,6
934
+ 1269890014,6
935
+ 1269890013,6
936
+ 1269890012,6
937
+ 1269890011,6
938
+ 1480650002,6
939
+ 1480650003,6
940
+ 1561560032,6
941
+ 1561560031,6
942
+ 1561560022,6
943
+ 1561560021,6
944
+ 1561560020,6
945
+ 1561560019,6
946
+ 1561560018,6
947
+ 1561560017,6
948
+ 1561560016,6
949
+ 1561560015,6
950
+ 1561560014,6
951
+ 1561560013,6
952
+ 1561560006,6
953
+ 1561560007,6
954
+ 1561560008,6
955
+ 1561560009,6
956
+ 1561560010,6
957
+ 1561560023,6
958
+ 2057000013,6
959
+ 1561560024,6
960
+ 2057000090,6
961
+ 1561560030,6
962
+ 1561560001,6
963
+ 1561560002,6
964
+ 1561560003,6
965
+ 1480650004,6
966
+ 1480650005,6
967
+ 1480650006,6
968
+ 1480650007,6
969
+ 1480650008,6
970
+ 1561560004,6
971
+ 1561560005,6
972
+ 1561560029,6
973
+ 1561560028,6
974
+ 1561560027,6
975
+ 1561560026,6
976
+ 1561560025,6
977
+ 2057000014,6
978
+ 2057000005,6
979
+ 2057000016,6
980
+ 2057000068,6
981
+ 2057000067,6
982
+ 2057000066,6
983
+ 2057000065,6
984
+ 2057000064,6
985
+ 2057000063,6
986
+ 2057000062,6
987
+ 2057000061,6
988
+ 2057000060,6
989
+ 2057000015,6
990
+ 2057000058,6
991
+ 2057000057,6
992
+ 2057000056,6
993
+ 2057000055,6
994
+ 2057000054,6
995
+ 2057000069,6
996
+ 2057000070,6
997
+ 2057000071,6
998
+ 2057000072,6
999
+ 2057000088,6
1000
+ 2057000087,6
1001
+ 2057000086,6
1002
+ 2057000085,6
1003
+ 2057000084,6
1004
+ 2057000083,6
1005
+ 2057000082,6
1006
+ 2057000053,6
1007
+ 2057000081,6
1008
+ 2057000079,6
1009
+ 2057000078,6
1010
+ 2057000077,6
1011
+ 2057000076,6
1012
+ 2057000075,6
1013
+ 2057000074,6
1014
+ 2057000073,6
1015
+ 2057000080,6
1016
+ 2057000052,6
1017
+ 2057000059,6
1018
+ 2057000050,6
1019
+ 2057000032,6
1020
+ 2057000031,6
1021
+ 2057000030,6
1022
+ 2057000029,6
1023
+ 2057000028,6
1024
+ 2057000051,6
1025
+ 2057000027,6
1026
+ 2057000026,6
1027
+ 2057000025,6
1028
+ 2057000024,6
1029
+ 2057000023,6
1030
+ 2057000022,6
1031
+ 2057000021,6
1032
+ 2057000020,6
1033
+ 2057000019,6
1034
+ 2057000018,6
1035
+ 2057000017,6
1036
+ 2057000033,6
1037
+ 2057000034,6
1038
+ 1561560012,6
1039
+ 2057000036,6
1040
+ 2057000035,6
1041
+ 2057000044,6
1042
+ 2057000045,6
1043
+ 2057000046,6
1044
+ 2057000047,6
1045
+ 2057000043,6
1046
+ 2057000049,6
1047
+ 2057000048,6
1048
+ 2057000042,6
1049
+ 2057000041,6
1050
+ 2057000040,6
1051
+ 2057000039,6
1052
+ 2057000038,6
1053
+ 2057000037,6
1054
+ 1063530027,7
1055
+ 1063530026,7
1056
+ 1063530025,7
1057
+ 1063530028,7
1058
+ 1063530043,7
1059
+ 1063530040,7
1060
+ 1063530041,7
1061
+ 1550280038,7
1062
+ 1063530039,7
1063
+ 1063530038,7
1064
+ 1063530037,7
1065
+ 1063530036,7
1066
+ 1063530035,7
1067
+ 1063530034,7
1068
+ 1063530033,7
1069
+ 1063530032,7
1070
+ 1063530031,7
1071
+ 1063530030,7
1072
+ 1063530042,7
1073
+ 1063530029,7
1074
+ 490250005,7
1075
+ 1063530045,7
1076
+ 1550280045,7
1077
+ 1550280046,7
1078
+ 1550280047,7
1079
+ 490250019,7
1080
+ 490250018,7
1081
+ 490250017,7
1082
+ 490250015,7
1083
+ 490250014,7
1084
+ 490250013,7
1085
+ 490250012,7
1086
+ 490250011,7
1087
+ 490250010,7
1088
+ 490250009,7
1089
+ 490250008,7
1090
+ 490250007,7
1091
+ 490250006,7
1092
+ 1063530024,7
1093
+ 1550280044,7
1094
+ 1063530044,7
1095
+ 1550280043,7
1096
+ 1550280041,7
1097
+ 1063530046,7
1098
+ 1063530047,7
1099
+ 1063530048,7
1100
+ 1063530049,7
1101
+ 1063530050,7
1102
+ 1063530051,7
1103
+ 1063530052,7
1104
+ 1063530053,7
1105
+ 533970003,7
1106
+ 533970002,7
1107
+ 533970001,7
1108
+ 529150004,7
1109
+ 529150003,7
1110
+ 529150002,7
1111
+ 529150001,7
1112
+ 1550280039,7
1113
+ 1550280040,7
1114
+ 1550280042,7
1115
+ 1063530023,7
1116
+ 1550280018,7
1117
+ 1550280036,7
1118
+ 1550280006,7
1119
+ 1550280007,7
1120
+ 1550280008,7
1121
+ 1550280009,7
1122
+ 1550280010,7
1123
+ 1550280011,7
1124
+ 1550280012,7
1125
+ 1550280013,7
1126
+ 1550280014,7
1127
+ 1550280005,7
1128
+ 1550280015,7
1129
+ 1550280017,7
1130
+ 490250004,7
1131
+ 1550280019,7
1132
+ 1550280020,7
1133
+ 1550280021,7
1134
+ 1550280022,7
1135
+ 1550280023,7
1136
+ 1550280024,7
1137
+ 1550280025,7
1138
+ 1550280016,7
1139
+ 1550280004,7
1140
+ 1550280003,7
1141
+ 1550280002,7
1142
+ 1264160017,7
1143
+ 1264160016,7
1144
+ 1264160015,7
1145
+ 1033960001,7
1146
+ 1033960002,7
1147
+ 1033960003,7
1148
+ 1033960004,7
1149
+ 1033960005,7
1150
+ 1033960006,7
1151
+ 1063530001,7
1152
+ 1481400001,7
1153
+ 1481400002,7
1154
+ 1481400003,7
1155
+ 1264160014,7
1156
+ 1264160013,7
1157
+ 1264160012,7
1158
+ 1264160011,7
1159
+ 1264160010,7
1160
+ 1264160009,7
1161
+ 1264160008,7
1162
+ 1550280001,7
1163
+ 720300018,7
1164
+ 1550280037,7
1165
+ 720300017,7
1166
+ 720300015,7
1167
+ 1063530013,7
1168
+ 1063530014,7
1169
+ 1063530015,7
1170
+ 1063530016,7
1171
+ 1063530017,7
1172
+ 1063530018,7
1173
+ 1063530019,7
1174
+ 1063530020,7
1175
+ 1063530021,7
1176
+ 1063530012,7
1177
+ 1063530022,7
1178
+ 1550280027,7
1179
+ 1550280028,7
1180
+ 1550280029,7
1181
+ 1550280030,7
1182
+ 1550280031,7
1183
+ 1550280032,7
1184
+ 1550280033,7
1185
+ 1550280034,7
1186
+ 1550280035,7
1187
+ 1550280026,7
1188
+ 1063530011,7
1189
+ 1063530010,7
1190
+ 1063530009,7
1191
+ 720300014,7
1192
+ 720300013,7
1193
+ 720300012,7
1194
+ 720300011,7
1195
+ 720300010,7
1196
+ 720300009,7
1197
+ 720300008,7
1198
+ 720300007,7
1199
+ 720300006,7
1200
+ 720300005,7
1201
+ 720300004,7
1202
+ 720300003,7
1203
+ 720300002,7
1204
+ 720300001,7
1205
+ 1063530002,7
1206
+ 1063530003,7
1207
+ 1063530004,7
1208
+ 1063530005,7
1209
+ 1063530006,7
1210
+ 1063530007,7
1211
+ 1063530008,7
1212
+ 720300016,7
1213
+ 490250003,7
1214
+ 490250016,7
1215
+ 490250001,7
1216
+ 1063530069,7
1217
+ 1063530070,7
1218
+ 1063530071,7
1219
+ 1063530072,7
1220
+ 1063530073,7
1221
+ 1063530074,7
1222
+ 1063530075,7
1223
+ 1063530076,7
1224
+ 1063530077,7
1225
+ 1063530078,7
1226
+ 1063530079,7
1227
+ 1063530080,7
1228
+ 1063530081,7
1229
+ 1063530083,7
1230
+ 490250002,7
1231
+ 1676620007,7
1232
+ 1264160007,7
1233
+ 1264160006,7
1234
+ 1264160005,7
1235
+ 1676620006,7
1236
+ 1676620005,7
1237
+ 1676620004,7
1238
+ 1264160004,7
1239
+ 1264160003,7
1240
+ 1264160002,7
1241
+ 1264160001,7
1242
+ 1676620003,7
1243
+ 1676620002,7
1244
+ 1676620001,7
1245
+ 1063530068,7
1246
+ 1063530067,7
1247
+ 1063530082,7
1248
+ 1063530066,7
1249
+ 1063530061,7
1250
+ 1063530062,7
1251
+ 1063530063,7
1252
+ 1063530064,7
1253
+ 1063530065,7
1254
+ 1063530058,7
1255
+ 1063530057,7
1256
+ 1063530056,7
1257
+ 1063530055,7
1258
+ 1063530054,7
1259
+ 1063530059,7
1260
+ 1550280054,7
1261
+ 1550280048,7
1262
+ 1550280049,7
1263
+ 1550280050,7
1264
+ 1550280055,7
1265
+ 1063530060,7
1266
+ 1550280051,7
1267
+ 1550280052,7
1268
+ 1550280053,7
1269
+ 1026760019,8
1270
+ 1026760020,8
1271
+ 1026760021,8
1272
+ 1026760022,8
1273
+ 1026760027,8
1274
+ 1026760024,8
1275
+ 1026760017,8
1276
+ 1026760025,8
1277
+ 1026760016,8
1278
+ 1026760026,8
1279
+ 1026760018,8
1280
+ 1026760023,8
1281
+ 1453730002,8
1282
+ 1906880002,8
1283
+ 1906880001,8
1284
+ 1862180007,8
1285
+ 1026760015,8
1286
+ 1862180006,8
1287
+ 1862180005,8
1288
+ 1862180004,8
1289
+ 1862180003,8
1290
+ 1862180002,8
1291
+ 1862180001,8
1292
+ 1250210001,8
1293
+ 1250210002,8
1294
+ 1906880003,8
1295
+ 1026760014,8
1296
+ 1026760011,8
1297
+ 1026760012,8
1298
+ 2077870001,8
1299
+ 2077870002,8
1300
+ 2077870003,8
1301
+ 2077870004,8
1302
+ 2077870005,8
1303
+ 1453730014,8
1304
+ 1453730013,8
1305
+ 1453730012,8
1306
+ 1453730011,8
1307
+ 1453730010,8
1308
+ 1453730009,8
1309
+ 1453730008,8
1310
+ 1453730007,8
1311
+ 1453730006,8
1312
+ 1453730005,8
1313
+ 1453730004,8
1314
+ 1453730003,8
1315
+ 1026760006,8
1316
+ 1026760005,8
1317
+ 1026760004,8
1318
+ 1026760003,8
1319
+ 1453730001,8
1320
+ 1026760010,8
1321
+ 1026760009,8
1322
+ 1026760008,8
1323
+ 1026760007,8
1324
+ 866540001,8
1325
+ 866540003,8
1326
+ 866540004,8
1327
+ 1026760013,8
1328
+ 866540005,8
1329
+ 866540007,8
1330
+ 866540008,8
1331
+ 866540009,8
1332
+ 866540010,8
1333
+ 866540011,8
1334
+ 866540002,8
1335
+ 1250210003,8
1336
+ 1026760002,8
1337
+ 866540006,8
1338
+ 1250210004,8
1339
+ 1026760001,8
1340
+ 600140014,8
1341
+ 600140012,8
1342
+ 600140013,8
1343
+ 1250210005,8
1344
+ 269170002,8
1345
+ 269170003,8
1346
+ 269170004,8
1347
+ 269170005,8
1348
+ 269170006,8
1349
+ 269170007,8
1350
+ 269170009,8
1351
+ 600140015,8
1352
+ 600140016,8
1353
+ 600140017,8
1354
+ 600140018,8
1355
+ 600140019,8
1356
+ 600140020,8
1357
+ 600140021,8
1358
+ 600140022,8
1359
+ 600140023,8
1360
+ 600140024,8
1361
+ 1113370001,8
1362
+ 600140011,8
1363
+ 600140010,8
1364
+ 269170008,8
1365
+ 600140009,8
1366
+ 1113370002,8
1367
+ 269170001,8
1368
+ 1113370003,8
1369
+ 1113370004,8
1370
+ 600140001,8
1371
+ 1250210007,8
1372
+ 1250210008,8
1373
+ 1250210009,8
1374
+ 600140002,8
1375
+ 1250210006,8
1376
+ 600140003,8
1377
+ 600140008,8
1378
+ 600140007,8
1379
+ 1250210011,8
1380
+ 1250210010,8
1381
+ 1250210012,8
1382
+ 600140006,8
1383
+ 600140005,8
1384
+ 600140004,8
1385
+ 2163140007,9
1386
+ 2163140008,9
1387
+ 2163140009,9
1388
+ 2163140006,9
1389
+ 2163140010,9
1390
+ 2163140011,9
1391
+ 2163140012,9
1392
+ 518580019,9
1393
+ 518580020,9
1394
+ 518580021,9
1395
+ 518580022,9
1396
+ 518580023,9
1397
+ 518580008,9
1398
+ 518580001,9
1399
+ 518580002,9
1400
+ 518580003,9
1401
+ 518580004,9
1402
+ 518580005,9
1403
+ 518580006,9
1404
+ 518580007,9
1405
+ 2163140013,9
1406
+ 2163140016,9
1407
+ 2163140015,9
1408
+ 2163140014,9
1409
+ 2163140005,9
1410
+ 518580024,9
1411
+ 2163140004,9
1412
+ 518580015,9
1413
+ 2163140002,9
1414
+ 982710007,9
1415
+ 982710008,9
1416
+ 982710009,9
1417
+ 982710010,9
1418
+ 982710011,9
1419
+ 982710012,9
1420
+ 982710013,9
1421
+ 982710014,9
1422
+ 982710015,9
1423
+ 1298890009,9
1424
+ 1298890010,9
1425
+ 982710006,9
1426
+ 1298890012,9
1427
+ 1298890014,9
1428
+ 1298890015,9
1429
+ 1298890016,9
1430
+ 1298890017,9
1431
+ 1298890018,9
1432
+ 1298890019,9
1433
+ 1298890020,9
1434
+ 1298890021,9
1435
+ 518580011,9
1436
+ 518580010,9
1437
+ 518580009,9
1438
+ 1298890013,9
1439
+ 2163140003,9
1440
+ 982710005,9
1441
+ 982710003,9
1442
+ 2163140001,9
1443
+ 1210650002,9
1444
+ 1210650001,9
1445
+ 518580018,9
1446
+ 518580017,9
1447
+ 518580016,9
1448
+ 518580014,9
1449
+ 1270910001,9
1450
+ 1270910002,9
1451
+ 1270910003,9
1452
+ 1270910004,9
1453
+ 982710004,9
1454
+ 1270910005,9
1455
+ 1298890002,9
1456
+ 1298890003,9
1457
+ 1298890004,9
1458
+ 1298890005,9
1459
+ 1298890006,9
1460
+ 1298890007,9
1461
+ 1298890008,9
1462
+ 518580013,9
1463
+ 518580012,9
1464
+ 982710001,9
1465
+ 982710002,9
1466
+ 1298890001,9
1467
+ 2163140017,9
1468
+ 714100001,9
1469
+ 2163140019,9
1470
+ 1210650018,9
1471
+ 1210650017,9
1472
+ 1210650016,9
1473
+ 1210650015,9
1474
+ 1210650014,9
1475
+ 343740003,9
1476
+ 1210650012,9
1477
+ 1210650011,9
1478
+ 1210650010,9
1479
+ 1210650009,9
1480
+ 1210650008,9
1481
+ 1210650007,9
1482
+ 1210650006,9
1483
+ 1210650005,9
1484
+ 1210650004,9
1485
+ 1210650003,9
1486
+ 2163140058,9
1487
+ 343740002,9
1488
+ 343740001,9
1489
+ 1210650019,9
1490
+ 1210650020,9
1491
+ 714100002,9
1492
+ 714100003,9
1493
+ 714100004,9
1494
+ 714100005,9
1495
+ 714100006,9
1496
+ 714100007,9
1497
+ 714100008,9
1498
+ 1210650028,9
1499
+ 2163140057,9
1500
+ 1210650027,9
1501
+ 1210650025,9
1502
+ 1210650024,9
1503
+ 2293180001,9
1504
+ 2293180002,9
1505
+ 2293180003,9
1506
+ 1210650023,9
1507
+ 1210650022,9
1508
+ 1210650021,9
1509
+ 1210650026,9
1510
+ 2163140018,9
1511
+ 2163140056,9
1512
+ 2163140054,9
1513
+ 2163140032,9
1514
+ 2163140031,9
1515
+ 2163140030,9
1516
+ 2163140029,9
1517
+ 2163140028,9
1518
+ 2163140027,9
1519
+ 2163140026,9
1520
+ 2163140025,9
1521
+ 2163140024,9
1522
+ 2163140023,9
1523
+ 2163140022,9
1524
+ 2163140021,9
1525
+ 2163140020,9
1526
+ 343740004,9
1527
+ 343740005,9
1528
+ 343740006,9
1529
+ 343740007,9
1530
+ 2163140033,9
1531
+ 2163140034,9
1532
+ 1488730001,9
1533
+ 2163140035,9
1534
+ 2163140053,9
1535
+ 2163140052,9
1536
+ 2163140051,9
1537
+ 2163140050,9
1538
+ 2163140049,9
1539
+ 2163140048,9
1540
+ 2163140047,9
1541
+ 2163140046,9
1542
+ 2163140055,9
1543
+ 2163140045,9
1544
+ 2163140043,9
1545
+ 2163140042,9
1546
+ 2163140041,9
1547
+ 2163140040,9
1548
+ 2163140039,9
1549
+ 2163140038,9
1550
+ 2163140037,9
1551
+ 2163140036,9
1552
+ 2163140044,9
1553
+ 1210650013,9
dataset/fold_cat.csv ADDED
@@ -0,0 +1,1553 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ img_id,fold
2
+ 1210650020,0
3
+ 866540008,0
4
+ 1453730013,0
5
+ 343740001,0
6
+ 1550280034,0
7
+ 2057000087,0
8
+ 667010003,0
9
+ 1150310003,0
10
+ 1270910005,0
11
+ 1394210007,0
12
+ 2057000101,0
13
+ 1337060005,0
14
+ 1063530027,0
15
+ 600140004,0
16
+ 1063530050,0
17
+ 1337530072,0
18
+ 1550280019,0
19
+ 2057000175,0
20
+ 1298890018,0
21
+ 1705030005,0
22
+ 2057000158,0
23
+ 1245640028,0
24
+ 2025000002,0
25
+ 457380004,0
26
+ 518580020,0
27
+ 2057000141,0
28
+ 1063530025,0
29
+ 1561560017,0
30
+ 490250002,0
31
+ 997760003,0
32
+ 2057000165,0
33
+ 1172280005,0
34
+ 1801560005,0
35
+ 790750006,0
36
+ 812460002,0
37
+ 1337060017,0
38
+ 982710011,0
39
+ 1337530025,0
40
+ 1595490014,0
41
+ 1245640023,0
42
+ 2293180003,0
43
+ 2057000146,0
44
+ 1063530013,0
45
+ 898080033,0
46
+ 1337530054,0
47
+ 1245640031,0
48
+ 2057000066,0
49
+ 815280010,0
50
+ 2057000168,0
51
+ 982710013,0
52
+ 490250001,0
53
+ 1486660008,0
54
+ 1178780004,0
55
+ 600140016,0
56
+ 1026760002,0
57
+ 1113370003,0
58
+ 910190006,0
59
+ 2025000001,0
60
+ 898080014,0
61
+ 1595490011,0
62
+ 1825460005,0
63
+ 1550280040,0
64
+ 2163140037,0
65
+ 1337060013,0
66
+ 2057000070,0
67
+ 1705030004,0
68
+ 457380002,0
69
+ 2057000100,0
70
+ 898080011,0
71
+ 533970002,0
72
+ 1210650022,0
73
+ 2057000156,0
74
+ 2057000014,0
75
+ 891960004,0
76
+ 898080012,0
77
+ 2057000036,0
78
+ 716260007,0
79
+ 1063530024,0
80
+ 1245640012,0
81
+ 1026760003,0
82
+ 2057000140,0
83
+ 898080008,0
84
+ 866540002,0
85
+ 1026760014,0
86
+ 1269890008,0
87
+ 2057000113,0
88
+ 1337530082,0
89
+ 2057000019,0
90
+ 1801560002,0
91
+ 1337530073,0
92
+ 2163140019,0
93
+ 1550280047,0
94
+ 2057000149,0
95
+ 1801560007,0
96
+ 866540003,0
97
+ 1270910004,0
98
+ 591680003,0
99
+ 1270910001,0
100
+ 1178780003,0
101
+ 1150310006,0
102
+ 1550280011,0
103
+ 2224020045,0
104
+ 2057000060,0
105
+ 997760008,0
106
+ 1550280003,0
107
+ 463290001,0
108
+ 1248270006,0
109
+ 451980012,0
110
+ 1906880002,0
111
+ 1931980009,0
112
+ 1480650002,0
113
+ 1172280007,0
114
+ 726910011,0
115
+ 998660004,0
116
+ 1453730001,0
117
+ 815280014,0
118
+ 898080006,0
119
+ 457550001,0
120
+ 490250012,0
121
+ 591680007,0
122
+ 2057000005,0
123
+ 343740007,0
124
+ 2057000003,0
125
+ 1512840005,0
126
+ 997760002,0
127
+ 1337530040,0
128
+ 457380009,0
129
+ 1456280004,0
130
+ 1382350003,0
131
+ 2057000093,0
132
+ 1248270003,0
133
+ 2057000151,0
134
+ 1705030009,0
135
+ 1269890007,0
136
+ 1337530066,0
137
+ 1063530043,0
138
+ 1486660006,0
139
+ 898080021,0
140
+ 1595490015,0
141
+ 2057000067,0
142
+ 716260006,0
143
+ 660520010,0
144
+ 591680001,0
145
+ 731790004,0
146
+ 1178780008,0
147
+ 1245640027,0
148
+ 716260018,0
149
+ 1337060032,0
150
+ 2057000096,0
151
+ 1245640033,0
152
+ 1862180003,0
153
+ 898080032,0
154
+ 2163140023,0
155
+ 1805510019,0
156
+ 1550280048,0
157
+ 910190002,0
158
+ 891960002,0
159
+ 726910008,0
160
+ 2057000012,0
161
+ 451980007,0
162
+ 1481400002,0
163
+ 2224020058,0
164
+ 1337530060,0
165
+ 1561560019,0
166
+ 1488730001,0
167
+ 1595490016,0
168
+ 1269890014,0
169
+ 1337530044,0
170
+ 1931980014,0
171
+ 2308940007,0
172
+ 1805510014,0
173
+ 1404360015,0
174
+ 716260008,0
175
+ 2057000057,0
176
+ 1337060036,0
177
+ 1707840003,0
178
+ 2057000129,0
179
+ 1550280015,0
180
+ 2025000003,0
181
+ 518580008,0
182
+ 2163140020,0
183
+ 1550280002,0
184
+ 716260020,0
185
+ 1801560009,0
186
+ 1862180006,0
187
+ 1652800006,0
188
+ 1695870003,0
189
+ 2163140002,0
190
+ 1931980006,0
191
+ 2057000132,0
192
+ 2057000091,0
193
+ 891960001,0
194
+ 1801560003,0
195
+ 1172280006,0
196
+ 660520015,0
197
+ 898080002,0
198
+ 1026760022,0
199
+ 667010007,0
200
+ 438100006,0
201
+ 457380018,0
202
+ 343740004,0
203
+ 1245640007,0
204
+ 1337530002,0
205
+ 1652800003,0
206
+ 528580001,0
207
+ 1245640021,0
208
+ 815280007,0
209
+ 1480650007,0
210
+ 2057000170,0
211
+ 1063530080,0
212
+ 2163140010,0
213
+ 714100008,0
214
+ 2057000038,0
215
+ 1453730011,0
216
+ 600140001,0
217
+ 720300005,0
218
+ 605850001,0
219
+ 1550280044,0
220
+ 269170004,0
221
+ 1550280046,0
222
+ 1805510031,0
223
+ 2057000157,0
224
+ 1707840005,0
225
+ 726910009,0
226
+ 910190005,0
227
+ 1561560009,0
228
+ 451980017,0
229
+ 2163140008,0
230
+ 1561560030,0
231
+ 1595490013,0
232
+ 720300009,0
233
+ 891960005,0
234
+ 997760001,0
235
+ 2057000172,0
236
+ 1931980012,0
237
+ 2057000125,0
238
+ 490250019,0
239
+ 2057000085,0
240
+ 1705030016,0
241
+ 1730290001,0
242
+ 529150001,0
243
+ 518580009,0
244
+ 1157070005,0
245
+ 2057000016,0
246
+ 1453730014,0
247
+ 2224020001,0
248
+ 1063530034,0
249
+ 2057000090,0
250
+ 1250210001,0
251
+ 269170008,0
252
+ 790750004,0
253
+ 1705030015,0
254
+ 1150310002,0
255
+ 1337530004,0
256
+ 982710004,0
257
+ 451980010,0
258
+ 1337530030,0
259
+ 2163140045,0
260
+ 1248270002,0
261
+ 343740005,0
262
+ 2057000164,0
263
+ 457380001,0
264
+ 2057000139,0
265
+ 660520013,0
266
+ 1394410004,0
267
+ 1805510022,0
268
+ 982710015,0
269
+ 438100010,0
270
+ 1250210009,0
271
+ 731790007,0
272
+ 660520001,0
273
+ 1337060041,0
274
+ 716260019,0
275
+ 1337530012,0
276
+ 716260011,0
277
+ 898080034,0
278
+ 1561560020,0
279
+ 1337060039,0
280
+ 1269890015,0
281
+ 2308940008,0
282
+ 1157070006,0
283
+ 1245640017,0
284
+ 2163140052,0
285
+ 1298890005,0
286
+ 457380005,0
287
+ 463290006,0
288
+ 518580012,0
289
+ 1707840002,0
290
+ 2057000072,0
291
+ 720300014,0
292
+ 2057000116,0
293
+ 1298890007,0
294
+ 463290007,0
295
+ 451980018,0
296
+ 1245640018,0
297
+ 2077870005,0
298
+ 2057000044,0
299
+ 1337530071,0
300
+ 982710010,0
301
+ 1337060006,0
302
+ 1801560011,0
303
+ 1825460008,0
304
+ 1334900001,0
305
+ 2057000073,0
306
+ 1063530037,0
307
+ 866540005,0
308
+ 1805510013,0
309
+ 2163140056,0
310
+ 1931980008,0
311
+ 1026760009,0
312
+ 2057000105,0
313
+ 898080013,0
314
+ 1453730007,0
315
+ 2057000144,0
316
+ 1178780013,0
317
+ 1063530028,0
318
+ 2057000011,0
319
+ 1063530051,0
320
+ 2057000098,0
321
+ 1178780007,0
322
+ 2308940004,0
323
+ 1150310001,0
324
+ 1595490005,0
325
+ 2057000025,0
326
+ 2057000114,0
327
+ 1245640001,0
328
+ 2163140036,0
329
+ 1453730012,0
330
+ 1063530082,0
331
+ 2308940006,0
332
+ 1862180004,0
333
+ 2057000147,0
334
+ 1210650009,0
335
+ 490250011,0
336
+ 1705030012,0
337
+ 1550280050,0
338
+ 1264160014,0
339
+ 1931980013,0
340
+ 2057000034,0
341
+ 2089520002,0
342
+ 1337530026,0
343
+ 2163140001,0
344
+ 731790003,0
345
+ 2057000099,0
346
+ 716260014,0
347
+ 1026760021,0
348
+ 2077870004,0
349
+ 667010006,0
350
+ 1601810002,0
351
+ 1550280033,0
352
+ 1561560008,0
353
+ 2163140003,0
354
+ 1063530039,0
355
+ 982710003,0
356
+ 1595490012,0
357
+ 1480650001,0
358
+ 1801560004,0
359
+ 898080020,0
360
+ 1026760006,0
361
+ 1113370001,0
362
+ 2293180002,0
363
+ 1063530064,0
364
+ 1264160016,0
365
+ 1652800008,0
366
+ 1960620001,0
367
+ 790750001,0
368
+ 1486660004,0
369
+ 2020950002,0
370
+ 1026760018,0
371
+ 1550280006,0
372
+ 1178780014,0
373
+ 2057000109,0
374
+ 2057000137,0
375
+ 898080007,0
376
+ 1172280001,0
377
+ 667010008,0
378
+ 2293180001,0
379
+ 2163140032,0
380
+ 997760005,0
381
+ 660520004,0
382
+ 1250210010,0
383
+ 2025000008,0
384
+ 982710007,0
385
+ 1404360011,0
386
+ 1334900002,0
387
+ 1805510029,0
388
+ 1178780006,0
389
+ 1805510005,0
390
+ 1512840007,0
391
+ 1172280003,0
392
+ 1250210007,0
393
+ 1404360026,0
394
+ 812460001,0
395
+ 1157070002,0
396
+ 1595490009,0
397
+ 1337060003,0
398
+ 1595490003,0
399
+ 438100005,0
400
+ 1862180001,0
401
+ 1245640019,0
402
+ 1404360003,0
403
+ 1210650019,0
404
+ 1707840006,0
405
+ 1248270001,0
406
+ 1550280017,0
407
+ 898080009,0
408
+ 2057000022,0
409
+ 2057000035,0
410
+ 2308940009,0
411
+ 1113370004,0
412
+ 1245640013,0
413
+ 1486660003,0
414
+ 1337530032,0
415
+ 726910001,0
416
+ 667010002,0
417
+ 451980016,0
418
+ 2089520001,0
419
+ 2057000076,0
420
+ 2163140041,0
421
+ 1550280024,0
422
+ 2163140035,0
423
+ 2163140005,0
424
+ 451980004,0
425
+ 463290005,0
426
+ 2057000161,0
427
+ 1595490017,0
428
+ 998660001,0
429
+ 457380019,0
430
+ 1337530043,0
431
+ 1595490018,0
432
+ 2057000136,0
433
+ 2057000167,0
434
+ 1210650018,0
435
+ 1825460007,0
436
+ 982710002,0
437
+ 1561560027,0
438
+ 1652800001,0
439
+ 2057000148,0
440
+ 529150002,0
441
+ 1960620016,0
442
+ 1063530078,0
443
+ 667010011,0
444
+ 1960620010,0
445
+ 1337530019,0
446
+ 1404360025,0
447
+ 1210650021,0
448
+ 667010012,0
449
+ 600140011,0
450
+ 731790001,0
451
+ 490250009,0
452
+ 2057000020,0
453
+ 815280004,0
454
+ 2057000145,0
455
+ 1063530047,0
456
+ 1453730002,0
457
+ 1705030010,0
458
+ 1337530007,0
459
+ 2163140044,0
460
+ 1805510028,0
461
+ 2057000102,0
462
+ 910190003,0
463
+ 1394210003,0
464
+ 2057000043,0
465
+ 2057000108,0
466
+ 529150003,0
467
+ 451980008,0
468
+ 2057000150,0
469
+ 1456280001,0
470
+ 1595490010,0
471
+ 2057000041,0
472
+ 716260021,0
473
+ 2163140051,0
474
+ 1453730005,0
475
+ 591680002,0
476
+ 1486660001,0
477
+ 451980014,0
478
+ 518580007,0
479
+ 1652800010,0
480
+ 1178780001,0
481
+ 2057000084,0
482
+ 2057000021,0
483
+ 1210650007,0
484
+ 997760012,0
485
+ 1931980005,0
486
+ 1337530062,0
487
+ 982710012,0
488
+ 1550280008,0
489
+ 1063530041,0
490
+ 1486660013,0
491
+ 1394210005,0
492
+ 720300007,0
493
+ 1210650017,0
494
+ 1337530076,0
495
+ 1269890005,0
496
+ 600140003,0
497
+ 1245640020,0
498
+ 1033960004,0
499
+ 1157070003,0
500
+ 815280018,0
501
+ 1245640029,0
502
+ 1033960002,0
503
+ 1210650010,0
504
+ 528580002,0
505
+ 1269890009,0
506
+ 1269890013,0
507
+ 982710009,0
508
+ 457550005,0
509
+ 815280003,0
510
+ 1033960006,0
511
+ 898080004,0
512
+ 457380012,0
513
+ 716260009,0
514
+ 1157070004,0
515
+ 1550280023,0
516
+ 451980011,0
517
+ 716260010,0
518
+ 2057000163,0
519
+ 1456280002,0
520
+ 1337530064,0
521
+ 457380006,0
522
+ 716260001,0
523
+ 2057000032,0
524
+ 1033960005,0
525
+ 2057000071,0
526
+ 1705030013,0
527
+ 451980001,0
528
+ 1063530081,0
529
+ 898080035,0
530
+ 2057000055,0
531
+ 2057000024,0
532
+ 1337530055,0
533
+ 1063530076,0
534
+ 269170003,0
535
+ 1561560023,0
536
+ 1210650008,0
537
+ 1394210009,0
538
+ 2224020031,0
539
+ 1250210002,0
540
+ 1801560001,0
541
+ 622310002,0
542
+ 1026760004,0
543
+ 1245640002,0
544
+ 1245640035,0
545
+ 898080010,0
546
+ 1178780005,0
547
+ 1805510027,0
548
+ 2057000039,0
549
+ 1561560018,0
550
+ 1825460012,0
551
+ 2077870002,0
552
+ 1337530065,0
553
+ 2057000008,0
554
+ 1805510023,0
555
+ 891960007,0
556
+ 2057000112,0
557
+ 1394210006,0
558
+ 2057000069,0
559
+ 1337530042,0
560
+ 667010010,0
561
+ 997760010,0
562
+ 1805510033,0
563
+ 1456280003,0
564
+ 2057000154,0
565
+ 2163140009,0
566
+ 490250006,0
567
+ 1394210004,0
568
+ 2057000131,0
569
+ 1337530001,0
570
+ 1801560010,0
571
+ 1805510007,0
572
+ 726910006,0
573
+ 1063530060,0
574
+ 457380007,0
575
+ 1063530046,0
576
+ 1269890004,0
577
+ 1264160017,0
578
+ 2163140021,0
579
+ 2163140054,0
580
+ 529150004,0
581
+ 898080016,0
582
+ 815280002,0
583
+ 1063530055,0
584
+ 1245640008,0
585
+ 457380014,0
586
+ 2089520004,0
587
+ 660520007,0
588
+ 1063530038,0
589
+ 1404360013,0
590
+ 790750005,0
591
+ 1337530016,0
592
+ 2057000045,0
593
+ 2057000176,0
594
+ 1512840004,0
595
+ 731790008,0
596
+ 1805510004,0
597
+ 1026760001,0
598
+ 490250010,0
599
+ 1676620001,0
600
+ 1269890006,0
601
+ 1217930001,0
602
+ 866540011,0
603
+ 1298890002,0
604
+ 720300004,0
605
+ 518580017,0
606
+ 1026760012,0
607
+ 1250210005,0
608
+ 1337530036,0
609
+ 1337530031,0
610
+ 1486660014,0
611
+ 1337060034,0
612
+ 2057000092,0
613
+ 866540007,0
614
+ 720300013,0
615
+ 1026760015,0
616
+ 2308940011,0
617
+ 600140015,0
618
+ 1561560024,0
619
+ 1485260003,0
620
+ 898080015,0
621
+ 518580014,0
622
+ 1612820001,0
623
+ 2057000110,0
624
+ 1931980001,0
625
+ 1601810003,0
626
+ 2057000130,0
627
+ 1695870001,0
628
+ 720300010,0
629
+ 998660005,0
630
+ 2163140047,0
631
+ 2057000160,0
632
+ 1486660011,0
633
+ 2163140031,0
634
+ 667010001,0
635
+ 1337060040,0
636
+ 1595490020,0
637
+ 2057000018,0
638
+ 1595490007,0
639
+ 1245640003,0
640
+ 1033960003,0
641
+ 1825460004,0
642
+ 1337530059,0
643
+ 866540004,0
644
+ 1337530038,0
645
+ 1394210008,0
646
+ 1063530030,0
647
+ 2057000089,0
648
+ 518580022,0
649
+ 1825460001,0
650
+ 1550280020,0
651
+ 1337530035,0
652
+ 1337060028,0
653
+ 2057000080,0
654
+ 1245640004,0
655
+ 1705030006,0
656
+ 1485260004,0
657
+ 1157070007,0
658
+ 2057000051,0
659
+ 1250210008,0
660
+ 2057000115,0
661
+ 1063530031,0
662
+ 1264160001,0
663
+ 343740006,0
664
+ 731790005,0
665
+ 660520006,0
666
+ 2057000111,0
667
+ 1063530068,0
668
+ 1512840001,0
669
+ 2224020002,0
670
+ 2057000128,0
671
+ 2057000037,0
672
+ 898080017,0
673
+ 622310001,0
674
+ 2057000103,0
675
+ 1404360019,0
676
+ 1178780011,0
677
+ 1337060029,0
678
+ 2163140026,0
679
+ 1730290002,0
680
+ 1264160015,0
681
+ 898080024,0
682
+ 660520014,0
683
+ 1486660005,0
684
+ 2163140013,0
685
+ 1550280032,0
686
+ 2193270001,0
687
+ 2163140018,0
688
+ 2057000068,0
689
+ 2163140049,0
690
+ 1486660009,0
691
+ 490250008,0
692
+ 716260013,0
693
+ 1337530051,0
694
+ 1550280016,0
695
+ 2163140027,0
696
+ 720300016,0
697
+ 1705030008,0
698
+ 2057000106,0
699
+ 1210650006,0
700
+ 1337530081,0
701
+ 1730290004,0
702
+ 1337530033,0
703
+ 1485260002,0
704
+ 457380015,0
705
+ 2057000054,0
706
+ 1652800012,0
707
+ 2025000007,0
708
+ 2057000077,0
709
+ 1612820002,0
710
+ 891960008,0
711
+ 1063530040,0
712
+ 1652800009,0
713
+ 1862180007,0
714
+ 1063530071,0
715
+ 600140006,0
716
+ 1063530035,0
717
+ 1245640024,0
718
+ 457380016,0
719
+ 2057000026,0
720
+ 1960620015,0
721
+ 451980002,0
722
+ 1695870006,0
723
+ 1264160002,0
724
+ 660520011,0
725
+ 463290004,0
726
+ 1063530077,0
727
+ 2057000017,0
728
+ 1404360001,0
729
+ 1862180002,0
730
+ 631660001,0
731
+ 1250210003,0
732
+ 1026760025,0
733
+ 898080003,0
734
+ 2057000153,0
735
+ 1063530056,0
736
+ 997760011,0
737
+ 1178780012,0
738
+ 1550280028,0
739
+ 898080029,0
740
+ 1298890003,0
741
+ 1337530029,0
742
+ 1960620012,0
743
+ 667010009,0
744
+ 518580021,0
745
+ 731790010,0
746
+ 1931980002,0
747
+ 1695870005,0
748
+ 2308940014,0
749
+ 497820002,0
750
+ 1825460006,0
751
+ 2057000171,0
752
+ 997760007,0
753
+ 910190007,0
754
+ 2057000095,0
755
+ 1382350005,0
756
+ 1217930004,0
757
+ 1026760005,0
758
+ 2057000002,0
759
+ 2308940002,0
760
+ 1337530047,0
761
+ 1217930003,0
762
+ 2308940010,0
763
+ 2163140030,0
764
+ 1337530039,0
765
+ 982710014,0
766
+ 513490001,0
767
+ 910190004,0
768
+ 998660002,0
769
+ 451980005,0
770
+ 997760009,0
771
+ 1210650015,0
772
+ 451980003,0
773
+ 2057000097,0
774
+ 898080025,0
775
+ 982710001,0
776
+ 1931980007,0
777
+ 716260012,0
778
+ 2089520003,0
779
+ 1825460011,0
780
+ 1178780016,0
781
+ 2308940012,0
782
+ 898080036,0
783
+ 2057000107,0
784
+ 497820001,0
785
+ 457380003,0
786
+ 2057000050,0
787
+ 1113370002,0
788
+ 1063530045,0
789
+ 343740003,0
790
+ 1063530049,0
791
+ 1178780010,0
792
+ 1269890010,0
793
+ 1404360024,0
794
+ 2057000031,0
795
+ 269170006,0
796
+ 1550280007,0
797
+ 1063530072,0
798
+ 1652800011,0
799
+ 2057000033,0
800
+ 1707840001,0
801
+ 2308940003,0
802
+ 910190001,0
803
+ 2057000015,0
804
+ 1960620008,0
805
+ 2057000173,0
806
+ 2057000166,0
807
+ 1805510032,0
808
+ 2057000086,0
809
+ 1595490006,0
810
+ 1178780009,0
811
+ 714100003,0
812
+ 1481400001,0
813
+ 1337530052,0
814
+ 891960003,0
815
+ 1707840004,0
816
+ 997760004,0
817
+ 1210650004,0
818
+ 815280016,0
819
+ 451980006,0
820
+ 1550280009,0
821
+ 1960620013,0
822
+ 1485260001,0
823
+ 714100002,0
824
+ 660520002,0
825
+ 1150310005,0
826
+ 1337530011,0
827
+ 1705030014,0
828
+ 2057000048,0
829
+ 1550280021,0
830
+ 891960006,0
831
+ 731790002,0
832
+ 716260017,0
833
+ 1705030001,0
834
+ 1063530053,0
835
+ 1652800013,0
836
+ 720300006,0
837
+ 815280001,0
838
+ 269170002,0
839
+ 866540009,0
840
+ 2224020003,0
841
+ 490250013,0
842
+ 1382350001,0
843
+ 1512840002,0
844
+ 716260015,0
845
+ 600140014,0
846
+ 1210650003,0
847
+ 1805510020,0
848
+ 2057000065,0
849
+ 1248270007,0
850
+ 438100009,0
851
+ 490250018,0
852
+ 1337530074,0
853
+ 2057000075,0
854
+ 1157070008,0
855
+ 2163140017,0
856
+ 1337530053,0
857
+ 1931980004,0
858
+ 1453730008,0
859
+ 1801560008,0
860
+ 1595490004,0
861
+ 1063530061,0
862
+ 1337060027,0
863
+ 2057000056,0
864
+ 1248270009,0
865
+ 1825460010,0
866
+ 1063530063,0
867
+ 1805510024,0
868
+ 1404360027,0
869
+ 1550280026,0
870
+ 1063530005,0
871
+ 2308940001,0
872
+ 269170005,0
873
+ 1394210010,0
874
+ 1705030002,0
875
+ 1245640022,0
876
+ 1550280055,0
877
+ 891960009,0
878
+ 1652800007,0
879
+ 438100002,0
880
+ 2057000104,0
881
+ 1931980003,0
882
+ 1486660002,0
883
+ 269170007,0
884
+ 1337530021,0
885
+ 1337530020,0
886
+ 1561560029,0
887
+ 1033960001,0
888
+ 2057000023,0
889
+ 1269890011,0
890
+ 1245640011,0
891
+ 1906880001,0
892
+ 2020950001,0
893
+ 1601810001,0
894
+ 457380011,0
895
+ 1394210001,0
896
+ 2308940016,0
897
+ 1486660007,0
898
+ 438100001,0
899
+ 1550280014,0
900
+ 1550280025,0
901
+ 1550280004,0
902
+ 1705030007,0
903
+ 997760006,0
904
+ 720300018,0
905
+ 1026760024,0
906
+ 866540006,0
907
+ 898080005,0
908
+ 1805510030,0
909
+ 1063530003,0
910
+ 1298890001,0
911
+ 1210650001,0
912
+ 2057000052,0
913
+ 1250210011,0
914
+ 1394410003,0
915
+ 1906880003,0
916
+ 1250210006,0
917
+ 2057000094,0
918
+ 1337530080,0
919
+ 1561560022,0
920
+ 1248270013,0
921
+ 2057000042,0
922
+ 1217930005,0
923
+ 1298890017,0
924
+ 815280009,0
925
+ 2057000061,0
926
+ 720300011,0
927
+ 714100001,0
928
+ 1486660010,0
929
+ 1337530028,0
930
+ 898080023,0
931
+ 2057000081,0
932
+ 1270910003,0
933
+ 2308940017,0
934
+ 1337530008,0
935
+ 2057000047,0
936
+ 2057000135,0
937
+ 1245640038,0
938
+ 1561560026,0
939
+ 1337060002,0
940
+ 731790009,0
941
+ 1026760016,0
942
+ 1264160013,0
943
+ 716260004,0
944
+ 2057000079,0
945
+ 1337530058,0
946
+ 1561560004,0
947
+ 1801560006,0
948
+ 1404360002,0
949
+ 1652800004,1
950
+ 1652800005,1
951
+ 1707840008,1
952
+ 591680008,1
953
+ 1394410001,1
954
+ 1394410002,1
955
+ 1394410005,1
956
+ 1063530006,1
957
+ 1063530017,1
958
+ 438100004,1
959
+ 1063530020,1
960
+ 1063530022,1
961
+ 438100007,1
962
+ 438100008,1
963
+ 1063530026,1
964
+ 1063530033,1
965
+ 1270910002,1
966
+ 1695870002,1
967
+ 1695870004,1
968
+ 1063530036,1
969
+ 1063530042,1
970
+ 1063530044,1
971
+ 2057000001,1
972
+ 660520003,1
973
+ 2057000006,1
974
+ 1063530054,1
975
+ 660520008,1
976
+ 2057000010,1
977
+ 1063530058,1
978
+ 660520012,1
979
+ 1063530062,1
980
+ 1295570001,1
981
+ 2224020049,1
982
+ 533970001,1
983
+ 533970003,1
984
+ 1063530066,1
985
+ 513490002,1
986
+ 1063530075,1
987
+ 343740002,1
988
+ 2057000040,1
989
+ 2057000046,1
990
+ 2057000049,1
991
+ 1150310004,1
992
+ 2057000058,1
993
+ 2057000078,1
994
+ 2057000082,1
995
+ 1337530015,1
996
+ 1337060001,1
997
+ 1337060004,1
998
+ 1862180005,1
999
+ 1337060014,1
1000
+ 716260016,1
1001
+ 457380017,1
1002
+ 1337060016,1
1003
+ 1157070001,1
1004
+ 731790006,1
1005
+ 1337530045,1
1006
+ 1337060031,1
1007
+ 1337060033,1
1008
+ 1172280002,1
1009
+ 1481400003,1
1010
+ 1172280004,1
1011
+ 1172280008,1
1012
+ 1172280009,1
1013
+ 891960010,1
1014
+ 1337530056,1
1015
+ 1595490001,1
1016
+ 1394210002,1
1017
+ 1453730003,1
1018
+ 1250210004,1
1019
+ 1337530068,1
1020
+ 1453730006,1
1021
+ 2057000152,1
1022
+ 2057000155,1
1023
+ 1250210012,1
1024
+ 1337530078,1
1025
+ 2057000159,1
1026
+ 1676620002,1
1027
+ 1595490019,1
1028
+ 2308940005,1
1029
+ 1485260005,1
1030
+ 451980009,1
1031
+ 2057000169,1
1032
+ 1931980010,1
1033
+ 2308940013,1
1034
+ 451980013,1
1035
+ 2308940015,1
1036
+ 451980015,1
1037
+ 2057000174,1
1038
+ 2057000177,1
1039
+ 600140019,1
1040
+ 898080001,1
1041
+ 1264160012,1
1042
+ 1217930002,1
1043
+ 490250007,1
1044
+ 898080030,1
1045
+ 1298890015,1
1046
+ 898080031,1
1047
+ 490250016,1
1048
+ 714100005,1
1049
+ 518580013,1
1050
+ 790750002,1
1051
+ 790750003,1
1052
+ 518580018,1
1053
+ 518580019,1
1054
+ 1512840003,1
1055
+ 1550280005,1
1056
+ 1512840006,1
1057
+ 1026760007,1
1058
+ 1026760008,1
1059
+ 1026760010,1
1060
+ 1245640010,1
1061
+ 1026760013,1
1062
+ 1730290003,1
1063
+ 1730290005,1
1064
+ 1550280022,1
1065
+ 1245640025,1
1066
+ 1245640026,1
1067
+ 1026760026,1
1068
+ 1026760027,1
1069
+ 1245640030,1
1070
+ 1178780002,1
1071
+ 497820003,1
1072
+ 1550280038,1
1073
+ 1705030003,1
1074
+ 1550280054,1
1075
+ 1705030011,1
1076
+ 1805510017,1
1077
+ 1805510018,1
1078
+ 815280005,1
1079
+ 1805510025,1
1080
+ 815280011,1
1081
+ 815280013,1
1082
+ 815280015,1
1083
+ 815280017,1
1084
+ 1210650016,1
1085
+ 998660003,1
1086
+ 2163140028,1
1087
+ 1561560003,1
1088
+ 2163140040,1
1089
+ 2163140046,1
1090
+ 1269890001,1
1091
+ 667010004,1
1092
+ 1561560021,1
1093
+ 667010005,1
1094
+ 1561560025,1
1095
+ 1269890012,1
1096
+ 1561560028,1
1097
+ 1561560032,1
1098
+ 866540001,1
1099
+ 1960620002,1
1100
+ 720300001,1
1101
+ 720300002,1
1102
+ 866540010,1
1103
+ 1960620011,1
1104
+ 1960620014,1
1105
+ 720300015,1
1106
+ 2020950003,1
1107
+ 438100003,2
1108
+ 1388030001,2
1109
+ 457380008,2
1110
+ 457380010,2
1111
+ 457380013,2
1112
+ 457550002,2
1113
+ 457550003,2
1114
+ 457550004,2
1115
+ 1453730004,2
1116
+ 1453730009,2
1117
+ 1453730010,2
1118
+ 600140002,2
1119
+ 1676620003,2
1120
+ 1676620004,2
1121
+ 1676620005,2
1122
+ 1676620006,2
1123
+ 1676620007,2
1124
+ 600140005,2
1125
+ 600140007,2
1126
+ 600140008,2
1127
+ 600140009,2
1128
+ 600140010,2
1129
+ 600140012,2
1130
+ 600140013,2
1131
+ 600140017,2
1132
+ 600140018,2
1133
+ 600140020,2
1134
+ 600140021,2
1135
+ 600140022,2
1136
+ 600140023,2
1137
+ 600140024,2
1138
+ 1178780015,2
1139
+ 1486660012,2
1140
+ 1652800002,2
1141
+ 591680004,2
1142
+ 591680005,2
1143
+ 591680006,2
1144
+ 1707840007,2
1145
+ 591680009,2
1146
+ 591680010,2
1147
+ 591680011,2
1148
+ 591680012,2
1149
+ 591680013,2
1150
+ 2224020004,2
1151
+ 2224020005,2
1152
+ 2224020006,2
1153
+ 2224020007,2
1154
+ 2224020008,2
1155
+ 2224020009,2
1156
+ 2224020010,2
1157
+ 2224020011,2
1158
+ 2224020012,2
1159
+ 2224020013,2
1160
+ 2224020014,2
1161
+ 2224020015,2
1162
+ 2224020016,2
1163
+ 2224020017,2
1164
+ 2224020018,2
1165
+ 2224020019,2
1166
+ 2224020020,2
1167
+ 2224020021,2
1168
+ 2224020022,2
1169
+ 2224020023,2
1170
+ 2224020024,2
1171
+ 2224020025,2
1172
+ 2224020026,2
1173
+ 2224020027,2
1174
+ 2224020028,2
1175
+ 2224020029,2
1176
+ 2224020030,2
1177
+ 2224020032,2
1178
+ 2224020033,2
1179
+ 2224020034,2
1180
+ 2224020035,2
1181
+ 2224020036,2
1182
+ 2224020037,2
1183
+ 2224020038,2
1184
+ 2224020039,2
1185
+ 2224020040,2
1186
+ 2224020041,2
1187
+ 2224020042,2
1188
+ 2224020043,2
1189
+ 2224020044,2
1190
+ 2224020046,2
1191
+ 2224020047,2
1192
+ 2224020048,2
1193
+ 2224020050,2
1194
+ 2224020051,2
1195
+ 2224020052,2
1196
+ 2224020053,2
1197
+ 2224020054,2
1198
+ 2224020055,2
1199
+ 2224020056,2
1200
+ 2224020057,2
1201
+ 716260002,2
1202
+ 716260003,2
1203
+ 716260005,2
1204
+ 1337060007,2
1205
+ 1337060008,2
1206
+ 1337060009,2
1207
+ 1337060010,2
1208
+ 1337060011,2
1209
+ 1337060012,2
1210
+ 1337060015,2
1211
+ 1337060018,2
1212
+ 1337060019,2
1213
+ 1337060020,2
1214
+ 1337060021,2
1215
+ 1337060022,2
1216
+ 1337060023,2
1217
+ 1337060024,2
1218
+ 1337060025,2
1219
+ 1337060026,2
1220
+ 1337060030,2
1221
+ 1337060035,2
1222
+ 954160001,2
1223
+ 954160002,2
1224
+ 1561560001,2
1225
+ 1561560002,2
1226
+ 1561560005,2
1227
+ 1561560006,2
1228
+ 1561560007,2
1229
+ 1561560010,2
1230
+ 1561560011,2
1231
+ 1561560012,2
1232
+ 1561560013,2
1233
+ 1561560014,2
1234
+ 1561560015,2
1235
+ 1561560016,2
1236
+ 1269890002,2
1237
+ 1269890003,2
1238
+ 1561560031,2
1239
+ 726910002,2
1240
+ 726910003,2
1241
+ 726910004,2
1242
+ 726910005,2
1243
+ 726910007,2
1244
+ 726910010,2
1245
+ 2025000004,2
1246
+ 2025000005,2
1247
+ 2025000006,2
1248
+ 2057000004,2
1249
+ 2057000007,2
1250
+ 2025000009,2
1251
+ 2025000010,2
1252
+ 2057000009,2
1253
+ 660520005,2
1254
+ 2057000013,2
1255
+ 660520009,2
1256
+ 2057000027,2
1257
+ 2057000028,2
1258
+ 2057000029,2
1259
+ 2057000030,2
1260
+ 2057000053,2
1261
+ 2057000059,2
1262
+ 2057000062,2
1263
+ 2057000063,2
1264
+ 2057000064,2
1265
+ 2057000074,2
1266
+ 2057000083,2
1267
+ 2057000088,2
1268
+ 1382350002,2
1269
+ 1382350004,2
1270
+ 2057000117,2
1271
+ 2057000118,2
1272
+ 2057000119,2
1273
+ 2057000120,2
1274
+ 2057000121,2
1275
+ 2057000122,2
1276
+ 2057000123,2
1277
+ 2057000124,2
1278
+ 1382350006,2
1279
+ 2057000126,2
1280
+ 2057000127,2
1281
+ 1382350007,2
1282
+ 1382350008,2
1283
+ 2057000133,2
1284
+ 2057000134,2
1285
+ 2057000138,2
1286
+ 2057000142,2
1287
+ 2057000143,2
1288
+ 2057000162,2
1289
+ 1931980011,2
1290
+ 2057000178,2
1291
+ 1264160003,2
1292
+ 1264160004,2
1293
+ 1264160005,2
1294
+ 1264160006,2
1295
+ 1264160007,2
1296
+ 1264160008,2
1297
+ 1264160009,2
1298
+ 1264160010,2
1299
+ 1264160011,2
1300
+ 898080018,2
1301
+ 1480650003,2
1302
+ 1298890004,2
1303
+ 1480650004,2
1304
+ 1298890006,2
1305
+ 1480650005,2
1306
+ 1298890008,2
1307
+ 1298890009,2
1308
+ 1298890010,2
1309
+ 1480650006,2
1310
+ 1298890012,2
1311
+ 1298890013,2
1312
+ 1298890014,2
1313
+ 1480650008,2
1314
+ 1298890016,2
1315
+ 898080026,2
1316
+ 898080027,2
1317
+ 1298890019,2
1318
+ 1298890020,2
1319
+ 1298890021,2
1320
+ 1334900006,2
1321
+ 1334900003,2
1322
+ 1334900004,2
1323
+ 1334900005,2
1324
+ 714100004,2
1325
+ 714100006,2
1326
+ 714100007,2
1327
+ 1550280001,2
1328
+ 1404360004,2
1329
+ 1245640005,2
1330
+ 1245640006,2
1331
+ 1404360005,2
1332
+ 1404360006,2
1333
+ 1245640009,2
1334
+ 1404360007,2
1335
+ 1026760011,2
1336
+ 1404360009,2
1337
+ 1404360010,2
1338
+ 1245640014,2
1339
+ 1245640015,2
1340
+ 1245640016,2
1341
+ 1026760017,2
1342
+ 1404360016,2
1343
+ 1026760019,2
1344
+ 1026760020,2
1345
+ 1404360018,2
1346
+ 1404360022,2
1347
+ 1026760023,2
1348
+ 1404360023,2
1349
+ 1550280018,2
1350
+ 269170001,2
1351
+ 1550280027,2
1352
+ 269170009,2
1353
+ 1404360029,2
1354
+ 1550280029,2
1355
+ 1550280030,2
1356
+ 1245640032,2
1357
+ 1550280031,2
1358
+ 1245640034,2
1359
+ 1550280035,2
1360
+ 1245640036,2
1361
+ 1245640037,2
1362
+ 1550280036,2
1363
+ 1550280037,2
1364
+ 1550280039,2
1365
+ 1550280041,2
1366
+ 1550280042,2
1367
+ 1550280043,2
1368
+ 1550280045,2
1369
+ 1550280049,2
1370
+ 1805510001,2
1371
+ 1550280051,2
1372
+ 1550280052,2
1373
+ 1550280053,2
1374
+ 1805510002,2
1375
+ 1805510003,2
1376
+ 1805510006,2
1377
+ 1805510008,2
1378
+ 1805510009,2
1379
+ 1805510010,2
1380
+ 1805510011,2
1381
+ 1805510012,2
1382
+ 1805510015,2
1383
+ 1805510016,2
1384
+ 1805510021,2
1385
+ 1805510026,2
1386
+ 1210650002,2
1387
+ 1210650005,2
1388
+ 1210650011,2
1389
+ 1210650012,2
1390
+ 1210650013,2
1391
+ 1210650014,2
1392
+ 1550280010,2
1393
+ 528580003,2
1394
+ 2163140004,2
1395
+ 528580004,2
1396
+ 2163140006,2
1397
+ 1210650023,2
1398
+ 1210650024,2
1399
+ 1210650025,2
1400
+ 1210650026,2
1401
+ 1210650027,2
1402
+ 1210650028,2
1403
+ 1550280012,2
1404
+ 1550280013,2
1405
+ 2163140007,2
1406
+ 2163140011,2
1407
+ 2163140012,2
1408
+ 2163140014,2
1409
+ 2163140015,2
1410
+ 2163140016,2
1411
+ 2163140022,2
1412
+ 2163140024,2
1413
+ 2163140025,2
1414
+ 2163140029,2
1415
+ 2163140033,2
1416
+ 2163140034,2
1417
+ 2163140038,2
1418
+ 2163140039,2
1419
+ 2163140042,2
1420
+ 2163140043,2
1421
+ 2163140048,2
1422
+ 758210001,2
1423
+ 2163140050,2
1424
+ 758210002,2
1425
+ 758210003,2
1426
+ 2163140053,2
1427
+ 758210004,2
1428
+ 2163140055,2
1429
+ 758210005,2
1430
+ 2163140057,2
1431
+ 2163140058,2
1432
+ 758210006,2
1433
+ 758210007,2
1434
+ 758210008,2
1435
+ 758210009,2
1436
+ 758210010,2
1437
+ 758210011,2
1438
+ 758210012,2
1439
+ 758210013,2
1440
+ 758210014,2
1441
+ 1063530001,2
1442
+ 1063530002,2
1443
+ 1063530004,2
1444
+ 1063530007,2
1445
+ 1063530008,2
1446
+ 1063530009,2
1447
+ 1063530010,2
1448
+ 1063530011,2
1449
+ 1063530012,2
1450
+ 1063530014,2
1451
+ 1063530015,2
1452
+ 1063530016,2
1453
+ 1063530018,2
1454
+ 1063530019,2
1455
+ 1063530021,2
1456
+ 1063530023,2
1457
+ 1063530029,2
1458
+ 1063530032,2
1459
+ 1063530048,2
1460
+ 1063530052,2
1461
+ 898080019,2
1462
+ 1063530057,2
1463
+ 1063530059,2
1464
+ 1063530065,2
1465
+ 1063530067,2
1466
+ 1063530069,2
1467
+ 1063530070,2
1468
+ 898080022,2
1469
+ 1063530073,2
1470
+ 1063530074,2
1471
+ 1063530079,2
1472
+ 1063530083,2
1473
+ 898080028,2
1474
+ 463290002,2
1475
+ 1337530003,2
1476
+ 463290003,2
1477
+ 1337530005,2
1478
+ 1337530006,2
1479
+ 463290008,2
1480
+ 1337530009,2
1481
+ 1337530010,2
1482
+ 1337530013,2
1483
+ 1337530014,2
1484
+ 1337530017,2
1485
+ 1337530018,2
1486
+ 1337530022,2
1487
+ 1337530023,2
1488
+ 1337530024,2
1489
+ 1337530027,2
1490
+ 1337530034,2
1491
+ 1248270004,2
1492
+ 1337530037,2
1493
+ 1248270005,2
1494
+ 1248270008,2
1495
+ 1337530041,2
1496
+ 1248270010,2
1497
+ 1248270011,2
1498
+ 1248270012,2
1499
+ 1337530046,2
1500
+ 1337530048,2
1501
+ 1337530049,2
1502
+ 1337530050,2
1503
+ 1337530057,2
1504
+ 1337530061,2
1505
+ 1337530063,2
1506
+ 1337530067,2
1507
+ 1337530069,2
1508
+ 1337530070,2
1509
+ 1595490008,2
1510
+ 1337530075,2
1511
+ 1337530077,2
1512
+ 1337530079,2
1513
+ 982710005,2
1514
+ 982710006,2
1515
+ 982710008,2
1516
+ 490250003,2
1517
+ 490250004,2
1518
+ 490250005,2
1519
+ 490250014,2
1520
+ 490250015,2
1521
+ 490250017,2
1522
+ 1825460002,2
1523
+ 1825460003,2
1524
+ 518580001,2
1525
+ 518580002,2
1526
+ 518580003,2
1527
+ 518580004,2
1528
+ 518580005,2
1529
+ 1825460009,2
1530
+ 518580010,2
1531
+ 518580011,2
1532
+ 518580006,2
1533
+ 518580015,2
1534
+ 518580016,2
1535
+ 518580023,2
1536
+ 518580024,2
1537
+ 815280006,2
1538
+ 815280008,2
1539
+ 815280012,2
1540
+ 2077870001,2
1541
+ 2077870003,2
1542
+ 720300003,2
1543
+ 812460003,2
1544
+ 812460004,2
1545
+ 812460005,2
1546
+ 812460006,2
1547
+ 720300008,2
1548
+ 812460007,2
1549
+ 812460008,2
1550
+ 812460009,2
1551
+ 720300012,2
1552
+ 812460010,2
1553
+ 720300017,2
dataset/fold_genre.csv ADDED
@@ -0,0 +1,1553 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ img_id,fold
2
+ 343740001,0
3
+ 343740002,0
4
+ 343740003,0
5
+ 343740004,0
6
+ 343740005,0
7
+ 343740006,0
8
+ 343740007,0
9
+ 457380001,0
10
+ 457380002,0
11
+ 457380003,0
12
+ 457380004,0
13
+ 457380005,0
14
+ 457380006,0
15
+ 457380007,0
16
+ 457380008,0
17
+ 457380009,0
18
+ 457380010,0
19
+ 457380011,0
20
+ 457380012,0
21
+ 457380013,0
22
+ 457380014,0
23
+ 457380015,0
24
+ 457380016,0
25
+ 457380017,0
26
+ 457380018,0
27
+ 457380019,0
28
+ 463290001,0
29
+ 463290002,0
30
+ 463290003,0
31
+ 463290004,0
32
+ 463290005,0
33
+ 463290006,0
34
+ 463290007,0
35
+ 463290008,0
36
+ 513490001,0
37
+ 513490002,0
38
+ 518580001,0
39
+ 518580002,0
40
+ 518580003,0
41
+ 518580004,0
42
+ 518580005,0
43
+ 518580006,0
44
+ 518580007,0
45
+ 518580008,0
46
+ 518580009,0
47
+ 518580010,0
48
+ 518580011,0
49
+ 518580012,0
50
+ 518580013,0
51
+ 518580014,0
52
+ 518580015,0
53
+ 518580016,0
54
+ 518580017,0
55
+ 518580018,0
56
+ 518580019,0
57
+ 518580020,0
58
+ 518580021,0
59
+ 518580022,0
60
+ 518580023,0
61
+ 518580024,0
62
+ 528580001,0
63
+ 528580002,0
64
+ 528580003,0
65
+ 528580004,0
66
+ 605850001,0
67
+ 622310001,0
68
+ 622310002,0
69
+ 660520001,0
70
+ 660520002,0
71
+ 660520003,0
72
+ 660520004,0
73
+ 660520005,0
74
+ 660520006,0
75
+ 660520007,0
76
+ 660520008,0
77
+ 660520009,0
78
+ 660520010,0
79
+ 660520011,0
80
+ 660520012,0
81
+ 660520013,0
82
+ 660520014,0
83
+ 660520015,0
84
+ 667010001,0
85
+ 667010002,0
86
+ 667010003,0
87
+ 667010004,0
88
+ 667010005,0
89
+ 667010006,0
90
+ 667010007,0
91
+ 667010008,0
92
+ 667010009,0
93
+ 667010010,0
94
+ 667010011,0
95
+ 667010012,0
96
+ 716260001,0
97
+ 716260002,0
98
+ 716260003,0
99
+ 716260004,0
100
+ 716260005,0
101
+ 716260006,0
102
+ 716260007,0
103
+ 716260008,0
104
+ 716260009,0
105
+ 716260010,0
106
+ 716260011,0
107
+ 716260012,0
108
+ 716260013,0
109
+ 716260014,0
110
+ 716260015,0
111
+ 716260016,0
112
+ 716260017,0
113
+ 716260018,0
114
+ 716260019,0
115
+ 716260020,0
116
+ 716260021,0
117
+ 720300001,0
118
+ 720300002,0
119
+ 720300003,0
120
+ 720300004,0
121
+ 720300005,0
122
+ 720300006,0
123
+ 720300007,0
124
+ 720300008,0
125
+ 720300009,0
126
+ 720300010,0
127
+ 720300011,0
128
+ 720300012,0
129
+ 720300013,0
130
+ 720300014,0
131
+ 720300015,0
132
+ 720300016,0
133
+ 720300017,0
134
+ 720300018,0
135
+ 726910001,0
136
+ 726910002,0
137
+ 726910003,0
138
+ 726910004,0
139
+ 726910005,0
140
+ 726910006,0
141
+ 726910007,0
142
+ 726910008,0
143
+ 726910009,0
144
+ 726910010,0
145
+ 726910011,0
146
+ 790750001,0
147
+ 790750002,0
148
+ 790750003,0
149
+ 790750004,0
150
+ 790750005,0
151
+ 790750006,0
152
+ 812460001,0
153
+ 812460002,0
154
+ 812460003,0
155
+ 812460004,0
156
+ 812460005,0
157
+ 812460006,0
158
+ 812460007,0
159
+ 812460008,0
160
+ 812460009,0
161
+ 812460010,0
162
+ 815280001,0
163
+ 815280002,0
164
+ 815280003,0
165
+ 815280004,0
166
+ 815280005,0
167
+ 815280006,0
168
+ 815280007,0
169
+ 815280008,0
170
+ 815280009,0
171
+ 815280010,0
172
+ 815280011,0
173
+ 815280012,0
174
+ 815280013,0
175
+ 815280014,0
176
+ 815280015,0
177
+ 815280016,0
178
+ 815280017,0
179
+ 815280018,0
180
+ 866540001,0
181
+ 866540002,0
182
+ 866540003,0
183
+ 866540004,0
184
+ 866540005,0
185
+ 866540006,0
186
+ 866540007,0
187
+ 866540008,0
188
+ 866540009,0
189
+ 866540010,0
190
+ 866540011,0
191
+ 898080001,0
192
+ 898080002,0
193
+ 898080003,0
194
+ 898080004,0
195
+ 898080005,0
196
+ 898080006,0
197
+ 898080007,0
198
+ 898080008,0
199
+ 898080009,0
200
+ 898080010,0
201
+ 898080011,0
202
+ 898080012,0
203
+ 898080013,0
204
+ 898080014,0
205
+ 898080015,0
206
+ 898080016,0
207
+ 898080017,0
208
+ 898080018,0
209
+ 898080019,0
210
+ 898080020,0
211
+ 898080021,0
212
+ 898080022,0
213
+ 898080023,0
214
+ 898080024,0
215
+ 898080025,0
216
+ 898080026,0
217
+ 898080027,0
218
+ 898080028,0
219
+ 898080029,0
220
+ 898080030,0
221
+ 898080031,0
222
+ 898080032,0
223
+ 898080033,0
224
+ 898080034,0
225
+ 898080035,0
226
+ 898080036,0
227
+ 910190001,0
228
+ 910190002,0
229
+ 910190003,0
230
+ 910190004,0
231
+ 910190005,0
232
+ 910190006,0
233
+ 910190007,0
234
+ 954160001,0
235
+ 954160002,0
236
+ 982710001,0
237
+ 982710002,0
238
+ 982710003,0
239
+ 982710004,0
240
+ 982710005,0
241
+ 982710006,0
242
+ 982710007,0
243
+ 982710008,0
244
+ 982710009,0
245
+ 982710010,0
246
+ 982710011,0
247
+ 982710012,0
248
+ 982710013,0
249
+ 982710014,0
250
+ 982710015,0
251
+ 1026760001,0
252
+ 1026760002,0
253
+ 1026760003,0
254
+ 1026760004,0
255
+ 1026760005,0
256
+ 1026760006,0
257
+ 1026760007,0
258
+ 1026760008,0
259
+ 1026760009,0
260
+ 1026760010,0
261
+ 1026760011,0
262
+ 1026760012,0
263
+ 1026760013,0
264
+ 1026760014,0
265
+ 1026760015,0
266
+ 1026760016,0
267
+ 1026760017,0
268
+ 1026760018,0
269
+ 1026760019,0
270
+ 1026760020,0
271
+ 1026760021,0
272
+ 1026760022,0
273
+ 1026760023,0
274
+ 1026760024,0
275
+ 1026760025,0
276
+ 1026760026,0
277
+ 1026760027,0
278
+ 1033960001,0
279
+ 1033960002,0
280
+ 1033960003,0
281
+ 1033960004,0
282
+ 1033960005,0
283
+ 1033960006,0
284
+ 1063530001,0
285
+ 1063530002,0
286
+ 1063530003,0
287
+ 1063530004,0
288
+ 1063530005,0
289
+ 1063530006,0
290
+ 1063530007,0
291
+ 1063530008,0
292
+ 1063530009,0
293
+ 1063530010,0
294
+ 1063530011,0
295
+ 1063530012,0
296
+ 1063530013,0
297
+ 1063530014,0
298
+ 1063530015,0
299
+ 1063530016,0
300
+ 1063530017,0
301
+ 1063530018,0
302
+ 1063530019,0
303
+ 1063530020,0
304
+ 1063530021,0
305
+ 1063530022,0
306
+ 1063530023,0
307
+ 1063530024,0
308
+ 1063530025,0
309
+ 1063530026,0
310
+ 1063530027,0
311
+ 1063530028,0
312
+ 1063530029,0
313
+ 1063530030,0
314
+ 1063530031,0
315
+ 1063530032,0
316
+ 1063530033,0
317
+ 1063530034,0
318
+ 1063530035,0
319
+ 1063530036,0
320
+ 1063530037,0
321
+ 1063530038,0
322
+ 1063530039,0
323
+ 1063530040,0
324
+ 1063530041,0
325
+ 1063530042,0
326
+ 1063530043,0
327
+ 1063530044,0
328
+ 1063530045,0
329
+ 1063530046,0
330
+ 1063530047,0
331
+ 1063530048,0
332
+ 1063530049,0
333
+ 1063530050,0
334
+ 1063530051,0
335
+ 1063530052,0
336
+ 1063530053,0
337
+ 1063530054,0
338
+ 1063530055,0
339
+ 1063530056,0
340
+ 1063530057,0
341
+ 1063530058,0
342
+ 1063530059,0
343
+ 1063530060,0
344
+ 1063530061,0
345
+ 1063530062,0
346
+ 1063530063,0
347
+ 1063530064,0
348
+ 1063530065,0
349
+ 1063530066,0
350
+ 1063530067,0
351
+ 1063530068,0
352
+ 1063530069,0
353
+ 1063530070,0
354
+ 1063530071,0
355
+ 1063530072,0
356
+ 1063530073,0
357
+ 1063530074,0
358
+ 1063530075,0
359
+ 1063530076,0
360
+ 1063530077,0
361
+ 1063530078,0
362
+ 1063530079,0
363
+ 1063530080,0
364
+ 1063530081,0
365
+ 1063530082,0
366
+ 1063530083,0
367
+ 1113370001,0
368
+ 1113370002,0
369
+ 1113370003,0
370
+ 1113370004,0
371
+ 1157070001,0
372
+ 1157070002,0
373
+ 1157070003,0
374
+ 1157070004,0
375
+ 1157070005,0
376
+ 1157070006,0
377
+ 1157070007,0
378
+ 1157070008,0
379
+ 1172280001,0
380
+ 1172280002,0
381
+ 1172280003,0
382
+ 1172280004,0
383
+ 1172280005,0
384
+ 1172280006,0
385
+ 1172280007,0
386
+ 1172280008,0
387
+ 1172280009,0
388
+ 1210650001,0
389
+ 1210650002,0
390
+ 1210650003,0
391
+ 1210650004,0
392
+ 1210650005,0
393
+ 1210650006,0
394
+ 1210650007,0
395
+ 1210650008,0
396
+ 1210650009,0
397
+ 1210650010,0
398
+ 1210650011,0
399
+ 1210650012,0
400
+ 1210650013,0
401
+ 1210650014,0
402
+ 1210650015,0
403
+ 1210650016,0
404
+ 1210650017,0
405
+ 1210650018,0
406
+ 1210650019,0
407
+ 1210650020,0
408
+ 1210650021,0
409
+ 1210650022,0
410
+ 1210650023,0
411
+ 1210650024,0
412
+ 1210650025,0
413
+ 1210650026,0
414
+ 1210650027,0
415
+ 1210650028,0
416
+ 1217930001,0
417
+ 1217930002,0
418
+ 1217930003,0
419
+ 1217930004,0
420
+ 1217930005,0
421
+ 1245640001,0
422
+ 1245640002,0
423
+ 1245640003,0
424
+ 1245640004,0
425
+ 1245640005,0
426
+ 1245640006,0
427
+ 1245640007,0
428
+ 1245640008,0
429
+ 1245640009,0
430
+ 1245640010,0
431
+ 1245640011,0
432
+ 1245640012,0
433
+ 1245640013,0
434
+ 1245640014,0
435
+ 1245640015,0
436
+ 1245640016,0
437
+ 1245640017,0
438
+ 1245640018,0
439
+ 1245640019,0
440
+ 1245640020,0
441
+ 1245640021,0
442
+ 1245640022,0
443
+ 1245640023,0
444
+ 1245640024,0
445
+ 1245640025,0
446
+ 1245640026,0
447
+ 1245640027,0
448
+ 1245640028,0
449
+ 1245640029,0
450
+ 1245640030,0
451
+ 1245640031,0
452
+ 1245640032,0
453
+ 1245640033,0
454
+ 1245640034,0
455
+ 1245640035,0
456
+ 1245640036,0
457
+ 1245640037,0
458
+ 1245640038,0
459
+ 1248270001,0
460
+ 1248270002,0
461
+ 1248270003,0
462
+ 1248270004,0
463
+ 1248270005,0
464
+ 1248270006,0
465
+ 1248270007,0
466
+ 1248270008,0
467
+ 1248270009,0
468
+ 1248270010,0
469
+ 1248270011,0
470
+ 1248270012,0
471
+ 1248270013,0
472
+ 1250210001,0
473
+ 1250210002,0
474
+ 1250210003,0
475
+ 1250210004,0
476
+ 1250210005,0
477
+ 1250210006,0
478
+ 1250210007,0
479
+ 1250210008,0
480
+ 1250210009,0
481
+ 1250210010,0
482
+ 1250210011,0
483
+ 1250210012,0
484
+ 1269890001,0
485
+ 1269890002,0
486
+ 1269890003,0
487
+ 1269890004,0
488
+ 1269890005,0
489
+ 1269890006,0
490
+ 1269890007,0
491
+ 1269890008,0
492
+ 1269890009,0
493
+ 1269890010,0
494
+ 1269890011,0
495
+ 1269890012,0
496
+ 1269890013,0
497
+ 1269890014,0
498
+ 1269890015,0
499
+ 1295570001,0
500
+ 1298890001,0
501
+ 1298890002,0
502
+ 1298890003,0
503
+ 1298890004,0
504
+ 1298890005,0
505
+ 1298890006,0
506
+ 1298890007,0
507
+ 1298890008,0
508
+ 1298890009,0
509
+ 1298890010,0
510
+ 1298890012,0
511
+ 1298890013,0
512
+ 1298890014,0
513
+ 1298890015,0
514
+ 1298890016,0
515
+ 1298890017,0
516
+ 1298890018,0
517
+ 1298890019,0
518
+ 1298890020,0
519
+ 1298890021,0
520
+ 1334900001,0
521
+ 1334900002,0
522
+ 1334900003,0
523
+ 1334900004,0
524
+ 1334900005,0
525
+ 1334900006,0
526
+ 1337530001,0
527
+ 1337530002,0
528
+ 1337530003,0
529
+ 1337530004,0
530
+ 1337530005,0
531
+ 1337530006,0
532
+ 1337530007,0
533
+ 1337530008,0
534
+ 1337530009,0
535
+ 1337530010,0
536
+ 1337530011,0
537
+ 1337530012,0
538
+ 1337530013,0
539
+ 1337530014,0
540
+ 1337530015,0
541
+ 1337530016,0
542
+ 1337530017,0
543
+ 1337530018,0
544
+ 1337530019,0
545
+ 1337530020,0
546
+ 1337530021,0
547
+ 1337530022,0
548
+ 1337530023,0
549
+ 1337530024,0
550
+ 1337530025,0
551
+ 1337530026,0
552
+ 1337530027,0
553
+ 1337530028,0
554
+ 1337530029,0
555
+ 1337530030,0
556
+ 1337530031,0
557
+ 1337530032,0
558
+ 1337530033,0
559
+ 1337530034,0
560
+ 1337530035,0
561
+ 1337530036,0
562
+ 1337530037,0
563
+ 1337530038,0
564
+ 1337530039,0
565
+ 1337530040,0
566
+ 1337530041,0
567
+ 1337530042,0
568
+ 1337530043,0
569
+ 1337530044,0
570
+ 1337530045,0
571
+ 1337530046,0
572
+ 1337530047,0
573
+ 1337530048,0
574
+ 1337530049,0
575
+ 1337530050,0
576
+ 1337530051,0
577
+ 1337530052,0
578
+ 1337530053,0
579
+ 1337530054,0
580
+ 1337530055,0
581
+ 1337530056,0
582
+ 1337530057,0
583
+ 1337530058,0
584
+ 1337530059,0
585
+ 1337530060,0
586
+ 1337530061,0
587
+ 1337530062,0
588
+ 1337530063,0
589
+ 1337530064,0
590
+ 1337530065,0
591
+ 1337530066,0
592
+ 1337530067,0
593
+ 1337530068,0
594
+ 1337530069,0
595
+ 1337530070,0
596
+ 1337530071,0
597
+ 1337530072,0
598
+ 1337530073,0
599
+ 1337530074,0
600
+ 1337530075,0
601
+ 1337530076,0
602
+ 1337530077,0
603
+ 1337530078,0
604
+ 1337530079,0
605
+ 1337530080,0
606
+ 1337530081,0
607
+ 1337530082,0
608
+ 1382350001,0
609
+ 1382350002,0
610
+ 1382350003,0
611
+ 1382350004,0
612
+ 1382350005,0
613
+ 1382350006,0
614
+ 1382350007,0
615
+ 1382350008,0
616
+ 1394210001,0
617
+ 1394210002,0
618
+ 1394210003,0
619
+ 1394210004,0
620
+ 1394210005,0
621
+ 1394210006,0
622
+ 1394210007,0
623
+ 1394210008,0
624
+ 1394210009,0
625
+ 1394210010,0
626
+ 1394410001,0
627
+ 1394410002,0
628
+ 1394410003,0
629
+ 1394410004,0
630
+ 1394410005,0
631
+ 1404360001,0
632
+ 1404360002,0
633
+ 1404360003,0
634
+ 1404360004,0
635
+ 1404360005,0
636
+ 1404360006,0
637
+ 1404360007,0
638
+ 1404360009,0
639
+ 1404360010,0
640
+ 1404360011,0
641
+ 1404360013,0
642
+ 1404360015,0
643
+ 1404360016,0
644
+ 1404360018,0
645
+ 1404360019,0
646
+ 1404360022,0
647
+ 1404360023,0
648
+ 1404360024,0
649
+ 1404360025,0
650
+ 1404360026,0
651
+ 1404360027,0
652
+ 1404360029,0
653
+ 1453730001,0
654
+ 1453730002,0
655
+ 1453730003,0
656
+ 1453730004,0
657
+ 1453730005,0
658
+ 1453730006,0
659
+ 1453730007,0
660
+ 1453730008,0
661
+ 1453730009,0
662
+ 1453730010,0
663
+ 1453730011,0
664
+ 1453730012,0
665
+ 1453730013,0
666
+ 1453730014,0
667
+ 1456280001,0
668
+ 1456280002,0
669
+ 1456280003,0
670
+ 1456280004,0
671
+ 1480650001,0
672
+ 1480650002,0
673
+ 1480650003,0
674
+ 1480650004,0
675
+ 1480650005,0
676
+ 1480650006,0
677
+ 1480650007,0
678
+ 1480650008,0
679
+ 1481400001,0
680
+ 1481400002,0
681
+ 1481400003,0
682
+ 1485260001,0
683
+ 1485260002,0
684
+ 1485260003,0
685
+ 1485260004,0
686
+ 1485260005,0
687
+ 1486660001,0
688
+ 1486660002,0
689
+ 1486660003,0
690
+ 1486660004,0
691
+ 1486660005,0
692
+ 1486660006,0
693
+ 1486660007,0
694
+ 1486660008,0
695
+ 1486660009,0
696
+ 1486660010,0
697
+ 1486660011,0
698
+ 1486660012,0
699
+ 1486660013,0
700
+ 1486660014,0
701
+ 1550280001,0
702
+ 1550280002,0
703
+ 1550280003,0
704
+ 1550280004,0
705
+ 1550280005,0
706
+ 1550280006,0
707
+ 1550280007,0
708
+ 1550280008,0
709
+ 1550280009,0
710
+ 1550280010,0
711
+ 1550280011,0
712
+ 1550280012,0
713
+ 1550280013,0
714
+ 1550280014,0
715
+ 1550280015,0
716
+ 1550280016,0
717
+ 1550280017,0
718
+ 1550280018,0
719
+ 1550280019,0
720
+ 1550280020,0
721
+ 1550280021,0
722
+ 1550280022,0
723
+ 1550280023,0
724
+ 1550280024,0
725
+ 1550280025,0
726
+ 1550280026,0
727
+ 1550280027,0
728
+ 1550280028,0
729
+ 1550280029,0
730
+ 1550280030,0
731
+ 1550280031,0
732
+ 1550280032,0
733
+ 1550280033,0
734
+ 1550280034,0
735
+ 1550280035,0
736
+ 1550280036,0
737
+ 1550280037,0
738
+ 1550280038,0
739
+ 1550280039,0
740
+ 1550280040,0
741
+ 1550280041,0
742
+ 1550280042,0
743
+ 1550280043,0
744
+ 1550280044,0
745
+ 1550280045,0
746
+ 1550280046,0
747
+ 1550280047,0
748
+ 1550280048,0
749
+ 1550280049,0
750
+ 1550280050,0
751
+ 1550280051,0
752
+ 1550280052,0
753
+ 1550280053,0
754
+ 1550280054,0
755
+ 1550280055,0
756
+ 1561560001,0
757
+ 1561560002,0
758
+ 1561560003,0
759
+ 1561560004,0
760
+ 1561560005,0
761
+ 1561560006,0
762
+ 1561560007,0
763
+ 1561560008,0
764
+ 1561560009,0
765
+ 1561560010,0
766
+ 1561560011,0
767
+ 1561560012,0
768
+ 1561560013,0
769
+ 1561560014,0
770
+ 1561560015,0
771
+ 1561560016,0
772
+ 1561560017,0
773
+ 1561560018,0
774
+ 1561560019,0
775
+ 1561560020,0
776
+ 1561560021,0
777
+ 1561560022,0
778
+ 1561560023,0
779
+ 1561560024,0
780
+ 1561560025,0
781
+ 1561560026,0
782
+ 1561560027,0
783
+ 1561560028,0
784
+ 1561560029,0
785
+ 1561560030,0
786
+ 1561560031,0
787
+ 1561560032,0
788
+ 1595490001,0
789
+ 1595490003,0
790
+ 1595490004,0
791
+ 1595490005,0
792
+ 1595490006,0
793
+ 1595490007,0
794
+ 1595490008,0
795
+ 1595490009,0
796
+ 1595490010,0
797
+ 1595490011,0
798
+ 1595490012,0
799
+ 1595490013,0
800
+ 1595490014,0
801
+ 1595490015,0
802
+ 1595490016,0
803
+ 1595490017,0
804
+ 1595490018,0
805
+ 1595490019,0
806
+ 1595490020,0
807
+ 1601810001,0
808
+ 1601810002,0
809
+ 1601810003,0
810
+ 1612820001,0
811
+ 1612820002,0
812
+ 1652800001,0
813
+ 1652800002,0
814
+ 1652800003,0
815
+ 1652800004,0
816
+ 1652800005,0
817
+ 1652800006,0
818
+ 1652800007,0
819
+ 1652800008,0
820
+ 1652800009,0
821
+ 1652800010,0
822
+ 1652800011,0
823
+ 1652800012,0
824
+ 1652800013,0
825
+ 1676620001,0
826
+ 1676620002,0
827
+ 1676620003,0
828
+ 1676620004,0
829
+ 1676620005,0
830
+ 1676620006,0
831
+ 1676620007,0
832
+ 1695870001,0
833
+ 1695870002,0
834
+ 1695870003,0
835
+ 1695870004,0
836
+ 1695870005,0
837
+ 1695870006,0
838
+ 1705030001,0
839
+ 1705030002,0
840
+ 1705030003,0
841
+ 1705030004,0
842
+ 1705030005,0
843
+ 1705030006,0
844
+ 1705030007,0
845
+ 1705030008,0
846
+ 1705030009,0
847
+ 1705030010,0
848
+ 1705030011,0
849
+ 1705030012,0
850
+ 1705030013,0
851
+ 1705030014,0
852
+ 1705030015,0
853
+ 1705030016,0
854
+ 1707840001,0
855
+ 1707840002,0
856
+ 1707840003,0
857
+ 1707840004,0
858
+ 1707840005,0
859
+ 1707840006,0
860
+ 1707840007,0
861
+ 1707840008,0
862
+ 1801560001,0
863
+ 1801560002,0
864
+ 1801560003,0
865
+ 1801560004,0
866
+ 1801560005,0
867
+ 1801560006,0
868
+ 1801560007,0
869
+ 1801560008,0
870
+ 1801560009,0
871
+ 1801560010,0
872
+ 1801560011,0
873
+ 1906880001,0
874
+ 1906880002,0
875
+ 1906880003,0
876
+ 1931980001,0
877
+ 1931980002,0
878
+ 1931980003,0
879
+ 1931980004,0
880
+ 1931980005,0
881
+ 1931980006,0
882
+ 1931980007,0
883
+ 1931980008,0
884
+ 1931980009,0
885
+ 1931980010,0
886
+ 1931980011,0
887
+ 1931980012,0
888
+ 1931980013,0
889
+ 1931980014,0
890
+ 2020950001,0
891
+ 2020950002,0
892
+ 2020950003,0
893
+ 2077870001,0
894
+ 2077870002,0
895
+ 2077870003,0
896
+ 2077870004,0
897
+ 2077870005,0
898
+ 2163140001,0
899
+ 2163140002,0
900
+ 2163140003,0
901
+ 2163140004,0
902
+ 2163140005,0
903
+ 2163140006,0
904
+ 2163140007,0
905
+ 2163140008,0
906
+ 2163140009,0
907
+ 2163140010,0
908
+ 2163140011,0
909
+ 2163140012,0
910
+ 2163140013,0
911
+ 2163140014,0
912
+ 2163140015,0
913
+ 2163140016,0
914
+ 2163140017,0
915
+ 2163140018,0
916
+ 2163140019,0
917
+ 2163140020,0
918
+ 2163140021,0
919
+ 2163140022,0
920
+ 2163140023,0
921
+ 2163140024,0
922
+ 2163140025,0
923
+ 2163140026,0
924
+ 2163140027,0
925
+ 2163140028,0
926
+ 2163140029,0
927
+ 2163140030,0
928
+ 2163140031,0
929
+ 2163140032,0
930
+ 2163140033,0
931
+ 2163140034,0
932
+ 2163140035,0
933
+ 2163140036,0
934
+ 2163140037,0
935
+ 2163140038,0
936
+ 2163140039,0
937
+ 2163140040,0
938
+ 2163140041,0
939
+ 2163140042,0
940
+ 2163140043,0
941
+ 2163140044,0
942
+ 2163140045,0
943
+ 2163140046,0
944
+ 2163140047,0
945
+ 2163140048,0
946
+ 2163140049,0
947
+ 2163140050,0
948
+ 2163140051,0
949
+ 2163140052,0
950
+ 2163140053,0
951
+ 2163140054,0
952
+ 2163140055,0
953
+ 2163140056,0
954
+ 2163140057,0
955
+ 2163140058,0
956
+ 2193270001,0
957
+ 2293180001,0
958
+ 2293180002,0
959
+ 2293180003,0
960
+ 438100001,1
961
+ 438100002,1
962
+ 438100003,1
963
+ 438100004,1
964
+ 438100005,1
965
+ 438100006,1
966
+ 438100007,1
967
+ 438100008,1
968
+ 438100009,1
969
+ 438100010,1
970
+ 529150001,1
971
+ 529150002,1
972
+ 529150003,1
973
+ 529150004,1
974
+ 533970001,1
975
+ 533970002,1
976
+ 533970003,1
977
+ 600140001,1
978
+ 600140002,1
979
+ 600140003,1
980
+ 600140004,1
981
+ 600140005,1
982
+ 600140006,1
983
+ 600140007,1
984
+ 600140008,1
985
+ 600140009,1
986
+ 600140010,1
987
+ 600140011,1
988
+ 600140012,1
989
+ 600140013,1
990
+ 600140014,1
991
+ 600140015,1
992
+ 600140016,1
993
+ 600140017,1
994
+ 600140018,1
995
+ 600140019,1
996
+ 600140020,1
997
+ 600140021,1
998
+ 600140022,1
999
+ 600140023,1
1000
+ 600140024,1
1001
+ 631660001,1
1002
+ 714100001,1
1003
+ 714100002,1
1004
+ 714100003,1
1005
+ 714100004,1
1006
+ 714100005,1
1007
+ 714100006,1
1008
+ 714100007,1
1009
+ 714100008,1
1010
+ 1178780001,1
1011
+ 1178780002,1
1012
+ 1178780003,1
1013
+ 1178780004,1
1014
+ 1178780005,1
1015
+ 1178780006,1
1016
+ 1178780007,1
1017
+ 1178780008,1
1018
+ 1178780009,1
1019
+ 1178780010,1
1020
+ 1178780011,1
1021
+ 1178780012,1
1022
+ 1178780013,1
1023
+ 1178780014,1
1024
+ 1178780015,1
1025
+ 1178780016,1
1026
+ 1337060001,1
1027
+ 1337060002,1
1028
+ 1337060003,1
1029
+ 1337060004,1
1030
+ 1337060005,1
1031
+ 1337060006,1
1032
+ 1337060007,1
1033
+ 1337060008,1
1034
+ 1337060009,1
1035
+ 1337060010,1
1036
+ 1337060011,1
1037
+ 1337060012,1
1038
+ 1337060013,1
1039
+ 1337060014,1
1040
+ 1337060015,1
1041
+ 1337060016,1
1042
+ 1337060017,1
1043
+ 1337060018,1
1044
+ 1337060019,1
1045
+ 1337060020,1
1046
+ 1337060021,1
1047
+ 1337060022,1
1048
+ 1337060023,1
1049
+ 1337060024,1
1050
+ 1337060025,1
1051
+ 1337060026,1
1052
+ 1337060027,1
1053
+ 1337060028,1
1054
+ 1337060029,1
1055
+ 1337060030,1
1056
+ 1337060031,1
1057
+ 1337060032,1
1058
+ 1337060033,1
1059
+ 1337060034,1
1060
+ 1337060035,1
1061
+ 1337060036,1
1062
+ 1337060039,1
1063
+ 1337060040,1
1064
+ 1337060041,1
1065
+ 1960620001,1
1066
+ 1960620002,1
1067
+ 1960620008,1
1068
+ 1960620010,1
1069
+ 1960620011,1
1070
+ 1960620012,1
1071
+ 1960620013,1
1072
+ 1960620014,1
1073
+ 1960620015,1
1074
+ 1960620016,1
1075
+ 2089520001,1
1076
+ 2089520002,1
1077
+ 2089520003,1
1078
+ 2089520004,1
1079
+ 2308940001,1
1080
+ 2308940002,1
1081
+ 2308940003,1
1082
+ 2308940004,1
1083
+ 2308940005,1
1084
+ 2308940006,1
1085
+ 2308940007,1
1086
+ 2308940008,1
1087
+ 2308940009,1
1088
+ 2308940010,1
1089
+ 2308940011,1
1090
+ 2308940012,1
1091
+ 2308940013,1
1092
+ 2308940014,1
1093
+ 2308940015,1
1094
+ 2308940016,1
1095
+ 2308940017,1
1096
+ 269170001,2
1097
+ 269170002,2
1098
+ 269170003,2
1099
+ 269170004,2
1100
+ 269170005,2
1101
+ 269170006,2
1102
+ 269170007,2
1103
+ 269170008,2
1104
+ 269170009,2
1105
+ 451980001,2
1106
+ 451980002,2
1107
+ 451980003,2
1108
+ 451980004,2
1109
+ 451980005,2
1110
+ 451980006,2
1111
+ 451980007,2
1112
+ 451980008,2
1113
+ 451980009,2
1114
+ 451980010,2
1115
+ 451980011,2
1116
+ 451980012,2
1117
+ 451980013,2
1118
+ 451980014,2
1119
+ 451980015,2
1120
+ 451980016,2
1121
+ 451980017,2
1122
+ 451980018,2
1123
+ 457550001,2
1124
+ 457550002,2
1125
+ 457550003,2
1126
+ 457550004,2
1127
+ 457550005,2
1128
+ 490250001,2
1129
+ 490250002,2
1130
+ 490250003,2
1131
+ 490250004,2
1132
+ 490250005,2
1133
+ 490250006,2
1134
+ 490250007,2
1135
+ 490250008,2
1136
+ 490250009,2
1137
+ 490250010,2
1138
+ 490250011,2
1139
+ 490250012,2
1140
+ 490250013,2
1141
+ 490250014,2
1142
+ 490250015,2
1143
+ 490250016,2
1144
+ 490250017,2
1145
+ 490250018,2
1146
+ 490250019,2
1147
+ 497820001,2
1148
+ 497820002,2
1149
+ 497820003,2
1150
+ 591680001,2
1151
+ 591680002,2
1152
+ 591680003,2
1153
+ 591680004,2
1154
+ 591680005,2
1155
+ 591680006,2
1156
+ 591680007,2
1157
+ 591680008,2
1158
+ 591680009,2
1159
+ 591680010,2
1160
+ 591680011,2
1161
+ 591680012,2
1162
+ 591680013,2
1163
+ 731790001,2
1164
+ 731790002,2
1165
+ 731790003,2
1166
+ 731790004,2
1167
+ 731790005,2
1168
+ 731790006,2
1169
+ 731790007,2
1170
+ 731790008,2
1171
+ 731790009,2
1172
+ 731790010,2
1173
+ 758210001,2
1174
+ 758210002,2
1175
+ 758210003,2
1176
+ 758210004,2
1177
+ 758210005,2
1178
+ 758210006,2
1179
+ 758210007,2
1180
+ 758210008,2
1181
+ 758210009,2
1182
+ 758210010,2
1183
+ 758210011,2
1184
+ 758210012,2
1185
+ 758210013,2
1186
+ 758210014,2
1187
+ 891960001,2
1188
+ 891960002,2
1189
+ 891960003,2
1190
+ 891960004,2
1191
+ 891960005,2
1192
+ 891960006,2
1193
+ 891960007,2
1194
+ 891960008,2
1195
+ 891960009,2
1196
+ 891960010,2
1197
+ 997760001,2
1198
+ 997760002,2
1199
+ 997760003,2
1200
+ 997760004,2
1201
+ 997760005,2
1202
+ 997760006,2
1203
+ 997760007,2
1204
+ 997760008,2
1205
+ 997760009,2
1206
+ 997760010,2
1207
+ 997760011,2
1208
+ 997760012,2
1209
+ 998660001,2
1210
+ 998660002,2
1211
+ 998660003,2
1212
+ 998660004,2
1213
+ 998660005,2
1214
+ 1150310001,2
1215
+ 1150310002,2
1216
+ 1150310003,2
1217
+ 1150310004,2
1218
+ 1150310005,2
1219
+ 1150310006,2
1220
+ 1264160001,2
1221
+ 1264160002,2
1222
+ 1264160003,2
1223
+ 1264160004,2
1224
+ 1264160005,2
1225
+ 1264160006,2
1226
+ 1264160007,2
1227
+ 1264160008,2
1228
+ 1264160009,2
1229
+ 1264160010,2
1230
+ 1264160011,2
1231
+ 1264160012,2
1232
+ 1264160013,2
1233
+ 1264160014,2
1234
+ 1264160015,2
1235
+ 1264160016,2
1236
+ 1264160017,2
1237
+ 1270910001,2
1238
+ 1270910002,2
1239
+ 1270910003,2
1240
+ 1270910004,2
1241
+ 1270910005,2
1242
+ 1388030001,2
1243
+ 1488730001,2
1244
+ 1512840001,2
1245
+ 1512840002,2
1246
+ 1512840003,2
1247
+ 1512840004,2
1248
+ 1512840005,2
1249
+ 1512840006,2
1250
+ 1512840007,2
1251
+ 1730290001,2
1252
+ 1730290002,2
1253
+ 1730290003,2
1254
+ 1730290004,2
1255
+ 1730290005,2
1256
+ 1805510001,2
1257
+ 1805510002,2
1258
+ 1805510003,2
1259
+ 1805510004,2
1260
+ 1805510005,2
1261
+ 1805510006,2
1262
+ 1805510007,2
1263
+ 1805510008,2
1264
+ 1805510009,2
1265
+ 1805510010,2
1266
+ 1805510011,2
1267
+ 1805510012,2
1268
+ 1805510013,2
1269
+ 1805510014,2
1270
+ 1805510015,2
1271
+ 1805510016,2
1272
+ 1805510017,2
1273
+ 1805510018,2
1274
+ 1805510019,2
1275
+ 1805510020,2
1276
+ 1805510021,2
1277
+ 1805510022,2
1278
+ 1805510023,2
1279
+ 1805510024,2
1280
+ 1805510025,2
1281
+ 1805510026,2
1282
+ 1805510027,2
1283
+ 1805510028,2
1284
+ 1805510029,2
1285
+ 1805510030,2
1286
+ 1805510031,2
1287
+ 1805510032,2
1288
+ 1805510033,2
1289
+ 1825460001,2
1290
+ 1825460002,2
1291
+ 1825460003,2
1292
+ 1825460004,2
1293
+ 1825460005,2
1294
+ 1825460006,2
1295
+ 1825460007,2
1296
+ 1825460008,2
1297
+ 1825460009,2
1298
+ 1825460010,2
1299
+ 1825460011,2
1300
+ 1825460012,2
1301
+ 1862180001,2
1302
+ 1862180002,2
1303
+ 1862180003,2
1304
+ 1862180004,2
1305
+ 1862180005,2
1306
+ 1862180006,2
1307
+ 1862180007,2
1308
+ 2025000001,2
1309
+ 2025000002,2
1310
+ 2025000003,2
1311
+ 2025000004,2
1312
+ 2025000005,2
1313
+ 2025000006,2
1314
+ 2025000007,2
1315
+ 2025000008,2
1316
+ 2025000009,2
1317
+ 2025000010,2
1318
+ 2057000001,2
1319
+ 2057000002,2
1320
+ 2057000003,2
1321
+ 2057000004,2
1322
+ 2057000005,2
1323
+ 2057000006,2
1324
+ 2057000007,2
1325
+ 2057000008,2
1326
+ 2057000009,2
1327
+ 2057000010,2
1328
+ 2057000011,2
1329
+ 2057000012,2
1330
+ 2057000013,2
1331
+ 2057000014,2
1332
+ 2057000015,2
1333
+ 2057000016,2
1334
+ 2057000017,2
1335
+ 2057000018,2
1336
+ 2057000019,2
1337
+ 2057000020,2
1338
+ 2057000021,2
1339
+ 2057000022,2
1340
+ 2057000023,2
1341
+ 2057000024,2
1342
+ 2057000025,2
1343
+ 2057000026,2
1344
+ 2057000027,2
1345
+ 2057000028,2
1346
+ 2057000029,2
1347
+ 2057000030,2
1348
+ 2057000031,2
1349
+ 2057000032,2
1350
+ 2057000033,2
1351
+ 2057000034,2
1352
+ 2057000035,2
1353
+ 2057000036,2
1354
+ 2057000037,2
1355
+ 2057000038,2
1356
+ 2057000039,2
1357
+ 2057000040,2
1358
+ 2057000041,2
1359
+ 2057000042,2
1360
+ 2057000043,2
1361
+ 2057000044,2
1362
+ 2057000045,2
1363
+ 2057000046,2
1364
+ 2057000047,2
1365
+ 2057000048,2
1366
+ 2057000049,2
1367
+ 2057000050,2
1368
+ 2057000051,2
1369
+ 2057000052,2
1370
+ 2057000053,2
1371
+ 2057000054,2
1372
+ 2057000055,2
1373
+ 2057000056,2
1374
+ 2057000057,2
1375
+ 2057000058,2
1376
+ 2057000059,2
1377
+ 2057000060,2
1378
+ 2057000061,2
1379
+ 2057000062,2
1380
+ 2057000063,2
1381
+ 2057000064,2
1382
+ 2057000065,2
1383
+ 2057000066,2
1384
+ 2057000067,2
1385
+ 2057000068,2
1386
+ 2057000069,2
1387
+ 2057000070,2
1388
+ 2057000071,2
1389
+ 2057000072,2
1390
+ 2057000073,2
1391
+ 2057000074,2
1392
+ 2057000075,2
1393
+ 2057000076,2
1394
+ 2057000077,2
1395
+ 2057000078,2
1396
+ 2057000079,2
1397
+ 2057000080,2
1398
+ 2057000081,2
1399
+ 2057000082,2
1400
+ 2057000083,2
1401
+ 2057000084,2
1402
+ 2057000085,2
1403
+ 2057000086,2
1404
+ 2057000087,2
1405
+ 2057000088,2
1406
+ 2057000089,2
1407
+ 2057000090,2
1408
+ 2057000091,2
1409
+ 2057000092,2
1410
+ 2057000093,2
1411
+ 2057000094,2
1412
+ 2057000095,2
1413
+ 2057000096,2
1414
+ 2057000097,2
1415
+ 2057000098,2
1416
+ 2057000099,2
1417
+ 2057000100,2
1418
+ 2057000101,2
1419
+ 2057000102,2
1420
+ 2057000103,2
1421
+ 2057000104,2
1422
+ 2057000105,2
1423
+ 2057000106,2
1424
+ 2057000107,2
1425
+ 2057000108,2
1426
+ 2057000109,2
1427
+ 2057000110,2
1428
+ 2057000111,2
1429
+ 2057000112,2
1430
+ 2057000113,2
1431
+ 2057000114,2
1432
+ 2057000115,2
1433
+ 2057000116,2
1434
+ 2057000117,2
1435
+ 2057000118,2
1436
+ 2057000119,2
1437
+ 2057000120,2
1438
+ 2057000121,2
1439
+ 2057000122,2
1440
+ 2057000123,2
1441
+ 2057000124,2
1442
+ 2057000125,2
1443
+ 2057000126,2
1444
+ 2057000127,2
1445
+ 2057000128,2
1446
+ 2057000129,2
1447
+ 2057000130,2
1448
+ 2057000131,2
1449
+ 2057000132,2
1450
+ 2057000133,2
1451
+ 2057000134,2
1452
+ 2057000135,2
1453
+ 2057000136,2
1454
+ 2057000137,2
1455
+ 2057000138,2
1456
+ 2057000139,2
1457
+ 2057000140,2
1458
+ 2057000141,2
1459
+ 2057000142,2
1460
+ 2057000143,2
1461
+ 2057000144,2
1462
+ 2057000145,2
1463
+ 2057000146,2
1464
+ 2057000147,2
1465
+ 2057000148,2
1466
+ 2057000149,2
1467
+ 2057000150,2
1468
+ 2057000151,2
1469
+ 2057000152,2
1470
+ 2057000153,2
1471
+ 2057000154,2
1472
+ 2057000155,2
1473
+ 2057000156,2
1474
+ 2057000157,2
1475
+ 2057000158,2
1476
+ 2057000159,2
1477
+ 2057000160,2
1478
+ 2057000161,2
1479
+ 2057000162,2
1480
+ 2057000163,2
1481
+ 2057000164,2
1482
+ 2057000165,2
1483
+ 2057000166,2
1484
+ 2057000167,2
1485
+ 2057000168,2
1486
+ 2057000169,2
1487
+ 2057000170,2
1488
+ 2057000171,2
1489
+ 2057000172,2
1490
+ 2057000173,2
1491
+ 2057000174,2
1492
+ 2057000175,2
1493
+ 2057000176,2
1494
+ 2057000177,2
1495
+ 2057000178,2
1496
+ 2224020001,2
1497
+ 2224020002,2
1498
+ 2224020003,2
1499
+ 2224020004,2
1500
+ 2224020005,2
1501
+ 2224020006,2
1502
+ 2224020007,2
1503
+ 2224020008,2
1504
+ 2224020009,2
1505
+ 2224020010,2
1506
+ 2224020011,2
1507
+ 2224020012,2
1508
+ 2224020013,2
1509
+ 2224020014,2
1510
+ 2224020015,2
1511
+ 2224020016,2
1512
+ 2224020017,2
1513
+ 2224020018,2
1514
+ 2224020019,2
1515
+ 2224020020,2
1516
+ 2224020021,2
1517
+ 2224020022,2
1518
+ 2224020023,2
1519
+ 2224020024,2
1520
+ 2224020025,2
1521
+ 2224020026,2
1522
+ 2224020027,2
1523
+ 2224020028,2
1524
+ 2224020029,2
1525
+ 2224020030,2
1526
+ 2224020031,2
1527
+ 2224020032,2
1528
+ 2224020033,2
1529
+ 2224020034,2
1530
+ 2224020035,2
1531
+ 2224020036,2
1532
+ 2224020037,2
1533
+ 2224020038,2
1534
+ 2224020039,2
1535
+ 2224020040,2
1536
+ 2224020041,2
1537
+ 2224020042,2
1538
+ 2224020043,2
1539
+ 2224020044,2
1540
+ 2224020045,2
1541
+ 2224020046,2
1542
+ 2224020047,2
1543
+ 2224020048,2
1544
+ 2224020049,2
1545
+ 2224020050,2
1546
+ 2224020051,2
1547
+ 2224020052,2
1548
+ 2224020053,2
1549
+ 2224020054,2
1550
+ 2224020055,2
1551
+ 2224020056,2
1552
+ 2224020057,2
1553
+ 2224020058,2
dataset/gen_cat_fold.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import random
3
+ import pandas as pd
4
+
5
+ DATASET_PATH = './data/coco_det/annotations/semantics.json'
6
+ CONTEXT_DATASET_PATH = './data/coco_context/annotations/context.json'
7
+
8
+
9
+ def parse_img_id(img_id):
10
+ app_id = int(str(img_id)[:-3])
11
+ img = int(str(img_id)[-3:])
12
+ return app_id, img
13
+
14
+ with open(DATASET_PATH, 'r') as f:
15
+ dataset = json.load(f)
16
+
17
+ cat_name_cat = {cat['name']: cat for cat in dataset['categories']}
18
+
19
+ img_ids = set([img['id'] for img in dataset['images']])
20
+
21
+ app_id_img = {}
22
+
23
+ for img in dataset['images']:
24
+ app_id, img = parse_img_id(img['id'])
25
+ if app_id not in app_id_img:
26
+ app_id_img[app_id] = []
27
+ app_id_img[app_id].append(img)
28
+
29
+ with open(CONTEXT_DATASET_PATH, 'r') as f:
30
+ context_dataset = json.load(f)
31
+
32
+ context_cat_name = [cat['name'] for cat in context_dataset['categories'] if not cat['name'].endswith('-n')]
33
+ context_cat_id = [cat['id'] for cat in dataset['categories'] if cat['name'] in context_cat_name]
34
+
35
+ context_related_img_ids = []
36
+
37
+ for ann in dataset['annotations']:
38
+ if ann['category_id'] in context_cat_id:
39
+ context_related_img_ids.append(ann['image_id'])
40
+
41
+ context_related_img_ids = set(context_related_img_ids)
42
+
43
+ # Get the images that are not already in the context_related_img set
44
+ remaining_img_ids = img_ids - context_related_img_ids
45
+
46
+ # Split the remaining images into two groups with a ratio of 6:1
47
+ num_train = int(len(remaining_img_ids) * 6 / 7)
48
+ train_images = random.sample(remaining_img_ids, num_train)
49
+ val_images = remaining_img_ids - set(train_images)
50
+
51
+ print(len(train_images), len(val_images), len(context_related_img_ids))
52
+
53
+ df_data = []
54
+ df_data.extend([[img_id, 0] for img_id in train_images])
55
+ df_data.extend([[img_id, 1] for img_id in val_images])
56
+ df_data.extend([[img_id, 2] for img_id in context_related_img_ids])
57
+ df = pd.DataFrame(df_data, columns=['img_id', 'fold'])
58
+ df.to_csv('./fold_context.csv', index=False)
dataset/get_union_test.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ FORMAT = 'det'
4
+
5
+ TASK = 'interactable'
6
+ # TASK = 'semantics'
7
+ # TASK = 'interaction'
8
+
9
+ DATASET_PATH = f'./data/coco_{FORMAT}/annotations/{TASK}.json'
10
+ TEST_613_PATH = f'../evaluation/gts/{FORMAT}/{TASK}/613.json'
11
+ TEST_GENRE_PATH = f'../evaluation/gts/{FORMAT}/{TASK}/genre.json'
12
+ TEST_CAT_PATH = f'../evaluation/gts/{FORMAT}/{TASK}/cat.json'
13
+ UNION_PATH = f'../evaluation/gts/{FORMAT}/{TASK}/union_test.json'
14
+ UNION3_PATH = f'../evaluation/gts/{FORMAT}/{TASK}/union3_test.json'
15
+
16
+
17
+ def get_union_test(fullset, testset1, testset2):
18
+
19
+ union_test = {'images': [], 'categories': fullset['categories'], 'annotations': []}
20
+
21
+ img_ids = []
22
+
23
+ for img in testset1['images']:
24
+ img_ids.append(img['id'])
25
+ union_test['images'].append(img)
26
+ for img in testset2['images']:
27
+ if img['id'] not in img_ids:
28
+ union_test['images'].append(img)
29
+ img_ids.append(img['id'])
30
+
31
+ for ann in fullset['annotations']:
32
+ if ann['image_id'] in img_ids:
33
+ union_test['annotations'].append(ann)
34
+
35
+ return union_test
36
+
37
+
38
+ with open(DATASET_PATH, 'r') as f:
39
+ dataset = json.load(f)
40
+ with open(TEST_613_PATH, 'r') as f:
41
+ test_613 = json.load(f)
42
+ with open(TEST_GENRE_PATH, 'r') as f:
43
+ test_genre = json.load(f)
44
+ with open(TEST_CAT_PATH, 'r') as f:
45
+ test_cat = json.load(f)
46
+
47
+ union_test = get_union_test(dataset, test_613, test_genre)
48
+ with open(UNION_PATH, 'w') as f:
49
+ json.dump(union_test, f, indent=4)
50
+ union3_test = get_union_test(dataset, union_test, test_cat)
51
+ with open(UNION3_PATH, 'w') as f:
52
+ json.dump(union3_test, f, indent=4)
dataset/get_val_gt.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+
4
+ # SPLIT = '613'
5
+ # TRAIN_FOLD = '0,1,4,5,7,9'
6
+ # VAL_FOLD = '3'
7
+ # TEST_FOLD = '2,6,8'
8
+ # SPLIT_FILE = 'fold_app.csv'
9
+
10
+ # SPLIT = 'genre'
11
+ # TRAIN_FOLD = '0'
12
+ # VAL_FOLD = '1'
13
+ # TEST_FOLD = '2'
14
+ # SPLIT_FILE = 'fold_genre.csv'
15
+
16
+ SPLIT = 'cat'
17
+ TRAIN_FOLD = '0'
18
+ VAL_FOLD = '1'
19
+ TEST_FOLD = '2'
20
+ SPLIT_FILE = 'fold_cat.csv'
21
+
22
+ INPUT_PATH = './data'
23
+ OUTPUT_PATH = '../evaluation/gts'
24
+ TASKS = ['interaction', 'semantics', 'interactable']
25
+ FORMATS = ['det']
26
+ split_script_file = './split_coco.py'
27
+ # fold_file = './fold.csv' # split on image
28
+
29
+
30
+ for task in TASKS:
31
+ for format in FORMATS:
32
+ ann_file = os.path.join(INPUT_PATH, f'coco_{format}/annotations/{task}.json')
33
+ img_dir = os.path.join(INPUT_PATH, 'coco_merged/images', task)
34
+ split_path = os.path.join(INPUT_PATH, 'coco_split')
35
+ if os.path.exists(split_path):
36
+ shutil.rmtree(split_path)
37
+ # K fold
38
+ # for test_fold in range(FOLD_CNT):
39
+ # train_fold = ','.join([str(i) for i in range(FOLD_CNT) if i != test_fold])
40
+ # cli = f'python {split_script_file} ' + \
41
+ # f'--ann_file {ann_file} ' + \
42
+ # f'--img_dir {img_dir} ' + \
43
+ # f'--output_path {split_path} ' + \
44
+ # f'--fold_file {fold_file} ' + \
45
+ # f'--train_fold {train_fold} ' + \
46
+ # f'--val_fold {test_fold}'
47
+ # print(cli)
48
+ # os.system(cli)
49
+ # os.makedirs(os.path.join(OUTPUT_PATH, f'{format}/{task}'), exist_ok=True)
50
+ # shutil.copyfile(os.path.join(split_path, 'annotations', 'instances_val2017.json'), os.path.join(OUTPUT_PATH, f'{format}/{task}/fold{test_fold}.json'))
51
+ # shutil.rmtree(split_path)
52
+
53
+ # custom split
54
+ cli = f'python {split_script_file} ' + \
55
+ f'--ann_file {ann_file} ' + \
56
+ f'--img_dir {img_dir} ' + \
57
+ f'--output_path {split_path} ' + \
58
+ f'--fold_file {SPLIT_FILE} ' + \
59
+ f'--train_fold {TRAIN_FOLD} ' + \
60
+ f'--val_fold {VAL_FOLD} ' + \
61
+ f'--test_fold {TEST_FOLD} '
62
+ print(cli)
63
+ os.system(cli)
64
+ os.makedirs(os.path.join(OUTPUT_PATH, f'{format}/{task}'), exist_ok=True)
65
+ shutil.copy2(os.path.join(split_path, 'annotations', 'instances_test2017.json'), os.path.join(OUTPUT_PATH, f'{format}/{task}/{SPLIT}.json'))
66
+ shutil.rmtree(split_path)
dataset/interactable_coco.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # obtain interactable dataset from semantics dataset
2
+ import json
3
+ import os
4
+ import argparse
5
+
6
+ parser = argparse.ArgumentParser()
7
+ parser.add_argument('--input', type=str, required=True)
8
+ parser.add_argument('--output', type=str, required=True)
9
+ args = parser.parse_args()
10
+
11
+ with open(args.input, 'r') as fr:
12
+ data = json.load(fr)
13
+
14
+ data['categories'] = [{'id': 1, 'name': 'interactable'}]
15
+ for annotation in data['annotations']:
16
+ annotation['category_id'] = 1
17
+
18
+ with open(args.output, 'w') as fw:
19
+ json.dump(data, fw, indent=4)
dataset/list-union3.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ 438100001, 438100002, 438100003, 438100004, 438100005, 438100006, 438100007, 438100008, 438100009, 438100010, 457380008, 457380010, 457380013, 1250210001, 1250210002, 1250210003, 1250210004, 1250210005, 1250210006, 1250210007, 1250210008, 1250210009, 1250210010, 1250210011, 1250210012, 1676620003, 1676620004, 1676620005, 1676620006, 1676620007, 1730290001, 1730290002, 1730290003, 1730290004, 1730290005, 497820001, 497820002, 497820003, 1178780015, 1486660012, 1456280001, 1456280002, 1456280003, 1456280004, 591680001, 591680002, 591680003, 591680004, 591680005, 591680006, 1707840007, 591680008, 591680009, 591680010, 591680011, 591680012, 591680013, 591680007, 1394410001, 1394410002, 1394410003, 1394410004, 1394410005, 622310001, 622310002, 716260002, 716260003, 716260005, 790750001, 790750002, 790750003, 790750004, 790750005, 790750006, 954160001, 954160002, 1561560001, 1561560002, 1561560003, 1561560004, 1561560005, 1561560006, 1561560007, 1561560008, 1561560009, 1561560010, 1561560011, 1561560012, 1561560013, 1561560014, 1561560015, 1561560016, 1561560017, 1561560018, 1561560019, 1561560020, 1561560021, 1561560022, 1561560023, 1561560024, 1561560025, 1561560026, 1561560027, 1561560028, 1561560029, 1561560030, 1561560031, 1561560032, 726910002, 726910003, 726910004, 726910005, 726910007, 726910010, 2057000001, 2057000002, 2057000003, 2057000004, 2057000005, 2057000006, 2057000007, 2057000008, 2057000009, 2057000010, 2057000011, 2057000012, 2057000013, 2057000014, 2057000015, 2057000016, 2057000017, 2057000018, 2057000019, 2057000020, 2057000021, 2057000022, 2057000023, 2057000024, 2057000025, 2057000026, 2057000027, 2057000028, 2057000029, 2057000030, 2057000031, 2057000032, 2057000033, 2057000034, 2057000035, 2057000036, 2057000037, 2057000038, 2057000039, 2057000040, 2057000041, 2057000042, 2057000043, 2057000044, 2057000045, 2057000046, 2057000047, 2057000048, 2057000049, 2057000050, 2057000051, 2057000052, 2057000053, 2057000054, 2057000055, 2057000056, 2057000057, 2057000058, 2057000059, 2057000060, 2057000061, 2057000062, 2057000063, 2057000064, 2057000065, 2057000066, 2057000067, 2057000068, 2057000069, 2057000070, 2057000071, 2057000072, 2057000073, 2057000074, 2057000075, 2057000076, 2057000077, 2057000078, 2057000079, 2057000080, 2057000081, 2057000082, 2057000083, 2057000084, 2057000085, 2057000086, 2057000087, 2057000088, 2057000089, 2057000090, 2057000091, 2057000092, 2057000093, 2057000094, 2057000095, 2057000096, 2057000097, 2057000098, 2057000099, 2057000100, 2057000101, 2057000102, 2057000103, 2057000104, 2057000105, 2057000106, 2057000107, 2057000108, 2057000109, 2057000110, 2057000111, 2057000112, 2057000113, 2057000114, 2057000115, 2057000116, 2057000117, 2057000118, 2057000119, 2057000120, 2057000121, 2057000122, 2057000123, 2057000124, 2057000125, 2057000126, 2057000127, 2057000128, 2057000129, 2057000130, 2057000131, 2057000132, 2057000133, 2057000134, 2057000135, 2057000136, 2057000137, 2057000138, 2057000139, 2057000140, 2057000141, 2057000142, 2057000143, 2057000144, 2057000145, 2057000146, 2057000147, 2057000148, 2057000149, 2057000150, 2057000151, 2057000152, 2057000153, 2057000154, 2057000155, 2057000156, 2057000157, 2057000158, 2057000159, 2057000160, 2057000161, 2057000162, 2057000163, 2057000164, 2057000165, 2057000166, 2057000167, 2057000168, 2057000169, 2057000170, 2057000171, 2057000172, 2057000173, 2057000174, 2057000175, 2057000176, 2057000177, 2057000178, 2193270001, 1334900003, 1334900004, 1334900005, 1334900006, 1550280001, 1404360004, 1245640005, 1245640006, 1404360005, 1404360006, 1245640009, 1404360007, 1404360009, 1404360010, 1550280010, 1245640014, 1245640015, 1245640016, 1404360016, 1404360018, 1550280018, 269170001, 1550280012, 1404360022, 1404360023, 269170002, 269170009, 269170008, 1550280027, 269170003, 1404360029, 1550280029, 1550280030, 1245640032, 1550280031, 1245640034, 1550280035, 1245640036, 1245640037, 1550280036, 1550280037, 1550280039, 1550280041, 1550280042, 1550280043, 1550280045, 1550280049, 1805510001, 1550280051, 1550280052, 1550280053, 1805510002, 1805510003, 1805510006, 1805510008, 1805510009, 1805510010, 1805510011, 1805510012, 1805510005, 1805510015, 1805510016, 1805510013, 1805510014, 1805510017, 1805510018, 1805510021, 1805510019, 1805510020, 1805510022, 1805510023, 1805510026, 1805510024, 1805510025, 1805510027, 1805510028, 1805510029, 1805510030, 1805510031, 1805510032, 1805510033, 2163140004, 2163140006, 2163140007, 2163140011, 1550280013, 2163140012, 2163140014, 2163140015, 2163140016, 2163140022, 2163140024, 2163140025, 2163140029, 2163140033, 2163140034, 2163140038, 2163140039, 2163140042, 2163140043, 2163140048, 2163140050, 2163140053, 2163140055, 2163140057, 2163140058, 1150310001, 1150310002, 1150310003, 1150310004, 1150310005, 1150310006, 1337530003, 1337530005, 1337530006, 1337530009, 1337530010, 1337530013, 1337530014, 1337530017, 1337530018, 1337530022, 1337530023, 1337530024, 1337530027, 1337530034, 1248270004, 1248270005, 1337530037, 1248270008, 1337530041, 1248270010, 1248270011, 1248270012, 1337530046, 1337530048, 1337530049, 1337530050, 1337530057, 1337530061, 1337530063, 1337530067, 1337530069, 1337530070, 1337530075, 1337530077, 1337530079, 982710005, 982710006, 982710008, 518580001, 518580002, 518580003, 518580004, 518580005, 518580006, 518580010, 518580011, 518580015, 518580016, 518580023, 518580024, 731790005, 731790010, 1488730001, 998660001, 998660002, 998660003, 998660004, 998660005, 2077870001, 2077870002, 2077870003, 2077870004, 2077870005, 720300003, 812460003, 812460004, 812460005, 812460006, 720300008, 812460007, 812460008, 812460009, 720300012, 812460010, 720300017, 1388030001, 269170004, 269170005, 269170006, 269170007, 457550001, 457550002, 457550003, 457550004, 457550005, 1480650002, 1453730001, 1453730002, 1453730003, 1453730004, 1453730005, 1453730006, 1453730007, 1453730008, 1453730009, 1453730010, 1453730011, 1453730012, 1453730013, 1453730014, 1480650007, 600140001, 600140002, 600140003, 600140004, 600140005, 600140006, 600140007, 600140008, 600140009, 600140010, 600140011, 600140012, 600140013, 600140014, 600140015, 600140016, 600140017, 600140018, 600140019, 600140020, 600140021, 600140022, 600140023, 600140024, 866540001, 866540002, 866540003, 866540004, 866540005, 866540006, 866540007, 866540008, 866540009, 866540010, 866540011, 1652800001, 1652800002, 1652800003, 1652800004, 1652800005, 1652800006, 1652800007, 1652800008, 1652800009, 1652800010, 1652800011, 1652800012, 1652800013, 2224020001, 2224020002, 2224020003, 2224020004, 2224020005, 2224020006, 2224020007, 2224020008, 2224020009, 2224020010, 2224020011, 2224020012, 2224020013, 2224020014, 2224020015, 2224020016, 2224020017, 2224020018, 2224020019, 2224020020, 2224020021, 2224020022, 2224020023, 2224020024, 2224020025, 2224020026, 2224020027, 2224020028, 2224020029, 2224020030, 2224020031, 2224020032, 2224020033, 2224020034, 2224020035, 2224020036, 2224020037, 2224020038, 2224020039, 2224020040, 2224020041, 2224020042, 2224020043, 2224020044, 2224020045, 2224020046, 2224020047, 2224020048, 2224020049, 2224020050, 2224020051, 2224020052, 2224020053, 2224020054, 2224020055, 2224020056, 2224020057, 2224020058, 1805510004, 1805510007, 1337060007, 1337060008, 1337060009, 1337060010, 1337060011, 1337060012, 1337060015, 731790001, 1337060018, 1337060019, 1337060020, 1337060021, 1337060022, 1337060023, 1337060024, 1337060025, 1337060026, 731790002, 731790003, 731790004, 1337060030, 731790006, 731790007, 731790008, 731790009, 1337060035, 891960001, 891960002, 891960003, 891960004, 891960005, 891960006, 891960007, 891960008, 891960009, 891960010, 451980001, 451980002, 451980003, 451980004, 451980005, 451980006, 451980007, 451980008, 451980009, 451980010, 451980011, 451980012, 451980013, 451980014, 451980015, 451980016, 451980017, 451980018, 1512840001, 1512840002, 1512840003, 1512840004, 1512840005, 1512840006, 1512840007, 2089520001, 2089520002, 2089520003, 2089520004, 1269890001, 1269890002, 1269890003, 1269890004, 1269890005, 1269890006, 1269890007, 1269890008, 1269890009, 1269890010, 1269890011, 1269890012, 1269890013, 1269890014, 1269890015, 997760001, 997760002, 997760003, 997760004, 997760005, 997760006, 997760007, 997760008, 997760009, 997760010, 997760011, 997760012, 1270910001, 1270910002, 1270910003, 1270910004, 1270910005, 2025000001, 2025000002, 2025000003, 2025000004, 2025000005, 2025000006, 660520005, 2025000007, 2025000009, 2025000010, 660520009, 2025000008, 1862180001, 1862180002, 1862180003, 1862180004, 1862180005, 1862180006, 1862180007, 1157070001, 1382350002, 1157070002, 1382350004, 1157070003, 1382350006, 1382350007, 1382350008, 1157070004, 1157070005, 1157070006, 1157070007, 1157070008, 1931980011, 1264160001, 1264160002, 1264160003, 1264160004, 1264160005, 1264160006, 1264160007, 1264160008, 1264160009, 1264160010, 1264160011, 1264160012, 1264160013, 1264160014, 1264160015, 1264160016, 1480650001, 898080018, 1480650003, 1298890004, 1480650004, 1298890006, 1480650005, 1298890008, 1298890009, 1298890010, 1480650006, 1298890012, 1298890013, 1298890014, 1480650008, 1298890016, 898080026, 898080027, 1298890019, 1298890020, 1298890021, 714100004, 714100006, 714100007, 1026760001, 1026760002, 1026760003, 1026760004, 1026760005, 1026760006, 1026760007, 1026760008, 1026760009, 1026760010, 1026760011, 1026760012, 1026760013, 1026760014, 1026760015, 1026760016, 1026760017, 1026760018, 1026760019, 1026760020, 1026760021, 1026760022, 1026760023, 1026760024, 1026760025, 1026760026, 1026760027, 1113370001, 1210650002, 1113370002, 1113370003, 1210650005, 1113370004, 1210650011, 1210650012, 1210650013, 1210650014, 528580003, 528580004, 1210650023, 1210650024, 1210650025, 1210650026, 1210650027, 1210650028, 1264160017, 758210001, 758210002, 758210003, 758210004, 758210005, 758210006, 758210007, 758210008, 758210009, 758210010, 758210011, 758210012, 758210013, 758210014, 1906880001, 1906880002, 1906880003, 1063530001, 1063530002, 1063530004, 1063530007, 1063530008, 1063530009, 1063530010, 1063530011, 1063530012, 1063530014, 1063530015, 1063530016, 1063530018, 1063530019, 1063530021, 1063530023, 1063530029, 1063530032, 1063530048, 1063530052, 898080019, 1063530057, 1063530059, 1063530065, 1063530067, 1063530069, 1063530070, 898080022, 1063530073, 1063530074, 1063530079, 1063530083, 898080028, 463290002, 463290003, 463290008, 1595490008, 490250001, 490250002, 490250003, 490250004, 490250005, 490250006, 490250007, 490250008, 490250009, 490250010, 490250011, 490250012, 490250013, 490250014, 490250015, 490250016, 490250017, 1825460002, 1825460003, 490250018, 490250019, 1825460001, 1825460004, 1825460005, 1825460009, 1825460006, 1825460007, 1825460008, 1825460010, 1825460011, 1825460012, 815280001, 815280002, 815280003, 815280004, 815280005, 815280006, 815280007, 815280008, 815280009, 815280010, 815280011, 815280012, 815280013, 815280014, 815280015, 815280016, 815280017, 815280018, 2020950001, 2020950002, 2020950003
dataset/list_union3.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ 438100001, 438100002, 438100003, 438100004, 438100005, 438100006, 438100007, 438100008, 438100009, 438100010, 457380008, 457380010, 457380013, 1250210001, 1250210002, 1250210003, 1250210004, 1250210005, 1250210006, 1250210007, 1250210008, 1250210009, 1250210010, 1250210011, 1250210012, 1676620003, 1676620004, 1676620005, 1676620006, 1676620007, 1730290001, 1730290002, 1730290003, 1730290004, 1730290005, 497820001, 497820002, 497820003, 1178780015, 1486660012, 1456280001, 1456280002, 1456280003, 1456280004, 591680001, 591680002, 591680003, 591680004, 591680005, 591680006, 1707840007, 591680008, 591680009, 591680010, 591680011, 591680012, 591680013, 591680007, 1394410001, 1394410002, 1394410003, 1394410004, 1394410005, 622310001, 622310002, 716260002, 716260003, 716260005, 790750001, 790750002, 790750003, 790750004, 790750005, 790750006, 954160001, 954160002, 1561560001, 1561560002, 1561560003, 1561560004, 1561560005, 1561560006, 1561560007, 1561560008, 1561560009, 1561560010, 1561560011, 1561560012, 1561560013, 1561560014, 1561560015, 1561560016, 1561560017, 1561560018, 1561560019, 1561560020, 1561560021, 1561560022, 1561560023, 1561560024, 1561560025, 1561560026, 1561560027, 1561560028, 1561560029, 1561560030, 1561560031, 1561560032, 726910002, 726910003, 726910004, 726910005, 726910007, 726910010, 2057000001, 2057000002, 2057000003, 2057000004, 2057000005, 2057000006, 2057000007, 2057000008, 2057000009, 2057000010, 2057000011, 2057000012, 2057000013, 2057000014, 2057000015, 2057000016, 2057000017, 2057000018, 2057000019, 2057000020, 2057000021, 2057000022, 2057000023, 2057000024, 2057000025, 2057000026, 2057000027, 2057000028, 2057000029, 2057000030, 2057000031, 2057000032, 2057000033, 2057000034, 2057000035, 2057000036, 2057000037, 2057000038, 2057000039, 2057000040, 2057000041, 2057000042, 2057000043, 2057000044, 2057000045, 2057000046, 2057000047, 2057000048, 2057000049, 2057000050, 2057000051, 2057000052, 2057000053, 2057000054, 2057000055, 2057000056, 2057000057, 2057000058, 2057000059, 2057000060, 2057000061, 2057000062, 2057000063, 2057000064, 2057000065, 2057000066, 2057000067, 2057000068, 2057000069, 2057000070, 2057000071, 2057000072, 2057000073, 2057000074, 2057000075, 2057000076, 2057000077, 2057000078, 2057000079, 2057000080, 2057000081, 2057000082, 2057000083, 2057000084, 2057000085, 2057000086, 2057000087, 2057000088, 2057000089, 2057000090, 2057000091, 2057000092, 2057000093, 2057000094, 2057000095, 2057000096, 2057000097, 2057000098, 2057000099, 2057000100, 2057000101, 2057000102, 2057000103, 2057000104, 2057000105, 2057000106, 2057000107, 2057000108, 2057000109, 2057000110, 2057000111, 2057000112, 2057000113, 2057000114, 2057000115, 2057000116, 2057000117, 2057000118, 2057000119, 2057000120, 2057000121, 2057000122, 2057000123, 2057000124, 2057000125, 2057000126, 2057000127, 2057000128, 2057000129, 2057000130, 2057000131, 2057000132, 2057000133, 2057000134, 2057000135, 2057000136, 2057000137, 2057000138, 2057000139, 2057000140, 2057000141, 2057000142, 2057000143, 2057000144, 2057000145, 2057000146, 2057000147, 2057000148, 2057000149, 2057000150, 2057000151, 2057000152, 2057000153, 2057000154, 2057000155, 2057000156, 2057000157, 2057000158, 2057000159, 2057000160, 2057000161, 2057000162, 2057000163, 2057000164, 2057000165, 2057000166, 2057000167, 2057000168, 2057000169, 2057000170, 2057000171, 2057000172, 2057000173, 2057000174, 2057000175, 2057000176, 2057000177, 2057000178, 2193270001, 1334900003, 1334900004, 1334900005, 1334900006, 1550280001, 1404360004, 1245640005, 1245640006, 1404360005, 1404360006, 1245640009, 1404360007, 1404360009, 1404360010, 1550280010, 1245640014, 1245640015, 1245640016, 1404360016, 1404360018, 1550280018, 269170001, 1550280012, 1404360022, 1404360023, 269170002, 269170009, 269170008, 1550280027, 269170003, 1404360029, 1550280029, 1550280030, 1245640032, 1550280031, 1245640034, 1550280035, 1245640036, 1245640037, 1550280036, 1550280037, 1550280039, 1550280041, 1550280042, 1550280043, 1550280045, 1550280049, 1805510001, 1550280051, 1550280052, 1550280053, 1805510002, 1805510003, 1805510006, 1805510008, 1805510009, 1805510010, 1805510011, 1805510012, 1805510005, 1805510015, 1805510016, 1805510013, 1805510014, 1805510017, 1805510018, 1805510021, 1805510019, 1805510020, 1805510022, 1805510023, 1805510026, 1805510024, 1805510025, 1805510027, 1805510028, 1805510029, 1805510030, 1805510031, 1805510032, 1805510033, 2163140004, 2163140006, 2163140007, 2163140011, 1550280013, 2163140012, 2163140014, 2163140015, 2163140016, 2163140022, 2163140024, 2163140025, 2163140029, 2163140033, 2163140034, 2163140038, 2163140039, 2163140042, 2163140043, 2163140048, 2163140050, 2163140053, 2163140055, 2163140057, 2163140058, 1150310001, 1150310002, 1150310003, 1150310004, 1150310005, 1150310006, 1337530003, 1337530005, 1337530006, 1337530009, 1337530010, 1337530013, 1337530014, 1337530017, 1337530018, 1337530022, 1337530023, 1337530024, 1337530027, 1337530034, 1248270004, 1248270005, 1337530037, 1248270008, 1337530041, 1248270010, 1248270011, 1248270012, 1337530046, 1337530048, 1337530049, 1337530050, 1337530057, 1337530061, 1337530063, 1337530067, 1337530069, 1337530070, 1337530075, 1337530077, 1337530079, 982710005, 982710006, 982710008, 518580001, 518580002, 518580003, 518580004, 518580005, 518580006, 518580010, 518580011, 518580015, 518580016, 518580023, 518580024, 731790005, 731790010, 1488730001, 998660001, 998660002, 998660003, 998660004, 998660005, 2077870001, 2077870002, 2077870003, 2077870004, 2077870005, 720300003, 812460003, 812460004, 812460005, 812460006, 720300008, 812460007, 812460008, 812460009, 720300012, 812460010, 720300017, 1388030001, 269170004, 269170005, 269170006, 269170007, 457550001, 457550002, 457550003, 457550004, 457550005, 1480650002, 1453730001, 1453730002, 1453730003, 1453730004, 1453730005, 1453730006, 1453730007, 1453730008, 1453730009, 1453730010, 1453730011, 1453730012, 1453730013, 1453730014, 1480650007, 600140001, 600140002, 600140003, 600140004, 600140005, 600140006, 600140007, 600140008, 600140009, 600140010, 600140011, 600140012, 600140013, 600140014, 600140015, 600140016, 600140017, 600140018, 600140019, 600140020, 600140021, 600140022, 600140023, 600140024, 866540001, 866540002, 866540003, 866540004, 866540005, 866540006, 866540007, 866540008, 866540009, 866540010, 866540011, 1652800001, 1652800002, 1652800003, 1652800004, 1652800005, 1652800006, 1652800007, 1652800008, 1652800009, 1652800010, 1652800011, 1652800012, 1652800013, 2224020001, 2224020002, 2224020003, 2224020004, 2224020005, 2224020006, 2224020007, 2224020008, 2224020009, 2224020010, 2224020011, 2224020012, 2224020013, 2224020014, 2224020015, 2224020016, 2224020017, 2224020018, 2224020019, 2224020020, 2224020021, 2224020022, 2224020023, 2224020024, 2224020025, 2224020026, 2224020027, 2224020028, 2224020029, 2224020030, 2224020031, 2224020032, 2224020033, 2224020034, 2224020035, 2224020036, 2224020037, 2224020038, 2224020039, 2224020040, 2224020041, 2224020042, 2224020043, 2224020044, 2224020045, 2224020046, 2224020047, 2224020048, 2224020049, 2224020050, 2224020051, 2224020052, 2224020053, 2224020054, 2224020055, 2224020056, 2224020057, 2224020058, 1805510004, 1805510007, 1337060007, 1337060008, 1337060009, 1337060010, 1337060011, 1337060012, 1337060015, 731790001, 1337060018, 1337060019, 1337060020, 1337060021, 1337060022, 1337060023, 1337060024, 1337060025, 1337060026, 731790002, 731790003, 731790004, 1337060030, 731790006, 731790007, 731790008, 731790009, 1337060035, 891960001, 891960002, 891960003, 891960004, 891960005, 891960006, 891960007, 891960008, 891960009, 891960010, 451980001, 451980002, 451980003, 451980004, 451980005, 451980006, 451980007, 451980008, 451980009, 451980010, 451980011, 451980012, 451980013, 451980014, 451980015, 451980016, 451980017, 451980018, 1512840001, 1512840002, 1512840003, 1512840004, 1512840005, 1512840006, 1512840007, 2089520001, 2089520002, 2089520003, 2089520004, 1269890001, 1269890002, 1269890003, 1269890004, 1269890005, 1269890006, 1269890007, 1269890008, 1269890009, 1269890010, 1269890011, 1269890012, 1269890013, 1269890014, 1269890015, 997760001, 997760002, 997760003, 997760004, 997760005, 997760006, 997760007, 997760008, 997760009, 997760010, 997760011, 997760012, 1270910001, 1270910002, 1270910003, 1270910004, 1270910005, 2025000001, 2025000002, 2025000003, 2025000004, 2025000005, 2025000006, 660520005, 2025000007, 2025000009, 2025000010, 660520009, 2025000008, 1862180001, 1862180002, 1862180003, 1862180004, 1862180005, 1862180006, 1862180007, 1157070001, 1382350002, 1157070002, 1382350004, 1157070003, 1382350006, 1382350007, 1382350008, 1157070004, 1157070005, 1157070006, 1157070007, 1157070008, 1931980011, 1264160001, 1264160002, 1264160003, 1264160004, 1264160005, 1264160006, 1264160007, 1264160008, 1264160009, 1264160010, 1264160011, 1264160012, 1264160013, 1264160014, 1264160015, 1264160016, 1480650001, 898080018, 1480650003, 1298890004, 1480650004, 1298890006, 1480650005, 1298890008, 1298890009, 1298890010, 1480650006, 1298890012, 1298890013, 1298890014, 1480650008, 1298890016, 898080026, 898080027, 1298890019, 1298890020, 1298890021, 714100004, 714100006, 714100007, 1026760001, 1026760002, 1026760003, 1026760004, 1026760005, 1026760006, 1026760007, 1026760008, 1026760009, 1026760010, 1026760011, 1026760012, 1026760013, 1026760014, 1026760015, 1026760016, 1026760017, 1026760018, 1026760019, 1026760020, 1026760021, 1026760022, 1026760023, 1026760024, 1026760025, 1026760026, 1026760027, 1113370001, 1210650002, 1113370002, 1113370003, 1210650005, 1113370004, 1210650011, 1210650012, 1210650013, 1210650014, 528580003, 528580004, 1210650023, 1210650024, 1210650025, 1210650026, 1210650027, 1210650028, 1264160017, 758210001, 758210002, 758210003, 758210004, 758210005, 758210006, 758210007, 758210008, 758210009, 758210010, 758210011, 758210012, 758210013, 758210014, 1906880001, 1906880002, 1906880003, 1063530001, 1063530002, 1063530004, 1063530007, 1063530008, 1063530009, 1063530010, 1063530011, 1063530012, 1063530014, 1063530015, 1063530016, 1063530018, 1063530019, 1063530021, 1063530023, 1063530029, 1063530032, 1063530048, 1063530052, 898080019, 1063530057, 1063530059, 1063530065, 1063530067, 1063530069, 1063530070, 898080022, 1063530073, 1063530074, 1063530079, 1063530083, 898080028, 463290002, 463290003, 463290008, 1595490008, 490250001, 490250002, 490250003, 490250004, 490250005, 490250006, 490250007, 490250008, 490250009, 490250010, 490250011, 490250012, 490250013, 490250014, 490250015, 490250016, 490250017, 1825460002, 1825460003, 490250018, 490250019, 1825460001, 1825460004, 1825460005, 1825460009, 1825460006, 1825460007, 1825460008, 1825460010, 1825460011, 1825460012, 815280001, 815280002, 815280003, 815280004, 815280005, 815280006, 815280007, 815280008, 815280009, 815280010, 815280011, 815280012, 815280013, 815280014, 815280015, 815280016, 815280017, 815280018, 2020950001, 2020950002, 2020950003
dataset/split_coco.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import shutil
4
+ import argparse
5
+ import random
6
+ import pandas as pd
7
+
8
+ FOLD_CNT = 10
9
+
10
+
11
+ def generate_fold(args):
12
+ df = pd.DataFrame(columns=['img_id', 'fold'])
13
+ json_file_path = args.ann_file
14
+ assert os.path.exists(json_file_path), f'json file not found: {json_file_path}'
15
+ with open(json_file_path, 'r') as f:
16
+ json_data = json.load(f)
17
+
18
+ # reproducable random shuffle
19
+ random.seed(1)
20
+ random.shuffle(json_data['images'])
21
+ fold_size = len(json_data['images']) // FOLD_CNT
22
+ for image in json_data['images']:
23
+ df.loc[len(df), df.columns] = [image['id'], min(len(df) // fold_size, FOLD_CNT - 1)]
24
+ df.to_csv(args.fold_file, index=False)
25
+
26
+
27
+ def generate_fold_app(args):
28
+ df = pd.DataFrame(columns=['img_id', 'fold'])
29
+ json_file_path = args.ann_file
30
+ assert os.path.exists(json_file_path), f'json file not found: {json_file_path}'
31
+ with open(json_file_path, 'r') as f:
32
+ json_data = json.load(f)
33
+
34
+ apps = set()
35
+
36
+ for image in json_data['images']:
37
+ appid = str(image['id'])[:-3]
38
+ apps.add(appid)
39
+
40
+ apps = list(apps)
41
+ bel_app = {}
42
+
43
+ # reproducable random shuffle
44
+ apps = sorted(apps)
45
+ random.seed(1)
46
+ random.shuffle(apps)
47
+ fold_size = len(apps) // FOLD_CNT
48
+ for app in apps:
49
+ bel_app[app] = min(len(bel_app) // fold_size, FOLD_CNT - 1)
50
+ for image in json_data['images']:
51
+ appid = str(image['id'])[:-3]
52
+ df.loc[len(df), df.columns] = [image['id'], bel_app[appid]]
53
+
54
+ df = df.sort_values(by='fold')
55
+ df.to_csv(args.fold_file, index=False)
56
+
57
+
58
+ def split(args):
59
+ json_file_path = args.ann_file
60
+ image_path = args.img_dir if args.img_dir and os.path.exists(args.img_dir) else None
61
+ fold_file = args.fold_file
62
+ output_path = args.output_path
63
+
64
+ assert os.path.exists(json_file_path), f'json file not found: {json_file_path}'
65
+ # assert os.path.exists(image_path), f'image path not found: {image_path}'
66
+ assert os.path.exists(fold_file), f'fold file not found: {fold_file}'
67
+
68
+ with open(json_file_path, 'r') as f:
69
+ json_data = json.load(f)
70
+
71
+ df = pd.read_csv(fold_file)
72
+
73
+ train_dataset = {'images': list(), 'categories': json_data['categories'], 'annotations': list()}
74
+ val_dataset = {'images': list(), 'categories': json_data['categories'], 'annotations': list()}
75
+ test_dataset = {'images': list(), 'categories': json_data['categories'], 'annotations': list()}
76
+
77
+ train_folds = [int(fold) for fold in args.train_folds.split(',')] if args.train_folds else []
78
+ val_folds = [int(fold) for fold in args.val_folds.split(',')] if args.val_folds else []
79
+ test_folds = [int(fold) for fold in args.test_folds.split(',')] if args.test_folds else []
80
+
81
+ print(f'train folds: {train_folds}')
82
+ print(f'val folds: {val_folds}')
83
+ print(f'test folds: {test_folds}')
84
+
85
+ train_imgid = set()
86
+ val_imgid = set()
87
+ test_imgid = set()
88
+
89
+ for _, row in df.iterrows():
90
+ if row['fold'] in train_folds:
91
+ train_imgid.add(row['img_id'])
92
+ elif row['fold'] in val_folds:
93
+ val_imgid.add(row['img_id'])
94
+ elif row['fold'] in test_folds:
95
+ test_imgid.add(row['img_id'])
96
+ else:
97
+ raise ValueError(f'fold not found: {row["fold"]}')
98
+
99
+ for image in json_data['images']:
100
+ if image['id'] in train_imgid:
101
+ train_dataset['images'].append(image)
102
+ elif image['id'] in val_imgid:
103
+ val_dataset['images'].append(image)
104
+ elif image['id'] in test_imgid:
105
+ test_dataset['images'].append(image)
106
+ else:
107
+ raise ValueError(f'image id not found: {image["id"]}')
108
+
109
+ for annotation in json_data['annotations']:
110
+ if annotation['image_id'] in train_imgid:
111
+ train_dataset['annotations'].append(annotation)
112
+ elif annotation['image_id'] in val_imgid:
113
+ val_dataset['annotations'].append(annotation)
114
+ elif annotation['image_id'] in test_imgid:
115
+ test_dataset['annotations'].append(annotation)
116
+ else:
117
+ raise ValueError(f'annotation image_id not found: {annotation["image_id"]}')
118
+
119
+ print(f'train dataset: {len(train_dataset["images"])} images, {len(train_dataset["annotations"])} annotations')
120
+ print(f'val dataset: {len(val_dataset["images"])} images, {len(val_dataset["annotations"])} annotations')
121
+ print(f'test dataset: {len(test_dataset["images"])} images, {len(test_dataset["annotations"])} annotations')
122
+
123
+ print(os.path.abspath(output_path))
124
+ train_image_path = os.path.join(output_path, 'images', 'instances_train2017')
125
+ val_image_path = os.path.join(output_path, 'images', 'instances_val2017')
126
+ test_image_path = os.path.join(output_path, 'images', 'instances_test2017')
127
+ os.makedirs(output_path, exist_ok=True)
128
+ os.makedirs(train_image_path, exist_ok=True)
129
+ os.makedirs(val_image_path, exist_ok=True)
130
+ os.makedirs(test_image_path, exist_ok=True)
131
+ os.makedirs(os.path.join(output_path, 'annotations'), exist_ok=True)
132
+
133
+ with open(os.path.join(output_path, 'annotations', 'instances_train2017.json'), 'w') as f:
134
+ json.dump(train_dataset, f, indent=4)
135
+ with open(os.path.join(output_path, 'annotations', 'instances_val2017.json'), 'w') as f:
136
+ json.dump(val_dataset, f, indent=4)
137
+ with open(os.path.join(output_path, 'annotations', 'instances_test2017.json'), 'w') as f:
138
+ json.dump(test_dataset, f, indent=4)
139
+
140
+ if image_path:
141
+ for image in train_dataset['images']:
142
+ shutil.copy(os.path.join(image_path, image['file_name']), train_image_path)
143
+ for image in val_dataset['images']:
144
+ shutil.copy(os.path.join(image_path, image['file_name']), val_image_path)
145
+ for image in test_dataset['images']:
146
+ shutil.copy(os.path.join(image_path, image['file_name']), test_image_path)
147
+
148
+
149
+ def main(args):
150
+ if args.gen:
151
+ generate_fold_app(args)
152
+ split(args)
153
+
154
+
155
+ if __name__ == '__main__':
156
+ parser = argparse.ArgumentParser()
157
+ parser.add_argument('--ann_file', type=str, required=True)
158
+ parser.add_argument('--img_dir', type=str, required=True)
159
+ parser.add_argument('--output_path', type=str, default='./coco_split')
160
+ parser.add_argument('--train_folds', type=str, default=None)
161
+ parser.add_argument('--val_folds', type=str, default=None)
162
+ parser.add_argument('--test_folds', type=str, default=None)
163
+ parser.add_argument('--gen', action='store_true')
164
+ parser.add_argument('--fold_file', type=str, required=True)
165
+ args = parser.parse_args()
166
+ main(args)
environment.reference.yml ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: orienter-ape-reference
2
+ channels:
3
+ - pytorch
4
+ - nvidia
5
+ - conda-forge
6
+ dependencies:
7
+ - python=3.10
8
+ - pip
9
+ - pytorch=2.2.0
10
+ - torchvision=0.17.0
11
+ - pytorch-cuda=12.1
12
+ - cuda-version=12.1
13
+ - numpy=1.22.4
14
+ - scipy=1.7.3
15
+ - pillow=12.3.0
16
+ - pip:
17
+ - beautifulsoup4==4.12.3
18
+ - cython==3.0.8
19
+ - einops==0.7.0
20
+ - fairscale==0.4.13
21
+ - fvcore==0.1.5.post20221221
22
+ - iopath==0.1.9
23
+ - lvis==0.5.3
24
+ - omegaconf==2.1.0
25
+ - opencv-python==4.9.0.80
26
+ - pycocotools==2.0.7
27
+ - pyyaml==6.0.1
28
+ - requests==2.31.0
29
+ - timm==0.9.12
30
+ - tqdm==4.66.1
31
+ - transformers==4.37.2
32
+ - xformers==0.0.24
33
+ - git+https://github.com/facebookresearch/detectron2.git@017abbfa5f2c2a2afa045200c2af9ccf2fc6227f
34
+ - git+https://github.com/IDEA-Research/detrex.git@776058ec229be37a5ff2a2b0bb54133bdd5da663
35
+ - git+https://github.com/openai/CLIP.git@d50d76daa670286dd6cacf3bcd80b5e4823fc8e1
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ beautifulsoup4>=4.12,<5
2
+ Pillow>=12.3,<13
3
+ PyYAML>=6,<7
4
+ requests>=2.31,<3
5
+ tqdm>=4.66,<5
tests/test_ape_stage.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import sys
3
+ import tempfile
4
+ import unittest
5
+ from pathlib import Path
6
+
7
+
8
+ REPO_ROOT = Path(__file__).resolve().parents[1]
9
+ sys.path.insert(0, str(REPO_ROOT))
10
+
11
+ from approach.ape_stage import extract_image_id, run_ape_stage
12
+
13
+
14
+ class ApeStageTests(unittest.TestCase):
15
+ def test_extract_image_id_requires_documented_basename_shape(self):
16
+ self.assertEqual(extract_image_id("123_4.jpg"), 123004)
17
+ with self.assertRaises(ValueError):
18
+ extract_image_id("123_4_view_a.png")
19
+ with self.assertRaises(ValueError):
20
+ extract_image_id("123_view_a.png")
21
+
22
+ def test_multiple_records_are_enriched_and_checkpointed_as_valid_json(self):
23
+ calls = []
24
+
25
+ def fake_inference(**kwargs):
26
+ calls.append(kwargs)
27
+ category = kwargs["text_prompt"].split(":", 1)[0]
28
+ return [
29
+ {
30
+ "category_name": category,
31
+ "bbox": [1, 2, 3, 4],
32
+ "score": 0.9,
33
+ }
34
+ ]
35
+
36
+ records = [
37
+ {"question_id": 0, "text": {"objects": {"button": "round red"}}},
38
+ {"question_id": 1, "text": {"objects": {"lever": "long silver"}}},
39
+ ]
40
+ questions = {
41
+ 0: {"image": "123_4.jpg"},
42
+ 1: {"image": "456_78.jpg"},
43
+ }
44
+
45
+ with tempfile.TemporaryDirectory() as tmpdir:
46
+ root = Path(tmpdir)
47
+ output_path = root / "predictions.json"
48
+ results, errors = run_ape_stage(
49
+ records=records,
50
+ questions=questions,
51
+ images_dir=root / "images",
52
+ output_path=output_path,
53
+ inference=fake_inference,
54
+ inference_kwargs={"confidence_threshold": 0.15},
55
+ resume=False,
56
+ )
57
+
58
+ on_disk = json.loads(output_path.read_text())
59
+
60
+ self.assertEqual(errors, [])
61
+ self.assertEqual(results, on_disk)
62
+ self.assertEqual(len(results), 2)
63
+ self.assertEqual(results[0]["image_id"], 123004)
64
+ self.assertEqual(results[0]["category_id"], "button")
65
+ self.assertEqual(results[1]["image_id"], 456078)
66
+ self.assertEqual(results[1]["category_id"], "lever")
67
+ self.assertEqual(calls[0]["confidence_threshold"], 0.15)
68
+
69
+ def test_resume_does_not_duplicate_completed_images(self):
70
+ calls = []
71
+
72
+ def fake_inference(**kwargs):
73
+ calls.append(kwargs)
74
+ return [
75
+ {
76
+ "category_name": "button",
77
+ "bbox": [1, 2, 3, 4],
78
+ "score": 0.9,
79
+ }
80
+ ]
81
+
82
+ records = [{"question_id": 0, "text": {"objects": {"button": "red"}}}]
83
+ questions = {0: {"image": "123_4.jpg"}}
84
+
85
+ with tempfile.TemporaryDirectory() as tmpdir:
86
+ root = Path(tmpdir)
87
+ output_path = root / "predictions.json"
88
+ first, _ = run_ape_stage(
89
+ records,
90
+ questions,
91
+ root,
92
+ output_path,
93
+ fake_inference,
94
+ resume=False,
95
+ )
96
+ second, _ = run_ape_stage(
97
+ records,
98
+ questions,
99
+ root,
100
+ output_path,
101
+ fake_inference,
102
+ resume=True,
103
+ )
104
+
105
+ self.assertEqual(first, second)
106
+ self.assertEqual(len(calls), 1)
107
+
108
+ def test_resume_tracks_completed_images_with_zero_detections(self):
109
+ calls = []
110
+
111
+ def empty_inference(**kwargs):
112
+ calls.append(kwargs)
113
+ return []
114
+
115
+ records = [{"question_id": "0", "text": {"objects": {"button": "red"}}}]
116
+ questions = {0: {"image": "123_4.jpg"}}
117
+
118
+ with tempfile.TemporaryDirectory() as tmpdir:
119
+ root = Path(tmpdir)
120
+ output_path = root / "predictions.json"
121
+ first, _ = run_ape_stage(
122
+ records,
123
+ questions,
124
+ root,
125
+ output_path,
126
+ empty_inference,
127
+ resume=False,
128
+ )
129
+ second, _ = run_ape_stage(
130
+ records,
131
+ questions,
132
+ root,
133
+ output_path,
134
+ empty_inference,
135
+ resume=True,
136
+ )
137
+
138
+ progress = json.loads((root / "predictions.progress.json").read_text())
139
+
140
+ self.assertEqual(first, [])
141
+ self.assertEqual(second, [])
142
+ self.assertEqual(calls, [calls[0]])
143
+ self.assertEqual(progress["completed_image_ids"], [123004])
144
+
145
+ def test_errors_are_recorded_without_corrupting_predictions(self):
146
+ def failing_inference(**kwargs):
147
+ raise RuntimeError("synthetic detector failure")
148
+
149
+ records = [{"question_id": 0, "text": {"objects": {"button": "red"}}}]
150
+ questions = {0: {"image": "123_4.jpg"}}
151
+
152
+ with tempfile.TemporaryDirectory() as tmpdir:
153
+ root = Path(tmpdir)
154
+ output_path = root / "predictions.json"
155
+ error_path = root / "errors.json"
156
+ results, errors = run_ape_stage(
157
+ records,
158
+ questions,
159
+ root,
160
+ output_path,
161
+ failing_inference,
162
+ error_path=error_path,
163
+ resume=False,
164
+ )
165
+
166
+ self.assertEqual(json.loads(output_path.read_text()), [])
167
+ self.assertEqual(json.loads(error_path.read_text()), errors)
168
+
169
+ self.assertEqual(results, [])
170
+ self.assertEqual(errors[0]["question_id"], 0)
171
+ self.assertEqual(errors[0]["error_type"], "RuntimeError")
172
+ self.assertNotIn("traceback", errors[0])
173
+
174
+
175
+ if __name__ == "__main__":
176
+ unittest.main()
tests/test_app_metadata_cache.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import tempfile
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ from approach.vlm.gpt4v.gpt4v import get_steam_app_data, load_app_metadata_cache
7
+
8
+
9
+ class AppMetadataCacheTests(unittest.TestCase):
10
+ def test_json_cache_supplies_app_context(self):
11
+ with tempfile.TemporaryDirectory() as tmpdir:
12
+ cache_path = Path(tmpdir) / "app_metadata.json"
13
+ cache_path.write_text(
14
+ json.dumps(
15
+ {
16
+ "123": {
17
+ "app_name": "Pinned App",
18
+ "app_description": "Pinned description",
19
+ }
20
+ }
21
+ ),
22
+ encoding="utf-8",
23
+ )
24
+
25
+ cache = load_app_metadata_cache(cache_path)
26
+
27
+ self.assertEqual(
28
+ get_steam_app_data("123", "123_4.jpg", metadata_cache=cache),
29
+ ("Pinned App", "Pinned description"),
30
+ )
31
+
32
+ def test_cache_missing_app_id_fails_closed(self):
33
+ with self.assertRaises(KeyError):
34
+ get_steam_app_data("999", "999_1.jpg", metadata_cache={})
35
+
36
+ def test_jsonl_cache_is_keyed_by_app_id(self):
37
+ with tempfile.TemporaryDirectory() as tmpdir:
38
+ cache_path = Path(tmpdir) / "app_metadata.jsonl"
39
+ cache_path.write_text(
40
+ json.dumps(
41
+ {
42
+ "app_id": 456,
43
+ "name": "JSONL App",
44
+ "description": "JSONL description",
45
+ }
46
+ )
47
+ + "\n",
48
+ encoding="utf-8",
49
+ )
50
+
51
+ cache = load_app_metadata_cache(cache_path)
52
+
53
+ self.assertEqual(
54
+ get_steam_app_data("456", "456_7.jpg", metadata_cache=cache),
55
+ ("JSONL App", "JSONL description"),
56
+ )
57
+
58
+ def test_duplicate_jsonl_app_id_is_rejected(self):
59
+ with tempfile.TemporaryDirectory() as tmpdir:
60
+ cache_path = Path(tmpdir) / "app_metadata.jsonl"
61
+ cache_path.write_text(
62
+ "\n".join(
63
+ [
64
+ json.dumps(
65
+ {
66
+ "app_id": 123,
67
+ "app_name": "First",
68
+ "app_description": "Description",
69
+ }
70
+ ),
71
+ json.dumps(
72
+ {
73
+ "app_id": "123",
74
+ "app_name": "Second",
75
+ "app_description": "Description",
76
+ }
77
+ ),
78
+ ]
79
+ )
80
+ + "\n",
81
+ encoding="utf-8",
82
+ )
83
+
84
+ with self.assertRaises(ValueError):
85
+ load_app_metadata_cache(cache_path)
86
+
87
+ def test_duplicate_json_object_key_is_rejected(self):
88
+ with tempfile.TemporaryDirectory() as tmpdir:
89
+ cache_path = Path(tmpdir) / "app_metadata.json"
90
+ cache_path.write_text(
91
+ '{"123":{"app_name":"First","app_description":"One"},'
92
+ '"123":{"app_name":"Second","app_description":"Two"}}',
93
+ encoding="utf-8",
94
+ )
95
+
96
+ with self.assertRaises(ValueError):
97
+ load_app_metadata_cache(cache_path)
98
+
99
+ def test_mapping_key_and_record_app_id_must_match(self):
100
+ with tempfile.TemporaryDirectory() as tmpdir:
101
+ cache_path = Path(tmpdir) / "app_metadata.json"
102
+ cache_path.write_text(
103
+ json.dumps(
104
+ {
105
+ "123": {
106
+ "app_id": "456",
107
+ "app_name": "Mismatch",
108
+ "app_description": "Description",
109
+ }
110
+ }
111
+ ),
112
+ encoding="utf-8",
113
+ )
114
+
115
+ with self.assertRaises(ValueError):
116
+ load_app_metadata_cache(cache_path)
117
+
118
+ def test_missing_cache_fields_fail_closed(self):
119
+ with self.assertRaises(ValueError):
120
+ get_steam_app_data("123", "123_4.jpg", metadata_cache={"123": {"app_name": "App"}})
121
+
122
+
123
+ if __name__ == "__main__":
124
+ unittest.main()
tests/test_cli_entrypoints.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import subprocess
3
+ import sys
4
+ import tempfile
5
+ import unittest
6
+ from pathlib import Path
7
+
8
+
9
+ ROOT = Path(__file__).resolve().parents[1]
10
+
11
+
12
+ class CliEntrypointTests(unittest.TestCase):
13
+ def test_run_vlm_script_path_supports_help(self):
14
+ result = subprocess.run(
15
+ [sys.executable, str(ROOT / "approach" / "run_vlm.py"), "--help"],
16
+ cwd=ROOT,
17
+ stdout=subprocess.PIPE,
18
+ stderr=subprocess.PIPE,
19
+ text=True,
20
+ check=False,
21
+ )
22
+
23
+ self.assertEqual(result.returncode, 0, result.stderr)
24
+ self.assertIn("--questions", result.stdout)
25
+
26
+ def test_run_vlm_returns_nonzero_when_a_record_fails(self):
27
+ with tempfile.TemporaryDirectory() as tmpdir:
28
+ root = Path(tmpdir)
29
+ questions = root / "questions.jsonl"
30
+ questions.write_text(
31
+ json.dumps(
32
+ {
33
+ "question_id": 0,
34
+ "image": "123_4.jpg",
35
+ "image_id": 123004,
36
+ "text": "mine",
37
+ }
38
+ )
39
+ + "\n",
40
+ encoding="utf-8",
41
+ )
42
+ metadata = root / "metadata.json"
43
+ metadata.write_text(
44
+ json.dumps(
45
+ {
46
+ "456": {
47
+ "app_name": "Different app",
48
+ "app_description": "Does not cover the selected screenshot.",
49
+ }
50
+ }
51
+ ),
52
+ encoding="utf-8",
53
+ )
54
+ result = subprocess.run(
55
+ [
56
+ sys.executable,
57
+ "-B",
58
+ str(ROOT / "approach" / "run_vlm.py"),
59
+ "--questions",
60
+ str(questions),
61
+ "--images-dir",
62
+ str(root / "images"),
63
+ "--output",
64
+ str(root / "answers.jsonl"),
65
+ "--app-metadata-cache",
66
+ str(metadata),
67
+ ],
68
+ cwd=ROOT,
69
+ stdout=subprocess.PIPE,
70
+ stderr=subprocess.PIPE,
71
+ text=True,
72
+ check=False,
73
+ )
74
+
75
+ self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
76
+ self.assertEqual(json.loads(result.stdout)["errors"], 1)
77
+
78
+ def test_run_ape_script_path_supports_help(self):
79
+ result = subprocess.run(
80
+ [sys.executable, str(ROOT / "approach" / "run_ape.py"), "--help"],
81
+ cwd=ROOT,
82
+ stdout=subprocess.PIPE,
83
+ stderr=subprocess.PIPE,
84
+ text=True,
85
+ check=False,
86
+ )
87
+
88
+ self.assertEqual(result.returncode, 0, result.stderr)
89
+ self.assertIn("--candidates", result.stdout)
90
+
91
+ def test_generate_questions_script_path_supports_help(self):
92
+ result = subprocess.run(
93
+ [sys.executable, str(ROOT / "scripts" / "generate_questions.py"), "--help"],
94
+ cwd=ROOT,
95
+ stdout=subprocess.PIPE,
96
+ stderr=subprocess.PIPE,
97
+ text=True,
98
+ check=False,
99
+ )
100
+
101
+ self.assertEqual(result.returncode, 0, result.stderr)
102
+ self.assertIn("--images-dir", result.stdout)
103
+
104
+ def test_environment_check_script_path_supports_help(self):
105
+ result = subprocess.run(
106
+ [sys.executable, str(ROOT / "scripts" / "check_environment.py"), "--help"],
107
+ cwd=ROOT,
108
+ stdout=subprocess.PIPE,
109
+ stderr=subprocess.PIPE,
110
+ text=True,
111
+ check=False,
112
+ )
113
+
114
+ self.assertEqual(result.returncode, 0, result.stderr)
115
+ self.assertIn("--allow-no-cuda", result.stdout)
116
+
117
+ def test_release_test_runner_is_present(self):
118
+ self.assertTrue((ROOT / "scripts" / "run_tests.py").is_file())
119
+
120
+ def test_offline_control_path_smoke(self):
121
+ result = subprocess.run(
122
+ [sys.executable, str(ROOT / "scripts" / "smoke_control_path.py")],
123
+ cwd=ROOT,
124
+ stdout=subprocess.PIPE,
125
+ stderr=subprocess.PIPE,
126
+ text=True,
127
+ check=False,
128
+ )
129
+
130
+ self.assertEqual(result.returncode, 0, result.stderr)
131
+ self.assertIn('"status": "ok"', result.stdout)
132
+
133
+ def test_ape_build_script_has_valid_shell_syntax(self):
134
+ result = subprocess.run(
135
+ ["bash", "-n", str(ROOT / "scripts" / "build_ape_extension.sh")],
136
+ cwd=ROOT,
137
+ stdout=subprocess.PIPE,
138
+ stderr=subprocess.PIPE,
139
+ text=True,
140
+ check=False,
141
+ )
142
+
143
+ self.assertEqual(result.returncode, 0, result.stderr)
144
+
145
+
146
+ if __name__ == "__main__":
147
+ unittest.main()
tests/test_config_providers.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import tempfile
4
+ import unittest
5
+ from pathlib import Path
6
+ from unittest import mock
7
+
8
+ from approach.config import (
9
+ OPENROUTER_BASE_URL,
10
+ get_model_profile,
11
+ sanitize_manifest,
12
+ validate_model_available,
13
+ )
14
+ from approach.providers import EncodedImage, OpenAICompatibleChatClient, make_multimodal_content
15
+
16
+
17
+ class ConfigProviderTests(unittest.TestCase):
18
+ def test_default_profile_uses_openrouter(self):
19
+ profile = get_model_profile("default")
20
+ self.assertEqual(profile.provider, "openrouter")
21
+ self.assertEqual(profile.model, "openai/gpt-5.6-sol")
22
+ self.assertEqual(profile.base_url, OPENROUTER_BASE_URL)
23
+ self.assertEqual(profile.api_key_env, "OPENROUTER_API_KEY")
24
+
25
+ def test_aliases_keep_paper_profiles_available(self):
26
+ self.assertEqual(get_model_profile("gpt4v").model, "openai/gpt-4o-2024-08-06")
27
+ self.assertEqual(get_model_profile("claude35sonnet").model, "anthropic/claude-3.5-sonnet")
28
+ self.assertEqual(get_model_profile("gemini15pro").model, "google/gemini-pro-1.5")
29
+
30
+ def test_json_config_override_without_yaml_dependency(self):
31
+ with tempfile.TemporaryDirectory() as tmpdir:
32
+ path = Path(tmpdir) / "profiles.json"
33
+ path.write_text(
34
+ json.dumps(
35
+ {
36
+ "profiles": {
37
+ "default": {
38
+ "provider": "openrouter",
39
+ "model": "custom/model",
40
+ "base_url": OPENROUTER_BASE_URL,
41
+ "api_key_env": "CUSTOM_KEY",
42
+ }
43
+ }
44
+ }
45
+ )
46
+ )
47
+ profile = get_model_profile("default", str(path))
48
+ self.assertEqual(profile.model, "custom/model")
49
+ self.assertEqual(profile.api_key_env, "CUSTOM_KEY")
50
+
51
+ def test_model_catalog_validation_is_dependency_injected(self):
52
+ profile = get_model_profile("default")
53
+ self.assertTrue(validate_model_available(profile, [{"id": "openai/gpt-5.6-sol"}]))
54
+ with self.assertRaises(ValueError):
55
+ validate_model_available(profile, [{"id": "other/model"}])
56
+
57
+ def test_multimodal_content_supports_multiple_images(self):
58
+ content = make_multimodal_content(
59
+ "check",
60
+ ["aaa", EncodedImage(data="bbb", media_type="image/png")],
61
+ )
62
+ self.assertEqual(content[0], {"type": "text", "text": "check"})
63
+ self.assertEqual(len(content), 3)
64
+ self.assertEqual(content[1]["image_url"]["url"], "data:image/jpeg;base64,aaa")
65
+ self.assertEqual(content[2]["image_url"]["url"], "data:image/png;base64,bbb")
66
+
67
+ def test_client_posts_openai_compatible_payload(self):
68
+ profile = get_model_profile("default")
69
+ response = mock.Mock()
70
+ response.json.return_value = {
71
+ "model": profile.model,
72
+ "choices": [{"message": {"content": "{\"ok\": true}"}}],
73
+ }
74
+ post = mock.Mock(return_value=response)
75
+
76
+ with mock.patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-token"}):
77
+ client = OpenAICompatibleChatClient(profile, post=post)
78
+ payload = client.build_payload(
79
+ "verify",
80
+ ["image-a", "image-b"],
81
+ response_format={"type": "json_schema", "json_schema": {"name": "x"}},
82
+ )
83
+ result = client.complete_json(
84
+ "verify",
85
+ ["image-a", "image-b"],
86
+ response_format={"type": "json_schema", "json_schema": {"name": "x"}},
87
+ )
88
+
89
+ self.assertEqual(payload["model"], "openai/gpt-5.6-sol")
90
+ self.assertEqual(result, {"ok": True})
91
+ self.assertEqual(
92
+ post.call_args.args[0],
93
+ "https://openrouter.ai/api/v1/chat/completions",
94
+ )
95
+ self.assertEqual(post.call_args.kwargs["headers"]["Authorization"], "Bearer test-token")
96
+ self.assertIn("response_format", post.call_args.kwargs["json"])
97
+
98
+ def test_client_supports_multi_turn_json_requests(self):
99
+ profile = get_model_profile("paper_claude")
100
+ response = mock.Mock()
101
+ response.json.return_value = {
102
+ "choices": [{"message": {"content": '{"objects": ["button"]}'}}]
103
+ }
104
+ post = mock.Mock(return_value=response)
105
+ messages = [
106
+ {"role": "user", "content": "list objects"},
107
+ {"role": "assistant", "content": '{"objects": ["button"]}'},
108
+ {"role": "user", "content": "filter interactable objects"},
109
+ ]
110
+
111
+ with mock.patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-token"}):
112
+ result = OpenAICompatibleChatClient(profile, post=post).complete_json_messages(
113
+ messages
114
+ )
115
+
116
+ self.assertEqual(result, {"objects": ["button"]})
117
+ self.assertEqual(post.call_args.kwargs["json"]["messages"], messages)
118
+
119
+ def test_manifest_never_records_api_key_value(self):
120
+ profile = get_model_profile("default")
121
+ manifest = sanitize_manifest(
122
+ profile,
123
+ {"model": "openai/gpt-5.6-sol"},
124
+ {"model": "openai/gpt-5.6-sol", "usage": {"total_tokens": 12}},
125
+ )
126
+ self.assertEqual(manifest["api_key_env"], "OPENROUTER_API_KEY")
127
+ self.assertNotIn("sk-", json.dumps(manifest))
128
+
129
+
130
+ if __name__ == "__main__":
131
+ unittest.main()
tests/test_evaluate_temp_paths.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ import tempfile
6
+ import unittest
7
+ from pathlib import Path
8
+
9
+
10
+ REPO_ROOT = Path(__file__).resolve().parents[1]
11
+ EVAL_DIR = REPO_ROOT / "evaluation"
12
+
13
+ PYCOCOTOOLS_STUB = """
14
+ import sys
15
+ import types
16
+ pycocotools = types.ModuleType("pycocotools")
17
+ mask = types.ModuleType("pycocotools._mask")
18
+ def _not_used(*args, **kwargs):
19
+ raise AssertionError("pycocotools._mask stub should not be exercised in this test")
20
+ for _name in ["iou", "merge", "frPyObjects", "encode", "decode", "area", "toBbox"]:
21
+ setattr(mask, _name, _not_used)
22
+ pycocotools._mask = mask
23
+ sys.modules["pycocotools"] = pycocotools
24
+ sys.modules["pycocotools._mask"] = mask
25
+ """
26
+
27
+
28
+ def run_eval_import(script: str, env_overrides: dict[str, str]) -> str:
29
+ env = os.environ.copy()
30
+ env.update(env_overrides)
31
+ env.pop("ORIENTER_TMP_ANN_PATH", None)
32
+ env.pop("ORIENTER_GT_CAT_MATCH_PATH", None)
33
+ pythonpath = [str(EVAL_DIR), str(REPO_ROOT)]
34
+ if env.get("PYTHONPATH"):
35
+ pythonpath.append(env["PYTHONPATH"])
36
+ env["PYTHONPATH"] = os.pathsep.join(pythonpath)
37
+ return subprocess.check_output(
38
+ [sys.executable, "-c", PYCOCOTOOLS_STUB + script],
39
+ cwd=EVAL_DIR,
40
+ env=env,
41
+ text=True,
42
+ ).strip()
43
+
44
+
45
+ class EvaluationTempPathTests(unittest.TestCase):
46
+ def test_default_temp_paths_are_process_isolated(self):
47
+ script = """
48
+ import json
49
+ import evaluate_coco
50
+ import pycocotools_ovod.semantic_matching as semantic_matching
51
+ print(json.dumps({
52
+ "tmp_ann": evaluate_coco.tmp_ann_path,
53
+ "gt_match": semantic_matching.gt_cat_match_path,
54
+ }))
55
+ """
56
+ with tempfile.TemporaryDirectory() as tmpdir:
57
+ env = {"ORIENTER_EVALUATION_TMPDIR": tmpdir}
58
+ first = json.loads(run_eval_import(script, env))
59
+ second = json.loads(run_eval_import(script, env))
60
+
61
+ self.assertNotEqual(first["tmp_ann"], second["tmp_ann"])
62
+ self.assertNotEqual(first["gt_match"], second["gt_match"])
63
+ self.assertIn("tmp_ann.", Path(first["tmp_ann"]).name)
64
+ self.assertIn("tmp_gt_cat_match.", Path(first["gt_match"]).name)
65
+
66
+ def test_cleanup_removes_configured_temp_files_and_resets_match_cache(self):
67
+ script = """
68
+ import json
69
+ from pathlib import Path
70
+ import evaluate_coco
71
+ import pycocotools_ovod.semantic_matching as semantic_matching
72
+
73
+ semantic_matching.gt_cat_match_dict = {"Button": ["Button"]}
74
+ for path in [evaluate_coco.tmp_ann_path, semantic_matching.gt_cat_match_path]:
75
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
76
+ Path(path).write_text("{}")
77
+
78
+ evaluate_coco.cleanup_temp_outputs()
79
+ print(json.dumps({
80
+ "tmp_ann_exists": Path(evaluate_coco.tmp_ann_path).exists(),
81
+ "gt_match_exists": Path(semantic_matching.gt_cat_match_path).exists(),
82
+ "match_cache": semantic_matching.gt_cat_match_dict,
83
+ }))
84
+ """
85
+ with tempfile.TemporaryDirectory() as tmpdir:
86
+ tmp = Path(tmpdir)
87
+ env = os.environ.copy()
88
+ env.update(
89
+ {
90
+ "ORIENTER_TMP_ANN_PATH": str(tmp / "custom_tmp_ann.json"),
91
+ "ORIENTER_GT_CAT_MATCH_PATH": str(tmp / "custom_gt_match.json"),
92
+ "PYTHONPATH": os.pathsep.join([str(EVAL_DIR), str(REPO_ROOT), env.get("PYTHONPATH", "")]),
93
+ }
94
+ )
95
+ result = subprocess.check_output(
96
+ [sys.executable, "-c", PYCOCOTOOLS_STUB + script],
97
+ cwd=EVAL_DIR,
98
+ env=env,
99
+ text=True,
100
+ ).strip()
101
+
102
+ state = json.loads(result)
103
+ self.assertFalse(state["tmp_ann_exists"])
104
+ self.assertFalse(state["gt_match_exists"])
105
+ self.assertIsNone(state["match_cache"])
106
+
107
+
108
+ if __name__ == "__main__":
109
+ unittest.main()
tests/test_evaluation_config.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib.util
2
+ import unittest
3
+ from pathlib import Path
4
+
5
+
6
+ def load_module(path):
7
+ spec = importlib.util.spec_from_file_location(path.stem, path)
8
+ module = importlib.util.module_from_spec(spec)
9
+ spec.loader.exec_module(module)
10
+ return module
11
+
12
+
13
+ class EvaluationConfigTest(unittest.TestCase):
14
+ def test_context_baseline_lists_are_deduplicated(self):
15
+ module = load_module(Path("evaluation/context_eval_baselines.py"))
16
+ self.assertEqual(len(module.METHODS), len(set(module.METHODS)))
17
+ self.assertEqual(len(module.LLM_METHODS), len(set(module.LLM_METHODS)))
18
+
19
+ def test_to_pred_requires_explicit_task(self):
20
+ module = load_module(Path("evaluation/tools/to_pred.py"))
21
+ self.assertIn("interactable", module.TASKS)
22
+ self.assertIn("all", module.build_parser()._option_string_actions["--task"].choices)
23
+
24
+
25
+ if __name__ == "__main__":
26
+ unittest.main()
tests/test_evaluation_tools.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import sys
3
+ import tempfile
4
+ import unittest
5
+ from itertools import chain, repeat
6
+ from pathlib import Path
7
+ from unittest import mock
8
+
9
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
10
+
11
+ from evaluation import context_eval_baselines
12
+ from evaluation.tools import to_pred
13
+
14
+
15
+ class ToPredTests(unittest.TestCase):
16
+ def test_task_is_required(self):
17
+ parser = to_pred.build_parser()
18
+
19
+ with self.assertRaises(SystemExit):
20
+ parser.parse_args(["--input", "pred.json", "--output", "out.json"])
21
+
22
+ def test_convert_old_predictions_to_semantics_fields(self):
23
+ with tempfile.TemporaryDirectory() as tmpdir:
24
+ tmp = Path(tmpdir)
25
+ pred_path = tmp / "pred.json"
26
+ question_path = tmp / "questions.jsonl"
27
+ output_path = tmp / "out.json"
28
+
29
+ pred_path.write_text(
30
+ json.dumps(
31
+ {
32
+ "q1": {
33
+ "oovd_result": {
34
+ "button": [
35
+ {
36
+ "bbox_pixels": [1, 2, 3, 4],
37
+ "probability": 0.7,
38
+ }
39
+ ]
40
+ }
41
+ }
42
+ }
43
+ )
44
+ )
45
+ question_path.write_text(
46
+ json.dumps({"question_id": "q1", "image": "1026760_11.jpg"}) + "\n"
47
+ )
48
+
49
+ to_pred.main(
50
+ [
51
+ "--task",
52
+ "semantics",
53
+ "--input",
54
+ str(pred_path),
55
+ "--questions",
56
+ str(question_path),
57
+ "--output",
58
+ str(output_path),
59
+ ]
60
+ )
61
+
62
+ converted = json.loads(output_path.read_text())
63
+ self.assertEqual(
64
+ converted,
65
+ [
66
+ {
67
+ "image_id": 1026760011,
68
+ "category_id": "button",
69
+ "bbox": [1, 2, 3, 4],
70
+ "score": 0.7,
71
+ }
72
+ ],
73
+ )
74
+
75
+ def test_all_outputs_do_not_overwrite(self):
76
+ with tempfile.TemporaryDirectory() as tmpdir:
77
+ tmp = Path(tmpdir)
78
+ pred_path = tmp / "pred.json"
79
+ output_path = tmp / "converted.json"
80
+
81
+ pred_path.write_text(
82
+ json.dumps(
83
+ [
84
+ {
85
+ "image_id": 1,
86
+ "category_id": "trigger",
87
+ "bbox": [1, 2, 3, 4],
88
+ "score": 0.5,
89
+ }
90
+ ]
91
+ )
92
+ )
93
+
94
+ to_pred.main(
95
+ [
96
+ "--task",
97
+ "all",
98
+ "--input",
99
+ str(pred_path),
100
+ "--output",
101
+ str(output_path),
102
+ ]
103
+ )
104
+
105
+ self.assertFalse(output_path.exists())
106
+ for task in to_pred.TASKS:
107
+ self.assertTrue((tmp / f"converted_{task}.json").exists())
108
+
109
+ interactable = json.loads((tmp / "converted_interactable.json").read_text())
110
+ interaction = json.loads((tmp / "converted_interaction.json").read_text())
111
+ semantics = json.loads((tmp / "converted_semantics.json").read_text())
112
+ self.assertEqual(interactable[0]["category_id"], 1)
113
+ self.assertEqual(interaction[0]["category_id"], "trigger")
114
+ self.assertEqual(semantics[0]["category_id"], "trigger")
115
+
116
+
117
+ class ContextEvalBaselineTests(unittest.TestCase):
118
+ def test_validate_unique_methods_rejects_duplicates(self):
119
+ with self.assertRaises(ValueError):
120
+ context_eval_baselines.validate_unique_methods(["Seed-E2E", "Seed-E2E"])
121
+
122
+ def test_main_uses_subprocess_check_true(self):
123
+ args = context_eval_baselines.build_parser().parse_args([])
124
+ methods = ["CenterNet2"]
125
+
126
+ with mock.patch.object(
127
+ context_eval_baselines, "METHODS", methods
128
+ ), mock.patch.object(
129
+ context_eval_baselines, "LLM_METHODS", []
130
+ ), mock.patch.object(
131
+ context_eval_baselines.os.path,
132
+ "exists",
133
+ side_effect=chain([True, False], repeat(False)),
134
+ ), mock.patch.object(
135
+ context_eval_baselines.subprocess, "run"
136
+ ) as run:
137
+ context_eval_baselines.main(args)
138
+
139
+ self.assertTrue(run.called)
140
+ self.assertTrue(run.call_args.kwargs["check"])
141
+ self.assertIsInstance(run.call_args.args[0], list)
142
+
143
+
144
+ if __name__ == "__main__":
145
+ unittest.main()
tests/test_merge_predictions.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import tempfile
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ from evaluation.tools.merge_predictions import merge_prediction_shards
7
+
8
+
9
+ class MergePredictionTests(unittest.TestCase):
10
+ def test_merges_disjoint_shards_and_validates_question_coverage(self):
11
+ with tempfile.TemporaryDirectory() as tmpdir:
12
+ root = Path(tmpdir)
13
+ first = root / "pred.shard00-of-02.json"
14
+ second = root / "pred.shard01-of-02.json"
15
+ output = root / "merged.json"
16
+ first.write_text(json.dumps([{"image_id": 1001, "category_id": "button", "bbox": [1, 2, 3, 4], "score": 0.9}]))
17
+ second.write_text(json.dumps([{"image_id": 1002, "category_id": "lever", "bbox": [4, 3, 2, 1], "score": 0.8}]))
18
+ first.with_suffix(".progress.json").write_text(json.dumps({"completed_image_ids": [1001]}))
19
+ second.with_suffix(".progress.json").write_text(json.dumps({"completed_image_ids": [1002]}))
20
+
21
+ report = merge_prediction_shards(
22
+ [first, second],
23
+ output,
24
+ expected_image_ids={1001, 1002},
25
+ )
26
+
27
+ self.assertEqual(len(json.loads(output.read_text())), 2)
28
+ self.assertEqual(report["completed_image_ids"], 2)
29
+ self.assertEqual(report["missing_image_ids"], [])
30
+
31
+ def test_rejects_overlapping_shard_progress(self):
32
+ with tempfile.TemporaryDirectory() as tmpdir:
33
+ root = Path(tmpdir)
34
+ paths = [root / "a.json", root / "b.json"]
35
+ for path in paths:
36
+ path.write_text("[]")
37
+ path.with_suffix(".progress.json").write_text(
38
+ json.dumps({"completed_image_ids": [1001]})
39
+ )
40
+
41
+ with self.assertRaisesRegex(ValueError, "overlap"):
42
+ merge_prediction_shards(paths, root / "merged.json")
43
+
44
+ def test_rejects_missing_expected_images(self):
45
+ with tempfile.TemporaryDirectory() as tmpdir:
46
+ root = Path(tmpdir)
47
+ shard = root / "a.json"
48
+ shard.write_text("[]")
49
+ shard.with_suffix(".progress.json").write_text(
50
+ json.dumps({"completed_image_ids": [1001]})
51
+ )
52
+
53
+ with self.assertRaisesRegex(ValueError, "missing"):
54
+ merge_prediction_shards(
55
+ [shard],
56
+ root / "merged.json",
57
+ expected_image_ids={1001, 1002},
58
+ )
59
+
60
+
61
+ if __name__ == "__main__":
62
+ unittest.main()
tests/test_paired_bootstrap.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import sys
3
+ import tempfile
4
+ import unittest
5
+ from pathlib import Path
6
+
7
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
8
+
9
+ from evaluation.tools import paired_bootstrap
10
+
11
+
12
+ def _write(path, payload):
13
+ path.write_text(json.dumps(payload), encoding="utf-8")
14
+
15
+
16
+ class PairedBootstrapTests(unittest.TestCase):
17
+ def _toy_gt(self):
18
+ return {
19
+ "images": [{"id": 1}, {"id": 2}, {"id": 3}],
20
+ "categories": [
21
+ {"id": 1, "name": "Button"},
22
+ {"id": 2, "name": "Sphere"},
23
+ ],
24
+ "annotations": [
25
+ {"id": 1, "image_id": 1, "category_id": 1, "bbox": [0, 0, 10, 10]},
26
+ {"id": 2, "image_id": 2, "category_id": 2, "bbox": [0, 0, 10, 10]},
27
+ ],
28
+ }
29
+
30
+ def test_greedy_matching_uses_score_order_and_complete_images(self):
31
+ gt = self._toy_gt()
32
+ preds = [
33
+ {"image_id": 1, "category_id": 1, "bbox": [0, 0, 10, 10], "score": 0.9},
34
+ {"image_id": 1, "category_id": 1, "bbox": [0, 0, 10, 10], "score": 0.2},
35
+ {"image_id": 2, "category_id": 2, "bbox": [0, 0, 10, 10], "score": 0.8},
36
+ ]
37
+
38
+ prepared_gt = paired_bootstrap.prepare_ground_truth(gt)
39
+ evaluated = paired_bootstrap.evaluate_predictions(
40
+ prepared_gt,
41
+ preds,
42
+ dimension="interactable",
43
+ iou_threshold=0.75,
44
+ score_threshold=0.0,
45
+ )
46
+
47
+ self.assertEqual(evaluated.counts["tp"], 2)
48
+ self.assertEqual(evaluated.counts["fp"], 1)
49
+ self.assertEqual(evaluated.counts["fn"], 0)
50
+ self.assertEqual(len(evaluated.per_image), 3)
51
+ self.assertEqual(evaluated.per_image[3].support, 0)
52
+
53
+ def test_auto_threshold_maximizes_micro_f1(self):
54
+ gt = self._toy_gt()
55
+ preds = [
56
+ {"image_id": 1, "category_id": 1, "bbox": [0, 0, 10, 10], "score": 0.4},
57
+ {"image_id": 2, "category_id": 1, "bbox": [0, 0, 10, 10], "score": 0.9},
58
+ {"image_id": 2, "category_id": 2, "bbox": [0, 0, 10, 10], "score": 0.8},
59
+ {"image_id": 3, "category_id": 1, "bbox": [0, 0, 10, 10], "score": 0.4},
60
+ {"image_id": 3, "category_id": 1, "bbox": [1, 1, 10, 10], "score": 0.4},
61
+ {"image_id": 3, "category_id": 2, "bbox": [2, 2, 10, 10], "score": 0.4},
62
+ {"image_id": 3, "category_id": 2, "bbox": [3, 3, 10, 10], "score": 0.4},
63
+ ]
64
+
65
+ prepared_gt = paired_bootstrap.prepare_ground_truth(gt)
66
+ threshold, evaluated = paired_bootstrap.select_best_threshold(
67
+ prepared_gt,
68
+ preds,
69
+ dimension="interactable",
70
+ iou_threshold=0.75,
71
+ )
72
+
73
+ self.assertEqual(threshold, 0.8)
74
+ self.assertAlmostEqual(evaluated.metrics["f1"], 0.5)
75
+
76
+ def test_fast_auto_threshold_matches_bruteforce_selection(self):
77
+ gt = self._toy_gt()
78
+ preds = [
79
+ {"image_id": 1, "category_id": 1, "bbox": [0, 0, 10, 10], "score": 0.91},
80
+ {"image_id": 1, "category_id": 1, "bbox": [1, 1, 10, 10], "score": 0.42},
81
+ {"image_id": 2, "category_id": 1, "bbox": [0, 0, 10, 10], "score": 0.88},
82
+ {"image_id": 2, "category_id": 2, "bbox": [0, 0, 10, 10], "score": 0.73},
83
+ {"image_id": 3, "category_id": 2, "bbox": [0, 0, 10, 10], "score": 0.11},
84
+ {"image_id": 99, "category_id": 2, "bbox": [0, 0, 10, 10], "score": 0.72},
85
+ ]
86
+ prepared_gt = paired_bootstrap.prepare_ground_truth(gt)
87
+
88
+ fast_threshold = paired_bootstrap.select_best_threshold_fast(
89
+ prepared_gt,
90
+ preds,
91
+ dimension="interactable",
92
+ iou_threshold=0.75,
93
+ )
94
+ brute_threshold, brute_result = self._select_best_threshold_bruteforce(prepared_gt, preds)
95
+
96
+ self.assertEqual(fast_threshold, brute_threshold)
97
+ self.assertEqual(fast_threshold, 0.72)
98
+ fast_result = paired_bootstrap.evaluate_predictions(
99
+ prepared_gt,
100
+ preds,
101
+ dimension="interactable",
102
+ iou_threshold=0.75,
103
+ score_threshold=fast_threshold,
104
+ )
105
+ self.assertEqual(fast_result.metrics, brute_result.metrics)
106
+
107
+ def _select_best_threshold_bruteforce(self, prepared_gt, preds):
108
+ best_threshold = None
109
+ best_result = None
110
+ for threshold in sorted({float(pred["score"]) for pred in preds}, reverse=True):
111
+ result = paired_bootstrap.evaluate_predictions(
112
+ prepared_gt,
113
+ preds,
114
+ dimension="interactable",
115
+ iou_threshold=0.75,
116
+ score_threshold=threshold,
117
+ )
118
+ if best_result is None or (
119
+ result.metrics["f1"],
120
+ result.metrics["precision"],
121
+ result.metrics["recall"],
122
+ -threshold,
123
+ ) > (
124
+ best_result.metrics["f1"],
125
+ best_result.metrics["precision"],
126
+ best_result.metrics["recall"],
127
+ -best_threshold,
128
+ ):
129
+ best_threshold = threshold
130
+ best_result = result
131
+ return best_threshold, best_result
132
+
133
+ def test_semantic_cache_match_maps_numeric_categories(self):
134
+ with tempfile.TemporaryDirectory() as tmpdir:
135
+ tmp = Path(tmpdir)
136
+ cache = tmp / "embedding.json"
137
+ _write(cache, {"Button": [1.0, 0.0], "Control": [0.9, 0.1]})
138
+ matcher = paired_bootstrap.SemanticMatcher(cache)
139
+
140
+ gt = self._toy_gt()
141
+ preds = [
142
+ {"image_id": 1, "category_id": "control", "bbox": [0, 0, 10, 10], "score": 1.0}
143
+ ]
144
+ prepared_gt = paired_bootstrap.prepare_ground_truth(gt)
145
+ evaluated = paired_bootstrap.evaluate_predictions(
146
+ prepared_gt,
147
+ preds,
148
+ dimension="semantics",
149
+ iou_threshold=0.75,
150
+ score_threshold=0.0,
151
+ semantic_matcher=matcher,
152
+ )
153
+
154
+ self.assertEqual(evaluated.counts["tp"], 1)
155
+
156
+ def test_semantic_cache_preserves_historical_camelcase_keys(self):
157
+ with tempfile.TemporaryDirectory() as tmpdir:
158
+ cache = Path(tmpdir) / "embedding.json"
159
+ _write(cache, {"QuitButton": [1.0, 0.0], "ConfirmButton": [0.9, 0.1]})
160
+ matcher = paired_bootstrap.SemanticMatcher(cache)
161
+
162
+ self.assertTrue(matcher.matches("quit_button", "confirm_button"))
163
+
164
+ def test_semantic_match_defaults_to_historical_raw_dot_product(self):
165
+ with tempfile.TemporaryDirectory() as tmpdir:
166
+ cache = Path(tmpdir) / "embedding.json"
167
+ _write(cache, {"A": [1.0, 0.0], "B": [0.84, 0.20]})
168
+
169
+ historical = paired_bootstrap.SemanticMatcher(cache)
170
+ cosine = paired_bootstrap.SemanticMatcher(cache, similarity_mode="cosine")
171
+
172
+ self.assertFalse(historical.matches("a", "b"))
173
+ self.assertTrue(cosine.matches("a", "b"))
174
+
175
+ def test_semantic_cache_miss_is_hard_error(self):
176
+ with tempfile.TemporaryDirectory() as tmpdir:
177
+ cache = Path(tmpdir) / "embedding.json"
178
+ _write(cache, {"Button": [1.0, 0.0]})
179
+ matcher = paired_bootstrap.SemanticMatcher(cache)
180
+
181
+ with self.assertRaisesRegex(KeyError, "Missing frozen embedding cache entries"):
182
+ matcher.matches("button", "unknown")
183
+
184
+ def test_cli_writes_json_report(self):
185
+ with tempfile.TemporaryDirectory() as tmpdir:
186
+ tmp = Path(tmpdir)
187
+ gt_path = tmp / "gt.json"
188
+ a_path = tmp / "a.json"
189
+ b_path = tmp / "b.json"
190
+ out_path = tmp / "report.json"
191
+ _write(gt_path, self._toy_gt())
192
+ _write(a_path, [{"image_id": 1, "category_id": 1, "bbox": [0, 0, 10, 10], "score": 1.0}])
193
+ _write(
194
+ b_path,
195
+ [
196
+ {"image_id": 1, "category_id": 1, "bbox": [0, 0, 10, 10], "score": 1.0},
197
+ {"image_id": 2, "category_id": 2, "bbox": [0, 0, 10, 10], "score": 0.9},
198
+ ],
199
+ )
200
+
201
+ paired_bootstrap.main(
202
+ [
203
+ "--gt",
204
+ str(gt_path),
205
+ "--method-a",
206
+ str(a_path),
207
+ "--method-b",
208
+ str(b_path),
209
+ "--dimension",
210
+ "interactable",
211
+ "--auto-threshold",
212
+ "--replicates",
213
+ "100",
214
+ "--seed",
215
+ "7",
216
+ "--output",
217
+ str(out_path),
218
+ ]
219
+ )
220
+
221
+ report = json.loads(out_path.read_text(encoding="utf-8"))
222
+ self.assertEqual(report["protocol"]["dimension"], "interactable")
223
+ self.assertEqual(report["protocol"]["bootstrap_unit"], "image_id")
224
+ self.assertGreater(report["methods"]["method_b"]["point"]["micro"]["f1"], report["methods"]["method_a"]["point"]["micro"]["f1"])
225
+ self.assertIn("mean_per_all_images", report["methods"]["method_a"]["point"])
226
+ self.assertIn("mean_per_positive_support_images", report["methods"]["method_a"]["point"])
227
+ self.assertIn("mean_per_all_images_delta", report["bootstrap"])
228
+ self.assertIn("mean_per_positive_support_images_delta", report["bootstrap"])
229
+ self.assertEqual(report["bootstrap"]["delta_direction"], "method_b_minus_method_a")
230
+
231
+
232
+ if __name__ == "__main__":
233
+ unittest.main()
tests/test_pipeline_utils.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import tempfile
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ from PIL import Image
7
+
8
+ from approach.pipeline_utils import (
9
+ enrich_ape_results,
10
+ output_path_for_selection,
11
+ parse_orienter_image_name,
12
+ resolve_image_path,
13
+ run_optional_reflection,
14
+ select_jsonl_lines,
15
+ write_json_atomic,
16
+ )
17
+
18
+
19
+ class PipelineUtilsTest(unittest.TestCase):
20
+ def test_parse_orienter_image_name_is_strict(self):
21
+ self.assertEqual(parse_orienter_image_name("123_4.jpg"), ("123", "4", 123004))
22
+ with self.assertRaises(ValueError):
23
+ parse_orienter_image_name("123_4_extra.jpg")
24
+ with self.assertRaises(ValueError):
25
+ parse_orienter_image_name("nested/123_4.jpg")
26
+ with self.assertRaises(ValueError):
27
+ parse_orienter_image_name("123_1000.jpg")
28
+
29
+ def test_resolve_image_path_rejects_escape_from_images_directory(self):
30
+ with tempfile.TemporaryDirectory() as tmp_dir:
31
+ images_dir = Path(tmp_dir) / "images"
32
+ images_dir.mkdir()
33
+ self.assertEqual(
34
+ resolve_image_path(images_dir, "nested/123_4.jpg"),
35
+ (images_dir / "nested" / "123_4.jpg").resolve(),
36
+ )
37
+ with self.assertRaises(ValueError):
38
+ resolve_image_path(images_dir, "../123_4.jpg")
39
+ with self.assertRaises(ValueError):
40
+ resolve_image_path(images_dir, str(Path(tmp_dir) / "123_4.jpg"))
41
+
42
+ def test_select_jsonl_lines_supports_ranges_or_shards(self):
43
+ lines = [f"{idx}\n" for idx in range(10)]
44
+ self.assertEqual(select_jsonl_lines(lines, start_index=2, end_index=8), lines[2:8])
45
+ self.assertEqual(
46
+ select_jsonl_lines(lines, shard_index=1, num_shards=2),
47
+ ["1\n", "3\n", "5\n", "7\n", "9\n"],
48
+ )
49
+ with self.assertRaises(ValueError):
50
+ select_jsonl_lines(
51
+ lines,
52
+ start_index=2,
53
+ end_index=8,
54
+ shard_index=1,
55
+ num_shards=2,
56
+ )
57
+
58
+ def test_output_path_for_selection_prevents_shard_overwrite(self):
59
+ self.assertEqual(
60
+ output_path_for_selection("predictions.json", shard_index=1, num_shards=4),
61
+ "predictions.shard01-of-04.json",
62
+ )
63
+ self.assertEqual(
64
+ output_path_for_selection("predictions.json", start_index=100, end_index=200),
65
+ "predictions.rows100-200.json",
66
+ )
67
+ self.assertEqual(output_path_for_selection("predictions.json"), "predictions.json")
68
+
69
+ def test_enrich_ape_results_keeps_added_metadata(self):
70
+ enriched = enrich_ape_results(
71
+ [{"bbox": [1, 2, 3, 4], "category_name": "button"}],
72
+ "123_4.jpg",
73
+ lambda image_name: 123004,
74
+ )
75
+ self.assertEqual(enriched[0]["image_id"], 123004)
76
+ self.assertEqual(enriched[0]["category_id"], "button")
77
+ self.assertEqual(enriched[0]["bbox"], [1, 2, 3, 4])
78
+
79
+ def test_write_json_atomic_writes_one_valid_json_document(self):
80
+ with tempfile.TemporaryDirectory() as tmp_dir:
81
+ out_path = Path(tmp_dir) / "ape.json"
82
+ write_json_atomic(str(out_path), [{"image_id": 1}, {"image_id": 2}])
83
+ self.assertEqual(json.loads(out_path.read_text()), [{"image_id": 1}, {"image_id": 2}])
84
+
85
+ def test_optional_reflection_is_disabled_by_default(self):
86
+ self.assertIsNone(
87
+ run_optional_reflection(
88
+ "unused.png",
89
+ [],
90
+ detector=lambda candidates, previous: previous,
91
+ enabled=False,
92
+ )
93
+ )
94
+
95
+ def test_optional_reflection_feeds_back_to_detector(self):
96
+ with tempfile.TemporaryDirectory() as tmp_dir:
97
+ image_path = Path(tmp_dir) / "image.png"
98
+ Image.new("RGB", (10, 10), "white").save(image_path)
99
+ detector_calls = []
100
+
101
+ def advisor(**kwargs):
102
+ if detector_calls:
103
+ return {"verified": [0], "needs_refinement": [], "feedback": []}
104
+ return {"verified": [], "needs_refinement": [0], "feedback": ["button label"]}
105
+
106
+ def detector(candidates, previous):
107
+ detector_calls.append(candidates)
108
+ return [{"bbox": [2, 2, 3, 3], "category_name": "button"}]
109
+
110
+ result = run_optional_reflection(
111
+ str(image_path),
112
+ [{"bbox": [1, 1, 3, 3], "category_name": "button"}],
113
+ detector=detector,
114
+ enabled=True,
115
+ advisor=advisor,
116
+ )
117
+
118
+ self.assertEqual(detector_calls, [["button label"]])
119
+ self.assertFalse(result["max_iterations_reached"])
120
+
121
+
122
+ if __name__ == "__main__":
123
+ unittest.main()
tests/test_reflection.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+
3
+ from PIL import Image
4
+
5
+ from unittest import mock
6
+
7
+ from approach.reflection import (
8
+ build_reflection_prompt,
9
+ make_successful_detection_request,
10
+ openai_compatible_advisor,
11
+ run_reflection_loop,
12
+ )
13
+
14
+
15
+ class ReflectionTest(unittest.TestCase):
16
+ def test_pii5_crop_uses_bbox_without_resize_or_flip(self):
17
+ image = Image.new("RGB", (12, 8), "black")
18
+ for x in range(2, 7):
19
+ for y in range(1, 5):
20
+ image.putpixel((x, y), (x, y, 100))
21
+
22
+ request = make_successful_detection_request(
23
+ image, {"bbox": [2, 1, 5, 4], "category_name": "button"}
24
+ )
25
+
26
+ self.assertEqual(request.crop_image.size, (5, 4))
27
+ self.assertEqual(request.crop_image.getpixel((0, 0)), image.getpixel((2, 1)))
28
+ self.assertEqual(request.crop_image.getpixel((4, 0)), image.getpixel((6, 1)))
29
+ self.assertIs(request.original_image, image)
30
+
31
+ def test_fractional_bbox_crop_contains_the_full_detected_region(self):
32
+ request = make_successful_detection_request(
33
+ Image.new("RGB", (10, 10), "white"),
34
+ {"bbox": [1.2, 2.2, 2.2, 1.2], "category_name": "button"},
35
+ )
36
+
37
+ self.assertEqual(request.crop_image.size, (3, 2))
38
+
39
+ def test_reflection_loop_stops_when_advisor_has_no_concerns(self):
40
+ calls = []
41
+
42
+ def advisor(**kwargs):
43
+ calls.append(kwargs)
44
+ return {"verified": [0], "needs_refinement": [], "feedback": []}
45
+
46
+ result = run_reflection_loop(
47
+ Image.new("RGB", (10, 10), "white"),
48
+ [{"bbox": [1, 1, 3, 3], "category_name": "menu"}],
49
+ miner=lambda trace: ["menu"],
50
+ detector=lambda candidates, previous: previous,
51
+ advisor=advisor,
52
+ max_iterations=10,
53
+ )
54
+
55
+ self.assertEqual(len(calls), 1)
56
+ self.assertFalse(result["max_iterations_reached"])
57
+ self.assertEqual(result["trace"][0]["verified"], [0])
58
+ self.assertEqual(len(calls[0]["verification_requests"]), 1)
59
+ self.assertIn("PII.6", calls[0]["pii6_prompt"])
60
+
61
+ def test_reflection_loop_marks_max_iterations(self):
62
+ def advisor(**kwargs):
63
+ return {"verified": [], "needs_refinement": [0], "feedback": ["retry"]}
64
+
65
+ result = run_reflection_loop(
66
+ Image.new("RGB", (10, 10), "white"),
67
+ [{"bbox": [1, 1, 3, 3], "category_name": "menu"}],
68
+ miner=lambda trace: ["menu"],
69
+ detector=lambda candidates, previous: previous,
70
+ advisor=advisor,
71
+ max_iterations=3,
72
+ )
73
+
74
+ self.assertTrue(result["max_iterations_reached"])
75
+ self.assertEqual(len(result["trace"]), 3)
76
+
77
+ def test_reflection_removes_rejected_false_positive_before_redetection(self):
78
+ detector_calls = []
79
+
80
+ def detector(candidates, retained):
81
+ detector_calls.append((candidates, retained))
82
+ return retained
83
+
84
+ advice = [
85
+ {"verified": [1], "needs_refinement": [0], "feedback": ["missing lever"]},
86
+ {"verified": [0], "needs_refinement": [], "feedback": []},
87
+ ]
88
+ detections = [
89
+ {"bbox": [1, 1, 2, 2], "category_name": "decoration"},
90
+ {"bbox": [5, 5, 2, 2], "category_name": "button"},
91
+ ]
92
+
93
+ result = run_reflection_loop(
94
+ Image.new("RGB", (10, 10), "white"),
95
+ detections,
96
+ miner=lambda trace: trace[-1]["feedback"],
97
+ detector=detector,
98
+ advisor=lambda **kwargs: advice.pop(0),
99
+ )
100
+
101
+ self.assertEqual(detector_calls[0][0], ["missing lever"])
102
+ self.assertEqual(detector_calls[0][1], [detections[1]])
103
+ self.assertEqual(result["detections"], [detections[1]])
104
+
105
+ def test_openai_compatible_advisor_sends_annotated_scene_original_and_crop(self):
106
+ fake_client = mock.Mock()
107
+ fake_client.complete_json.return_value = {
108
+ "verified": [0],
109
+ "needs_refinement": [],
110
+ "feedback": [],
111
+ }
112
+
113
+ with mock.patch("approach.reflection.OpenAICompatibleChatClient", return_value=fake_client):
114
+ advisor = openai_compatible_advisor("default")
115
+ request = make_successful_detection_request(
116
+ Image.new("RGB", (10, 10), "white"),
117
+ {"bbox": [1, 1, 3, 3], "category_name": "button"},
118
+ )
119
+ result = advisor(
120
+ verification_requests=[request],
121
+ unsuccessful_detection_image=Image.new("RGB", (10, 10), "white"),
122
+ pii5_prompt="PII.5",
123
+ pii6_prompt="PII.6",
124
+ pii7_prompt="PII.7",
125
+ )
126
+
127
+ self.assertEqual(result["verified"], [0])
128
+ call = fake_client.complete_json.call_args
129
+ self.assertIn("PII.5", call.args[0])
130
+ self.assertEqual(len(call.args[1]), 3)
131
+ self.assertTrue(all(image.media_type == "image/png" for image in call.args[1]))
132
+ self.assertIn("response_format", call.kwargs)
133
+
134
+ def test_reflection_prompt_includes_detection_metadata(self):
135
+ request = make_successful_detection_request(
136
+ Image.new("RGB", (10, 10), "white"),
137
+ {"bbox": [1, 2, 3, 4], "category_name": "slider", "score": 0.9},
138
+ )
139
+
140
+ prompt = build_reflection_prompt([request], "PII.5", "PII.6", "PII.7")
141
+
142
+ self.assertIn('"category_name": "slider"', prompt)
143
+ self.assertIn("PII.6", prompt)
144
+
145
+
146
+ if __name__ == "__main__":
147
+ unittest.main()
tests/test_release_hygiene.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import unittest
3
+ from pathlib import Path
4
+
5
+
6
+ ROOT = Path(__file__).resolve().parents[1]
7
+
8
+
9
+ class ReleaseHygieneTests(unittest.TestCase):
10
+ allowed_large_files = {
11
+ Path("approach/ovod/APE/ape_d_model_final.pth"),
12
+ }
13
+
14
+ def test_release_policies_exclude_generated_python_state(self):
15
+ gitignore = (ROOT / ".gitignore").read_text(encoding="utf-8")
16
+ release_excludes = (ROOT / "docs" / "RELEASE_EXCLUDES.txt").read_text(encoding="utf-8")
17
+
18
+ self.assertIn("__pycache__/", gitignore)
19
+ self.assertIn("*.py[cod]", gitignore)
20
+ self.assertIn(".pytest_cache/", gitignore)
21
+ self.assertIn("**/__pycache__/", release_excludes)
22
+ self.assertIn("**/*.pyc", release_excludes)
23
+ self.assertIn("**/.pytest_cache/", release_excludes)
24
+
25
+ def test_release_does_not_reference_private_relays_or_source_machines(self):
26
+ forbidden = (
27
+ "chat" + "anywhere",
28
+ "aigpt" + "x.top",
29
+ "/home/" + "sqli/",
30
+ "/research/d4/gds/" + "sqli21",
31
+ "10.249." + "190.53",
32
+ "/Users" + "/",
33
+ "/users/" + "prannay",
34
+ )
35
+ violations = []
36
+ for path in ROOT.rglob("*"):
37
+ if not path.is_file() or ".git" in path.parts:
38
+ continue
39
+ try:
40
+ text = path.read_text(encoding="utf-8")
41
+ except (UnicodeDecodeError, OSError):
42
+ continue
43
+ for needle in forbidden:
44
+ if needle in text:
45
+ violations.append(f"{path.relative_to(ROOT)}: {needle}")
46
+ self.assertEqual(violations, [])
47
+
48
+ def test_release_excludes_generated_results_and_local_git_state(self):
49
+ forbidden_parts = {
50
+ ".git",
51
+ "__pycache__",
52
+ ".pytest_cache",
53
+ "outputs",
54
+ "output",
55
+ "runs",
56
+ "wandb",
57
+ "results",
58
+ "eval_results",
59
+ "validation",
60
+ }
61
+ violations = []
62
+ for path in ROOT.rglob("*"):
63
+ rel = path.relative_to(ROOT)
64
+ if any(part in forbidden_parts for part in rel.parts):
65
+ violations.append(str(rel))
66
+ self.assertEqual(violations, [])
67
+
68
+ self.assertFalse(
69
+ (ROOT / "data").exists(),
70
+ "Top-level data/ must remain an external mount, not release content",
71
+ )
72
+
73
+ explicitly_forbidden = (
74
+ Path("docs/EVALUATION_VALIDATION.md"),
75
+ Path("evaluation/validation"),
76
+ Path("approach/ovod/APE/test_output.png"),
77
+ Path("baselines/claude-4.5-sonnet-e2e/claude-4.5-sonnet-tiny.json"),
78
+ )
79
+ self.assertEqual([str(path) for path in explicitly_forbidden if (ROOT / path).exists()], [])
80
+
81
+ forbidden_suffixes = (".log", ".pyc", ".pyo", ".nfs")
82
+ unexpected = []
83
+ for path in ROOT.rglob("*"):
84
+ if not path.is_file():
85
+ continue
86
+ if path.name.startswith(".nfs") or path.name.endswith(forbidden_suffixes):
87
+ unexpected.append(str(path.relative_to(ROOT)))
88
+ self.assertEqual(unexpected, [])
89
+
90
+ def test_release_has_no_credential_material(self):
91
+ forbidden_files = []
92
+ for path in ROOT.rglob("*"):
93
+ if not path.is_file():
94
+ continue
95
+ rel = path.relative_to(ROOT)
96
+ if path.name == ".env" or (
97
+ path.name.startswith(".env.") and path.name != ".env.example"
98
+ ):
99
+ forbidden_files.append(str(rel))
100
+ if path.suffix.lower() in {".pem", ".key"}:
101
+ forbidden_files.append(str(rel))
102
+ self.assertEqual(forbidden_files, [])
103
+
104
+ secret_patterns = (
105
+ re.compile(r"sk-or-v1-[A-Za-z0-9_-]{20,}"),
106
+ re.compile(r"olp_[A-Za-z0-9_-]{12,}"),
107
+ re.compile(r"AIza[0-9A-Za-z_-]{20,}"),
108
+ re.compile(r"gh[pousr]_[0-9A-Za-z]{20,}"),
109
+ re.compile(r"hf_[0-9A-Za-z]{20,}"),
110
+ re.compile(r"AKIA[0-9A-Z]{16}"),
111
+ re.compile(r"BEGIN (?:RSA |OPENSSH |EC )?PRIVATE KEY"),
112
+ )
113
+ violations = []
114
+ for path in ROOT.rglob("*"):
115
+ if not path.is_file() or path.suffix.lower() == ".pth":
116
+ continue
117
+ try:
118
+ text = path.read_text(encoding="utf-8")
119
+ except (UnicodeDecodeError, OSError):
120
+ continue
121
+ if any(pattern.search(text) for pattern in secret_patterns):
122
+ violations.append(str(path.relative_to(ROOT)))
123
+ self.assertEqual(violations, [])
124
+
125
+ def test_large_binary_files_are_limited_to_bundled_main_checkpoint(self):
126
+ threshold = 20 * 1024 * 1024
127
+ violations = []
128
+ for path in ROOT.rglob("*"):
129
+ if not path.is_file():
130
+ continue
131
+ rel = path.relative_to(ROOT)
132
+ if path.stat().st_size > threshold and rel not in self.allowed_large_files:
133
+ violations.append(str(rel))
134
+ self.assertEqual(violations, [])
135
+
136
+ def test_model_manifest_matches_bundled_checkpoint_policy(self):
137
+ manifest = (ROOT / "docs" / "MODEL_MANIFEST.md").read_text(encoding="utf-8")
138
+ checkpoint = ROOT / "approach" / "ovod" / "APE" / "ape_d_model_final.pth"
139
+ self.assertIn("3548f41a3238148180e08fd4b16c71f4abc3ac3caf9c8434444462d1bdb7f965", manifest)
140
+ self.assertTrue(checkpoint.is_file())
141
+ self.assertEqual(checkpoint.stat().st_size, 5_956_547_279)
142
+
143
+ attributes = (ROOT / ".gitattributes").read_text(encoding="utf-8")
144
+ self.assertIn(
145
+ "approach/ovod/APE/ape_d_model_final.pth filter=lfs diff=lfs merge=lfs -text",
146
+ attributes,
147
+ )
148
+
149
+
150
+ if __name__ == "__main__":
151
+ unittest.main()
tests/test_release_scripts.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import tempfile
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ from scripts.generate_questions import build_questions, write_metadata_template
7
+ from scripts.verify_assets import (
8
+ check_embedding_cache,
9
+ check_metadata_cache,
10
+ check_questions,
11
+ sha256_file,
12
+ )
13
+
14
+
15
+ class ReleaseScriptTests(unittest.TestCase):
16
+ def test_generate_questions_from_image_directory(self):
17
+ with tempfile.TemporaryDirectory() as tmpdir:
18
+ root = Path(tmpdir)
19
+ images = root / "images"
20
+ images.mkdir()
21
+ (images / "123_4.jpg").write_bytes(b"fake")
22
+ (images / "123_5.png").write_bytes(b"fake")
23
+ (images / "123_10.png").write_bytes(b"fake")
24
+ (images / "notes.txt").write_text("ignored", encoding="utf-8")
25
+
26
+ questions = build_questions(images, "Prompt")
27
+ metadata_template = root / "metadata.json"
28
+ write_metadata_template(metadata_template, questions)
29
+
30
+ metadata = json.loads(metadata_template.read_text(encoding="utf-8"))
31
+
32
+ self.assertEqual([record["image_id"] for record in questions], [123004, 123005, 123010])
33
+ self.assertEqual(questions[0]["text"], "Prompt")
34
+ self.assertEqual(sorted(metadata), ["123"])
35
+
36
+ def test_recursive_question_generation_uses_basename_for_app_id(self):
37
+ with tempfile.TemporaryDirectory() as tmpdir:
38
+ root = Path(tmpdir)
39
+ images = root / "images"
40
+ nested = images / "nested"
41
+ nested.mkdir(parents=True)
42
+ (nested / "123_4.jpg").write_bytes(b"fake")
43
+
44
+ questions = build_questions(images, "Prompt", recursive=True)
45
+ metadata_template = root / "metadata.json"
46
+ write_metadata_template(metadata_template, questions)
47
+ metadata = json.loads(metadata_template.read_text(encoding="utf-8"))
48
+
49
+ self.assertEqual(questions[0]["image"], "nested/123_4.jpg")
50
+ self.assertEqual(sorted(metadata), ["123"])
51
+
52
+ def test_verify_questions_and_metadata_cache(self):
53
+ with tempfile.TemporaryDirectory() as tmpdir:
54
+ root = Path(tmpdir)
55
+ images = root / "images"
56
+ images.mkdir()
57
+ (images / "123_4.jpg").write_bytes(b"fake")
58
+ questions = root / "questions.jsonl"
59
+ questions.write_text(
60
+ json.dumps(
61
+ {
62
+ "question_id": 0,
63
+ "image": "123_4.jpg",
64
+ "image_id": 123004,
65
+ "text": "Prompt",
66
+ }
67
+ )
68
+ + "\n",
69
+ encoding="utf-8",
70
+ )
71
+ metadata = root / "metadata.json"
72
+ metadata.write_text(
73
+ json.dumps(
74
+ {
75
+ "123": {
76
+ "app_name": "Test VR",
77
+ "app_description": "Description.",
78
+ }
79
+ }
80
+ ),
81
+ encoding="utf-8",
82
+ )
83
+
84
+ report = check_questions(questions, images)
85
+ metadata_report = check_metadata_cache(metadata, report["app_ids"])
86
+
87
+ self.assertEqual(report["questions"], 1)
88
+ self.assertEqual(metadata_report["covered_app_ids"], 1)
89
+
90
+ def test_verify_metadata_cache_rejects_duplicates_and_missing_fields(self):
91
+ with tempfile.TemporaryDirectory() as tmpdir:
92
+ root = Path(tmpdir)
93
+ duplicate = root / "duplicate.jsonl"
94
+ duplicate.write_text(
95
+ "\n".join(
96
+ [
97
+ json.dumps(
98
+ {
99
+ "app_id": 123,
100
+ "app_name": "First",
101
+ "app_description": "Description.",
102
+ }
103
+ ),
104
+ json.dumps(
105
+ {
106
+ "app_id": "123",
107
+ "app_name": "Second",
108
+ "app_description": "Description.",
109
+ }
110
+ ),
111
+ ]
112
+ )
113
+ + "\n",
114
+ encoding="utf-8",
115
+ )
116
+ missing_field = root / "missing_field.json"
117
+ missing_field.write_text(
118
+ json.dumps({"123": {"app_name": "Test VR"}}),
119
+ encoding="utf-8",
120
+ )
121
+
122
+ with self.assertRaises(ValueError):
123
+ check_metadata_cache(duplicate, ["123"])
124
+ with self.assertRaises(ValueError):
125
+ check_metadata_cache(missing_field, ["123"])
126
+
127
+ def test_verify_external_embedding_cache_against_manifest(self):
128
+ with tempfile.TemporaryDirectory() as tmpdir:
129
+ root = Path(tmpdir)
130
+ cache = root / "embedding_dict.json"
131
+ cache.write_bytes(b"frozen-cache")
132
+ manifest = root / "cache_manifest.json"
133
+ manifest.write_text(
134
+ json.dumps(
135
+ {
136
+ "artifact": "embedding_dict.json",
137
+ "size_bytes": cache.stat().st_size,
138
+ "sha256": sha256_file(cache),
139
+ "entry_count": 1,
140
+ "embedding_dimension": 2,
141
+ }
142
+ ),
143
+ encoding="utf-8",
144
+ )
145
+
146
+ report = check_embedding_cache(cache, manifest)
147
+
148
+ self.assertEqual(report["entry_count"], 1)
149
+ self.assertEqual(report["embedding_dimension"], 2)
150
+
151
+
152
+ if __name__ == "__main__":
153
+ unittest.main()
tests/test_run_ape.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import tempfile
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ from approach.run_ape import (
7
+ _resolve_ape_checkpoint,
8
+ _verify_ape_checkpoint,
9
+ build_parser,
10
+ run,
11
+ )
12
+
13
+
14
+ class RunApeTests(unittest.TestCase):
15
+ def test_custom_checkpoint_requires_explicit_trust(self):
16
+ with tempfile.TemporaryDirectory() as tmpdir:
17
+ checkpoint = Path(tmpdir) / "custom.pth"
18
+ checkpoint.write_bytes(b"not-the-released-checkpoint")
19
+ with self.assertRaises(ValueError):
20
+ _verify_ape_checkpoint(checkpoint)
21
+ _verify_ape_checkpoint(checkpoint, trust_custom_checkpoint=True)
22
+
23
+ def test_checkpoint_resolver_accepts_repo_relative_path(self):
24
+ repo_relative = Path("approach/ovod/APE/ape_d_model_final.pth")
25
+ resolved = _resolve_ape_checkpoint(Path("approach/ovod/APE"), repo_relative)
26
+
27
+ self.assertEqual(resolved, (Path.cwd() / repo_relative).resolve())
28
+
29
+ def test_configurable_runner_selects_shard_and_writes_isolated_output(self):
30
+ calls = []
31
+
32
+ def inference(**kwargs):
33
+ calls.append(kwargs)
34
+ return [
35
+ {
36
+ "category_name": kwargs["text_prompt"].split(":", 1)[0],
37
+ "bbox": [1, 2, 3, 4],
38
+ "score": 0.9,
39
+ }
40
+ ]
41
+
42
+ with tempfile.TemporaryDirectory() as tmpdir:
43
+ root = Path(tmpdir)
44
+ questions = root / "questions.jsonl"
45
+ candidates = root / "candidates.jsonl"
46
+ output = root / "predictions.json"
47
+ questions.write_text(
48
+ "\n".join(
49
+ [
50
+ json.dumps({"question_id": 0, "image": "123_4.jpg"}),
51
+ json.dumps({"question_id": 1, "image": "456_7.jpg"}),
52
+ ]
53
+ )
54
+ + "\n"
55
+ )
56
+ candidates.write_text(
57
+ "\n".join(
58
+ [
59
+ json.dumps(
60
+ {
61
+ "question_id": 0,
62
+ "text": '{"objects": {"button": "round red"}}',
63
+ }
64
+ ),
65
+ json.dumps(
66
+ {
67
+ "question_id": 1,
68
+ "text": {"objects": {"lever": "long silver"}},
69
+ }
70
+ ),
71
+ ]
72
+ )
73
+ + "\n"
74
+ )
75
+ args = build_parser().parse_args(
76
+ [
77
+ "--questions",
78
+ str(questions),
79
+ "--candidates",
80
+ str(candidates),
81
+ "--images-dir",
82
+ str(root / "images"),
83
+ "--output",
84
+ str(output),
85
+ "--shard-index",
86
+ "0",
87
+ "--num-shards",
88
+ "2",
89
+ ]
90
+ )
91
+
92
+ report = run(args, inference=inference)
93
+ shard_output = root / "predictions.shard00-of-02.json"
94
+
95
+ self.assertTrue(shard_output.exists())
96
+ self.assertTrue((root / "predictions.shard00-of-02.progress.json").exists())
97
+ self.assertEqual(json.loads(shard_output.read_text())[0]["image_id"], 123004)
98
+
99
+ self.assertEqual(report["records_selected"], 1)
100
+ self.assertEqual(len(calls), 1)
101
+ self.assertEqual(calls[0]["confidence_threshold"], 0.15)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ unittest.main()
tests/test_run_vlm.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import tempfile
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ from approach.run_vlm import build_parser, run
7
+
8
+
9
+ class RunVlmTests(unittest.TestCase):
10
+ def test_configurable_vlm_runner_writes_selected_records(self):
11
+ calls = []
12
+
13
+ def processor(profile, question, image_path, ablation, key_index):
14
+ calls.append((profile, question, Path(image_path).name, ablation, key_index))
15
+ return {"objects": {"button": "round red"}}
16
+
17
+ with tempfile.TemporaryDirectory() as tmpdir:
18
+ root = Path(tmpdir)
19
+ questions = root / "questions.jsonl"
20
+ questions.write_text(
21
+ "\n".join(
22
+ [
23
+ json.dumps({"question_id": 0, "image": "123_4.jpg", "text": "mine"}),
24
+ json.dumps({"question_id": 1, "image": "456_7.jpg", "text": "mine"}),
25
+ ]
26
+ )
27
+ + "\n"
28
+ )
29
+ args = build_parser().parse_args(
30
+ [
31
+ "--questions",
32
+ str(questions),
33
+ "--images-dir",
34
+ str(root / "images"),
35
+ "--output",
36
+ str(root / "answers.jsonl"),
37
+ "--start-index",
38
+ "0",
39
+ "--end-index",
40
+ "1",
41
+ "--profile",
42
+ "paper_claude",
43
+ ]
44
+ )
45
+
46
+ report = run(args, processor=processor)
47
+ output = root / "answers.rows0-1.jsonl"
48
+ answer = json.loads(output.read_text().strip())
49
+
50
+ self.assertEqual(report["completed"], 1)
51
+ self.assertEqual(answer["question_id"], 0)
52
+ self.assertEqual(answer["model_id"], "anthropic/claude-3.5-sonnet")
53
+ self.assertEqual(calls[0][:4], ("paper_claude", "mine", "123_4.jpg", False))
54
+
55
+ def test_default_processor_uses_app_metadata_cache_without_live_fallback(self):
56
+ from approach.vlm.gpt4v.gpt4v import get_steam_app_data, load_app_metadata_cache
57
+
58
+ with tempfile.TemporaryDirectory() as tmpdir:
59
+ root = Path(tmpdir)
60
+ cache_path = root / "metadata.json"
61
+ cache_path.write_text(
62
+ json.dumps(
63
+ {
64
+ "123": {
65
+ "app_name": "Test VR",
66
+ "app_description": "Synthetic cache-only description.",
67
+ }
68
+ }
69
+ ),
70
+ encoding="utf-8",
71
+ )
72
+ cache = load_app_metadata_cache(cache_path)
73
+
74
+ self.assertEqual(
75
+ get_steam_app_data("123", "123_4.jpg", cache),
76
+ ("Test VR", "Synthetic cache-only description."),
77
+ )
78
+ with self.assertRaises(KeyError):
79
+ get_steam_app_data("456", "456_7.jpg", cache)
80
+
81
+
82
+ if __name__ == "__main__":
83
+ unittest.main()
tests/test_semantic_matching_cache.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib.util
2
+ import json
3
+ import os
4
+ import sys
5
+ import tempfile
6
+ import types
7
+ import unittest
8
+ from pathlib import Path
9
+ from unittest import mock
10
+
11
+ import numpy # noqa: F401 - preload C extension before dynamic module reloads.
12
+
13
+
14
+ MODULE_PATH = (
15
+ Path(__file__).resolve().parents[1]
16
+ / "evaluation"
17
+ / "pycocotools_ovod"
18
+ / "semantic_matching.py"
19
+ )
20
+
21
+
22
+ class _FakeEmbeddings:
23
+ def __init__(self):
24
+ self.calls = []
25
+
26
+ def create(self, input, model):
27
+ self.calls.append((list(input), model))
28
+
29
+ class Response:
30
+ def model_dump(self_inner):
31
+ return {
32
+ "data": [
33
+ {"embedding": [1.0, 0.0]},
34
+ {"embedding": [1.0, 0.0]},
35
+ ]
36
+ }
37
+
38
+ return Response()
39
+
40
+
41
+ def load_semantic_matching(cache_path, *, offline=False, readonly=False, api_key=None, fake_openai=None):
42
+ fake_openai = fake_openai or types.SimpleNamespace(embeddings=_FakeEmbeddings())
43
+
44
+ with mock.patch.dict(
45
+ os.environ,
46
+ {
47
+ "ORIENTER_EMBEDDING_CACHE": str(cache_path),
48
+ "ORIENTER_EMBEDDING_OFFLINE": "1" if offline else "",
49
+ "ORIENTER_EMBEDDING_READONLY": "1" if readonly else "",
50
+ "ZHIPU_API_KEY": api_key or "",
51
+ },
52
+ clear=False,
53
+ ), mock.patch.dict(sys.modules, {"openai": fake_openai}):
54
+ name = f"semantic_matching_under_test_{id(cache_path)}_{offline}_{readonly}"
55
+ spec = importlib.util.spec_from_file_location(name, MODULE_PATH)
56
+ module = importlib.util.module_from_spec(spec)
57
+ spec.loader.exec_module(module)
58
+ return module, fake_openai
59
+
60
+
61
+ class SemanticMatchingCacheTests(unittest.TestCase):
62
+ def test_offline_cache_hit_does_not_call_api(self):
63
+ with tempfile.TemporaryDirectory() as tmpdir:
64
+ cache_path = Path(tmpdir) / "embedding_dict.json"
65
+ cache_path.write_text(json.dumps({"Button": [1.0, 0.0]}))
66
+
67
+ module, fake_openai = load_semantic_matching(cache_path, offline=True)
68
+
69
+ self.assertTrue(module.is_semantic_match("button", "button", eval_dimension="s"))
70
+ self.assertEqual(fake_openai.embeddings.calls, [])
71
+
72
+ def test_offline_cache_miss_fails_before_api_call(self):
73
+ with tempfile.TemporaryDirectory() as tmpdir:
74
+ cache_path = Path(tmpdir) / "embedding_dict.json"
75
+ cache_path.write_text(json.dumps({"Known": [1.0, 0.0]}))
76
+
77
+ module, fake_openai = load_semantic_matching(cache_path, offline=True)
78
+
79
+ with self.assertRaisesRegex(RuntimeError, "ORIENTER_EMBEDDING_OFFLINE=1"):
80
+ module.is_semantic_match("known", "missing", eval_dimension="s")
81
+
82
+ self.assertEqual(fake_openai.embeddings.calls, [])
83
+ self.assertFalse((Path(tmpdir) / "embedding_dict.json.save").exists())
84
+
85
+ def test_invalid_cache_is_rejected_at_load_time(self):
86
+ with tempfile.TemporaryDirectory() as tmpdir:
87
+ cache_path = Path(tmpdir) / "embedding_dict.json"
88
+ cache_path.write_text(json.dumps({"A": [1.0, 0.0], "B": [1.0]}))
89
+
90
+ with self.assertRaisesRegex(ValueError, "dimension"):
91
+ load_semantic_matching(cache_path, offline=True)
92
+
93
+ def test_non_finite_cache_value_is_rejected_at_load_time(self):
94
+ with tempfile.TemporaryDirectory() as tmpdir:
95
+ cache_path = Path(tmpdir) / "embedding_dict.json"
96
+ cache_path.write_text(json.dumps({"A": [1.0, float("nan")]}))
97
+
98
+ with self.assertRaisesRegex(ValueError, "non-finite"):
99
+ load_semantic_matching(cache_path, offline=True)
100
+
101
+ def test_readonly_cache_does_not_write_after_api_fill(self):
102
+ with tempfile.TemporaryDirectory() as tmpdir:
103
+ cache_path = Path(tmpdir) / "embedding_dict.json"
104
+ cache_path.write_text(json.dumps({}))
105
+ original = cache_path.read_text()
106
+
107
+ module, fake_openai = load_semantic_matching(
108
+ cache_path,
109
+ readonly=True,
110
+ api_key="fake-key",
111
+ )
112
+ module.SAVE_INTERVAL = 1
113
+
114
+ self.assertTrue(module.is_semantic_match("alpha", "beta", eval_dimension="s"))
115
+ self.assertEqual(fake_openai.embeddings.calls, [(["Alpha", "Beta"], "embedding-3")])
116
+ self.assertEqual(cache_path.read_text(), original)
117
+ self.assertFalse((Path(tmpdir) / "embedding_dict.json.save").exists())
118
+
119
+
120
+ if __name__ == "__main__":
121
+ unittest.main()
tests/test_vlm_adapter.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tempfile
2
+ import unittest
3
+ import json
4
+ from pathlib import Path
5
+ from unittest import mock
6
+
7
+ from approach.vlm.gpt4v import gpt4v
8
+
9
+
10
+ class VlmAdapterTests(unittest.TestCase):
11
+ def test_app_metadata_cache_fails_on_missing_app_id(self):
12
+ with tempfile.TemporaryDirectory() as tmpdir:
13
+ cache_path = Path(tmpdir) / "apps.json"
14
+ cache_path.write_text(json.dumps({"123": {"app_name": "App", "app_description": "Desc"}}))
15
+ cache = gpt4v.load_app_metadata_cache(cache_path)
16
+
17
+ self.assertEqual(gpt4v.get_steam_app_data("123", "123_4.jpg", cache), ("App", "Desc"))
18
+ with self.assertRaises(KeyError):
19
+ gpt4v.get_steam_app_data("456", "456_1.jpg", cache)
20
+
21
+ def test_process_image_uses_selected_openrouter_profile_and_real_mime_type(self):
22
+ client = mock.Mock()
23
+ client.complete_json.return_value = {"objects": {"button": "round red"}}
24
+
25
+ with tempfile.TemporaryDirectory() as tmpdir:
26
+ image_path = Path(tmpdir) / "123_4.png"
27
+ image_path.write_bytes(b"not-decoded-by-the-adapter")
28
+ with mock.patch.object(
29
+ gpt4v,
30
+ "get_steam_app_data",
31
+ return_value=("Test App", "Test description"),
32
+ ), mock.patch.object(
33
+ gpt4v,
34
+ "OpenAICompatibleChatClient",
35
+ return_value=client,
36
+ ) as client_class:
37
+ result = gpt4v.process_image(
38
+ "default",
39
+ "describe",
40
+ str(image_path),
41
+ False,
42
+ 0,
43
+ )
44
+
45
+ self.assertEqual(result["objects"]["button"], "round red")
46
+ self.assertEqual(client_class.call_args.args[0].provider, "openrouter")
47
+ encoded_image = client.complete_json.call_args.args[1][0]
48
+ self.assertEqual(encoded_image.media_type, "image/png")
49
+
50
+
51
+ if __name__ == "__main__":
52
+ unittest.main()
util/llm_gemini.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import google.generativeai as genai
3
+
4
+ API_KEY = os.environ.get("GOOGLE_API_KEY", "[GOOGLE_API_KEY]")
5
+
6
+ genai.configure(api_key=API_KEY)
7
+ model = genai.GenerativeModel('gemini-pro')
8
+
9
+ def generate_content(prompt, _place_holder = None):
10
+ response = model.generate_content(prompt)
11
+ return response.text
12
+
13
+ if __name__ == "__main__":
14
+ print(generate_content("What are you?"))
util/llm_third_party.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ import json
4
+ import llm_gemini
5
+
6
+ AVAILABLE_MODELS = [
7
+ "claude-3-haiku",
8
+ "claude-3-sonnet",
9
+ "claude-3-opus",
10
+ "gemini-pro",
11
+ "gpt-3.5-turbo",
12
+ "gpt-4-turbo",
13
+ "gpt-4",
14
+ ]
15
+
16
+ MODEL = "claude-3-haiku"
17
+
18
+ URL = "https://api.theb.ai/v1/chat/completions"
19
+ # URL = "https://api.baizhi.ai/v1/chat/completions"
20
+ API_KEY = os.environ.get("THEB_API_KEY", "[THEB_API_KEY]")
21
+
22
+
23
+ def generate_content(prompt, model=MODEL, api_key=API_KEY):
24
+ assert model in AVAILABLE_MODELS, f"Model {model} not available"
25
+ if model == "gemini-pro":
26
+ return llm_gemini.generate_content(prompt)
27
+ payload = json.dumps({
28
+ "model": model,
29
+ "messages": [
30
+ {
31
+ "role": "user",
32
+ "content": prompt
33
+ }
34
+ ],
35
+ "stream": False
36
+ })
37
+ headers = {
38
+ 'Authorization': f'Bearer {api_key}',
39
+ 'Content-Type': 'application/json'
40
+ }
41
+
42
+ response = requests.request("POST", URL, headers=headers, data=payload, timeout=10)
43
+ if response.status_code != 200:
44
+ retry_count = 0
45
+ while retry_count < 5:
46
+ response = requests.request("POST", URL, headers=headers, data=payload, timeout=10)
47
+ if response.status_code == 200:
48
+ break
49
+ else:
50
+ retry_count += 1
51
+ if retry_count == 5:
52
+ raise Exception(f"Failed to get completion from LLM. Status code: {response.status_code}. Response: {response.text}")
53
+
54
+ # print(response)
55
+
56
+ return response.json()['choices'][0]['message']['content']
57
+
58
+
59
+ if __name__ == "__main__":
60
+ print(generate_content("What are you?", "claude-3-haiku"))