CoolFace
Datasetpublic

yrlyrl/spatial-mmcot-ipt_pet

Spatial MMCoT v1 · ipt_pet Perspective taking in ProcTHOR houses (IPT PET-ipt). The input is the current view with an X marked on the floor; the target is the view after moving to the X and turning 90 degrees; the question asks whether a named object gets closer or further, or lies left or right. The reasoning text comes from the text chain-of-thought donor named below, joined by row order (its question and options equal ours on every upstream row, apart from one blank line… See the full description on the dataset page: https://huggingface.co/datasets/yrlyrl/spatial-mmcot-ipt_pet.

sourceHugging Faceotherupdated 1d agoView on Hugging Face
0likes51downloads
Dataset Card

Spatial MMCoT v1 · ipt_pet

Perspective taking in ProcTHOR houses (IPT PET-ipt). The input is the current view with an X marked on the floor; the target is the view after moving to the X and turning 90 degrees; the question asks whether a named object gets closer or further, or lies left or right. The reasoning text comes from the text chain-of-thought donor named below, joined by row order (its question and options equal ours on every upstream row, apart from one blank line before the options). The donor model saw the input view (its plans describe the red X and the furniture around it) but never the target view, and it drew nothing: the plan (thought_0) already reaches the answer, and the final thought is the donor's closing sentence 'My answer is [A].' (or [B]), not a read-back of the generated view. The plans are the donor's rationales for the stored answer, not checked geometry, and many state the turn backwards (see Known issues). The option order is shuffled per row.

Supervision kind (supervision_kind in meta): visual_aux on every row: the reasoning text is a text-only chain of thought, written by a model that saw the input image(s) but never the target image, so the answer can usually be derived from the text before the image. Use these rows as image supervision, not as interleaved reasoning whose answer depends on the generated image, and not as evidence that generating the image helps.

Upstream: images, questions and answers from `weikaih/imaginative-perception-token-pet-ipt`; every thought (plan and read-back) from the text chain-of-thought release `weikaih/imaginative-perception-token-pet-textcot`. Licence: undeclared. Neither upstream repository declares a licence; this converted copy is shared for research use only, whatever terms the upstream authors set apply to it as well, and it will be taken down at their request. The images are renders of ProcTHOR houses made with AI2-THOR, both released by the Allen Institute for AI under Apache-2.0.

Known issues

Rows with a measured per-row problem are listed in reports/known_issues/, one TSV per issue (a # <description> line, then row_uid<TAB>split<TAB>detail lines), so they can be filtered out. They are still in this release: no row was removed for these issues.

issuerowstrainvalidationwhathow it was foundfile
plan_turn_rule_backwards67365716The plan states the 90-degree turn backwards (e.g. on a right turn 'what was in front of me is now on my right') and still names the labelled side; the text is a rationale for the stored answer, not geometry.thought&#95;0 has a 'what was &lt;front&#124;left&#124;right&#124;behind&gt; ... is/becomes/moves ... &lt;side&gt;' statement that contradicts the question's turn direction (right turn: front-&gt;left, right-&gt;front, behind-&gt;right, left-&gt;behind; mirrored for left); measured 2026-09-25; known&#95;issues.py sha1 655e5307; export 4aaee1f rows 6c471ac0/6a8e1d28reports/known_issues/plan_turn_rule_backwards.tsv
contradicting_answers_same_images15150Another shipped row asks the same question (with the options in another order, or reworded) of the same input and target images and has the opposite answer; S10's conflict check compares the question with its options in listed order, so it did not see them.rows grouped by (input-image md5, question text, option set) or by (input-image md5, question type, turn direction, last object phrase incl. 'on the left/right') whose answers differ; every member is listed; measured 2026-09-25; known&#95;issues.py sha1 655e5307; export 4aaee1f rows 6c471ac0/6a8e1d28reports/known_issues/contradicting_answers_same_images.tsv
plan_repeats_donor_chain110The donor wrote its chain twice, so the plan holds its Step 1-4 twice in a row, and the read-back ends with the answer twice ('My answer is &#91;X&#93;. The answer is X').thought&#95;0 contains 'Step 1:' more than once; measured 2026-09-25; known&#95;issues.py sha1 655e5307; export 4aaee1f rows 6c471ac0/6a8e1d28reports/known_issues/plan_repeats_donor_chain.tsv

To leave the listed rows out (the snippet in the loader section downloads reports/known_issues/ with the data):

python
import glob, os
root = "<root>/ipt_pet"
drop = {line.split("\t")[0] for f in glob.glob(os.path.join(root, "reports/known_issues/*.tsv"))
        for line in open(f) if line.strip() and not line.startswith(("#", "row_uid\t"))}
# keep a row when its row_uid (a column of train/, meta/ and preview/) is not in drop

Measured caveats

Measured on this release by the pre-publication review (2026-09-25): problems that cannot be listed row by row (a shortcut in the options, a label convention, an upstream labelling scheme) and what the review found around the lists above. Where a caveat counts listed rows ("listed as ..."), the count is the table's, read from reports/known_issues/summary.json. Its other numbers are the review's own measurements, which no file carries: they hold for exactly these rows and are not re-measured automatically. Items marked Training-signal defect are problems in what the rows teach, not only in how they are described; no row was removed for them.

  • —Training-signal defect. The plans often state the 90-degree turn backwards and still name the labelled side. On 657 of the 4,812 training position rows (13.7%) and 16 of the 120 validation position rows (listed as plan_turn_rule_backwards), thought_0 states a backwards turn rule (for a right turn, 'what was in front of me is now on my right' or 'what was on my right moves behind me'; left turns have the mirror-image mistake). Of the training plans that state such a rule at all (1,169), 56% state it backwards. These plans are trained as text through output_text_list (673 of the 8,632 rows, 7.8%, and a lower bound: the detector reads one wording of the rule), so do not use them as a model of how turns work.
  • —Training-signal defect. S10's label-conflict check compares the question with its options in the order listed, so conflicts it cannot see remain: 15 rows (15 train, 0 validation; listed as contradicting_answers_same_images) ask the same or a reworded question of the same input and target images as another listed row, with the opposite answer. 7f5fb3c36493455a and 7448b1dfd2d694b6 ask the same question with the options swapped and opposite answers (left / right); the others ask reworded questions about the same object (the <object> on the left/right) of the same images (houses 6810, 9435, 8636, 7425 and 7866). The object reference in those rows is ambiguous upstream (the room holds two such objects).
  • —The IPT paper's PET evaluation sets are not held out for a model trained here. weikaih/imaginative-perception-token-pet-eval-ai2thor (278 rows, ProcTHOR houses 5-998) shares no house with this release (houses 2001-9998), but it uses the same ProcTHOR renders and the same question templates (every one of its 278 questions, object names masked, occurs here): it is in-domain, not out-of-distribution.
  • —On b1d7838bd87078fc (flagged S4.answer_moved; listed as plan_repeats_donor_chain: 1 row, 1 train and 0 validation) the donor wrote its chain twice, so the plan repeats its four steps and the final thought ends 'My answer is [B]. The answer is B.'.

Size

splitrowstarget image slotsdistinct target images
train8,4208,4204,847
validation212212126

A slot is one target position in one row; rows can share a target image (same scene or same intermediate state), and a row can repeat one of its own targets, so there are fewer distinct images (by content hash) than slots.

tasktrainvalidation
perspective_taking8,420212

Input images per row: 1. Target images per row (the images the model is trained to generate): 1. Image corpus (source_scene_corpus): procthor 8,632.

Row format

One row is: input image(s) and a question, then K rounds of thought → target image (the target is the source's own ground-truth image, which the model is trained to generate), then a final thought (normally a read-back of the last target; where a source's final thought is something else, or often leaves out the answer, the source note or Known issues says so) and the answer; here K is 1. In the train config:

image_list        list<binary>  inputs first, then the K target images in order
num_input_images  int64         how many of image_list are inputs
instruction_list  list<string>  one element: system prompt + question + options
output_text_list  list<string>  K+1 elements:
  [0]   <think>plan 1</think><image_start>
  [j]   <image_end><think>plan j+1</think><image_start>
  [K]   <image_end><think>read-back</think><answer>answer</answer>
row_uid           string        join key to `meta` and `preview`

Every image is a JPEG, and no input image is larger than 512 px on its long edge (measured on this release, 2026-09-25); the size each target was stored at is target_px in meta.

On every row <answer> holds the option key (answer type mcq_letter 8,632) that the model is trained to emit (a letter for mcq_letter), while meta.answer_value (the answer column of preview) holds that option's text. Map the key through the options listed in the question before comparing the two, and score model output against <answer>.

The system prompt is ThinkMorph's VLM_THINK_SYSTEM_PROMPT from its inferencer.py, verbatim (GEN_THINK_SYSTEM_PROMPT there has the same text), including its leading and trailing newline. The markers are plain strings, not tokenizer special tokens; the prompt writes </image_end> and the data writes <image_end>, exactly as the ThinkMorph-7B checkpoint was trained.

preview shows the same rows with one column per slot: input_image_i for the inputs; for each of the K = num_steps rounds, the plan thought_j and its target target_image_j; and the read-back in thought_1 on every row.

meta holds the per-row sidecar: task, scene_id and geometry_uid (the scene and geometry keys; the split key is named in the split paragraph below), trajectory_id (a camera-path or sample label, empty where the source has none), num_steps, num_input_images, answer_type, answer_value, majority_class_rate, target_image_kind, target_px, est_tokens, licence, split (train / validation, the Hub split names), supervision_kind (full_interleaved / visual_aux / visual_only) and filter_flags. majority_class_rate is the share of the task's most frequent answer_value among its training rows: it measures answer skew and is not a guessing baseline (where a task mixes question types or each row has its own options it can be far below chance); compare scores with the text-only baselines below.

scene_id (equal to geometry_uid) is the ProcTHOR house. trajectory_id is a per-house sample index (perspective_sample_0-9), so one sample is the pair (scene_id, trajectory_id); never group by trajectory_id alone.

Per-row license in meta: undeclared 8,632.

Flags on released rows (filter_flags in meta and preview, comma-separated):

flagrowsmeaning
S5.replay_unsupported8,632no solver re-derives this task's answer from the trace, so S5 did not replay it
src_split=distance_change_closer1,850the upstream split file the row came from
src_split=distance_change_further1,850the upstream split file the row came from
src_split=relative_position_left_left1,235the upstream split file the row came from
src_split=relative_position_left_right1,235the upstream split file the row came from
src_split=relative_position_right_left1,231the upstream split file the row came from
src_split=relative_position_right_right1,231the upstream split file the row came from
S14.sampled_qa200chosen for the S14 human spot-check (reports/s14_sample.tsv)
S0.row_uid_salted70the content-derived row_uid repeated within the build and was re-derived
S4.answer_moved1the plan stated the answer before the image ('the answer is X'); that phrase was cut from the plan and 'The answer is X.' appended to the read-back

Training with a BAGEL-family loader

Every row here has one input image (num_input_images is 1), so the stock ThinkMorph UnifiedEditIterableDataset (https://github.com/ThinkMorph/ThinkMorph: image_list[0] as input, image_list[j+1] after output_text_list[j]) and the IPT release's version (which reads num_input_images) both read it as intended. Mixed with a source whose rows have more than one input image, only a loader that reads num_input_images is correct.

The stock BAGEL edit loader (ByteDance-Seed/Bagel) cannot train these rows: it never reads output_text_list and expects each instruction_list element to be a list of paraphrases.

parquet_info.json keys each training chunk as <source>/<split>/<file>, here ipt_pet/train/chunk_00000.parquet, with row-group counts read from the parquet footers. The loader matches a chunk only when its key equals the path it builds, os.path.join(data_dir, file), and skips a chunk with no key without a warning: a source that is alone in its group then fails with IndexError: list index out of range, and in a mixed group it adds no rows. Download into a directory named after the source, not after the repository:

python
from huggingface_hub import snapshot_download
snapshot_download("yrlyrl/spatial-mmcot-ipt_pet", repo_type="dataset", local_dir="<root>/ipt_pet",
                  allow_patterns=["train/*", "validation/*", "parquet_info.json", "reports/known_issues/*"])

Then either run from <root> with data_dir: ipt_pet/train and parquet_info_path: ipt_pet/parquet_info.json, or rebuild the index with absolute keys and use an absolute data_dir:

python
import json, os
root = "/abs/path/to/root"                      # the directory that holds ipt_pet/
info = json.load(open(os.path.join(root, "ipt_pet", "parquet_info.json")))
info = {os.path.join(root, k): v for k, v in info.items()}
json.dump(info, open(os.path.join(root, "ipt_pet", "parquet_info_abs.json"), "w"))
# data_dir = os.path.join(root, "ipt_pet", "train")   (spelled exactly so, no trailing slash)
# parquet_info_path = os.path.join(root, "ipt_pet", "parquet_info_abs.json")

The Hugging Face cache (.../snapshots/<hash>/train/) or a folder named spatial-mmcot-ipt_pet matches no key.

num_used_data counts chunk files, not rows: the loader repeats this source's file list up to that number, lists every (file, row group) pair, and deals whole row groups out, floor(R / worldsize) to each rank and floor(that / numworkers) to each DataLoader worker. The remainder is never read. This source has 1 training chunk file holding 66 row groups of up to 128 rows, so keep num_used_data large, e.g. the 128 of ThinkMorph's interleaved_reasoning.yaml (upstream's example.yaml asks for more than GPUs x workers); every row group is then read. Set to 1 and alone in its group on 8 GPUs with 4 workers, it reads only 64 of the 66 row groups. In a run that mixes sources, give each source the same multiple of its own training chunk-file count, e.g. 128 per file (128 here): the file list is repeated up to num_used_data entries, so a flat 128 for every source would read a two-file source's rows half as often as a one-file source's.

How the rows were chosen

stagerows
upstream rows read20,531
refused before conversion (S0raw)0
after conversion and per-row filters20,531
removed by S10 (369 exact duplicates; 6 withheld as label conflicts: identical question text, options in the same order, identical images, different answers)375
removed by answer-prior balancing (S13)11,524
released8,632

Every removed row has one line, with its reason, in reports/:

filestepreason (the line's `flag`, or the field shown)rows
s10_dropped.jsonlS10level: exact369
s10_label_conflicts.jsonlS10S10.label_conflict6
s13_dropped.jsonlS13step: cell11,524

Every line of s13_dropped.jsonl has reason: prior_downsample; step names the balancing pass that removed it, and split is written train or val (the Hub's validation).

<details><summary>Per-step counters of the conversion</summary>

20,531 upstream rows were read; S0raw refused 0 before a row existed and passed 20,531 to the first step. S0 runs once more, last, on the final bytes. The reason for every refused, dropped or quarantined row is in the files above.

stepinoutdroppedquarantinedrejectedrepaired
S420,53120,5310001
S4c20,53120,5310000
S520,53120,5310000
S820,53120,5310000
S920,53120,5310000
S0 (final structural check, after S9)20,53120,5310000

</details>

The train/validation split keeps rows sharing a scene_id in meta on one side, and the assignment is frozen (splits/ in the summary repository). S12 saw 20,156 rows under 7,560 keys. No validation input image has the content of a training input image, and none is a pixel-level near-copy of one. S12 does not record per source whether that test ran, but it skips it only for a source whose spec sets split_leak_pixels: false, and no spec does; over all sources it compared 21,661 candidate pairs (perceptual hash within 6 bits) pixel by pixel and found no near-copy (checked 2026-09-25).

Answer-prior balancing (S13)

Each (task, split) group is checked separately. An answer is the answer value compared as lower-cased text without a trailing full stop, with 'farther' read as 'further' and 'nearer' as 'closer' (for multiple choice, the option text, not the letter; where the candidates are drawn in the image, as in zebrajigsaw and zebratetris, the answer is the letter itself). An answer is real when it holds at least 5 rows and 2% of the group; k is the number of real answers. Answer step: the target is max(30%, 1/k) when k >= 2, and max(30%, 1/d) over the d distinct answers when k = 1; a validation group uses the larger of its own target and its task's train target. A group is cut only when k >= 1 and its most common answer holds more than the target plus 5 percentage points; every answer is then capped at one common count, chosen so that none exceeds the target, and smaller answers keep all their rows. At the answer step, a group at or below that trigger, or with no real answer (k = 0), is left as it is, so its most common answer can hold up to the target plus 5 percentage points. A task whose train group has exactly two real answers is instead cut, in every split, so that its two largest answers have equal counts, with no trigger. Rank and label steps: then, in a group where every option value of every row is a number, the rank of the correct option among the sorted values, and after it, in a group where every trained answer is an option label, the label, are each capped by the same cut-and-trigger rule on their own counts (own target, validation included): capped, never evened out, so two labels are cut only when one exceeds 55%, and then only down to 50%. These steps can also cut groups the answer step left whole, including k = 0 groups, and can raise an answer's final share above its target; the run fails if a real answer ends above the target plus 5 percentage points. A train group of at least 20 rows in which one answer holds 90% or more fails the run. PET (exactcellspet) instead cuts each (question type x turn direction) cell to equal counts of its two answers; a PET cell that shows only one answer is removed.

Before this cut the PET answer could be guessed without the image: distance questions were answered 'closer' on 74.8% of training rows whichever way the turn went; position questions were answered with the side opposite the turn on 80.6% of training rows. The marginal answer counts hide this, because the shortcut depends on the question type and the turn, so each (question type x turn direction) cell is cut to equal counts of its two answers.

tasksplitcellanswers beforekept per answer
perspective_takingtraindistance/leftcloser 2,506, further 1,0011,001
perspective_takingtraindistance/rightcloser 2,851, further 803803
perspective_takingtrainposition/leftleft 1,196, right 4,9181,196
perspective_takingtrainposition/rightleft 5,077, right 1,2101,210
perspective_takingvalidationdistance/leftcloser 86, further 3333
perspective_takingvalidationdistance/rightcloser 82, further 1313
perspective_takingvalidationposition/leftleft 39, right 15139
perspective_takingvalidationposition/rightleft 169, right 2121

S13 removed 11,524 rows from this source.

Text-only baselines

Accuracy of guessers that never see an image. For each task the released training rows are split into two fixed halves by a hash of row_uid; each guesser is fitted on one half and scored once on the other (one held-out half, not cross-validation; eval rows below). The reference is chance (the mean of 1 / number of options) where every row is multiple choice, and otherwise the eval-half accuracy of always giving the answer most common in the fit half (when a task's top answers are nearly tied, this need not be the task's most common answer; the line after the table gives that answer's validation score). Accuracies are recounted from the stored rates and eval rows, so they are exact. A task is flagged when a text-only guesser beats its reference by more than 0.15 (for a free-form task, a guesser other than the most common answer). A flagged task can be partly answered from the text alone; an unflagged task passed only these probes, which do not prove the text carries no answer. Report scores on every task next to this baseline.

Guessers: keywords: the most common answer per set of spatial words in the question; last_mentioned: the option named last in the question body; letter_prior: the most common answer letter; majority: the answer most common in the fit half; option_prior: the option text that won most often when shown; template: the most common answer per question wording (numbers masked, object names kept).

taskbest text-only guesseraccuracyreferencemargineval rowsflagged
perspective_takingoption_prior0.4980.500 (chance)-0.0024,220no

Spot-check (S14)

Pending. The S14 rows are chosen and flagged S14.sampled_qa in meta and preview; the human pass over them has not been signed off yet.

Citation

Both upstream repositories were released with Bigverdi et al., Imaginative Perception Tokens Enhance Spatial Reasoning in Multimodal Language Models (arXiv:2606.03988); please cite it:

bibtex
@misc{bigverdi2026imaginativeperceptiontokensenhance,
  title={Imaginative Perception Tokens Enhance Spatial Reasoning in Multimodal Language Models},
  author={Mahtab Bigverdi and Linjie Li and Weikai Huang and Yiming Liu and Jaemin Cho and Jieyu Zhang and Tuhin Kundu and Chris Dangjoo Kim and Zelun Luo and Linda Shapiro and Ranjay Krishna},
  year={2026},
  eprint={2606.03988},
  archivePrefix={arXiv},
  primaryClass={cs.AI},
  url={https://arxiv.org/abs/2606.03988}
}

Provenance

The release files were written by our conversion code (the code repository is not public yet), scripts/convert/export.py at commit 4aaee1f4e946, from build ipt_pet_r2. The build was made by scripts/convert/run_source.py from the same repository at commit 949af62f8ab7. S10, S12 and S13 ran before the export; reports/export_manifest.json pins every input the export read by SHA-1 (build_manifest_sha1, s10_keep_sha1, s12_assignments_sha1, s13_balanced_keep_sha1).

Every row removed between upstream and this release has one line, with its reason, in reports/: build/dropped.jsonl (rows refused before conversion or dropped by a conversion step); build/quarantine.jsonl (rows set aside by S4c because an automatic check could not match the read-back's conclusion to the label); s10_dropped.jsonl (duplicates removed by S10); s10_label_conflicts.jsonl (rows S10 withheld because another row asks the identical question, options in the same order, of the same images with a different answer); s13_dropped.jsonl (rows removed by answer-prior balancing). known_issues/ lists rows with a measured problem (see Known issues); reports/ also holds the build manifest (absolute paths cut to basenames) and counters, the S14 sample list (s14_sample.tsv: rowuid, task, split) and `exportmanifest.json. Part of [yrlyrl/spatial-mmcot`](https://huggingface.co/datasets/yrlyrl/spatial-mmcot).