Skip to content

Layout detr

Transformers-style LayoutDETR package.

BackgroundPreprocessing

Bases: StrEnum

Supported public background preprocessing modes.

Source code in models/layout-detr/src/layout_detr/configuration_layout_detr.py
14
15
16
17
18
19
20
21
class BackgroundPreprocessing(StrEnum):
    """Supported public background preprocessing modes."""

    none = auto()
    resize_256 = "256"
    resize_128 = "128"
    blur = auto()
    edge = auto()

LayoutDetrConfig

Bases: PretrainedConfig

Configuration for LayoutDETR model, processor, and pipeline.

Source code in models/layout-detr/src/layout_detr/configuration_layout_detr.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
class LayoutDetrConfig(PretrainedConfig):
    """Configuration for LayoutDETR model, processor, and pipeline."""

    model_type = "layout-detr"

    def __init__(
        self,
        *,
        dataset_name: str = "ad_banner",
        id2label: Mapping[int | str, str] | None = None,
        max_seq_length: int = 9,
        z_dim: int = 4,
        img_channels: int = 3,
        img_height: int = 256,
        img_width: int = 256,
        background_size: int = 256,
        hidden_dim: int = 256,
        bert_f_dim: int = 768,
        bert_num_encoder_layers: int = 12,
        bert_num_decoder_layers: int = 2,
        bert_num_heads: int = 4,
        max_text_length: int = 256,
        text_vocab_size: int = 30_522,
        med_config: Mapping[str, LayoutDetrMetadataValue] | None = None,
        backbone_name: str = "resnet50",
        image_mean: Sequence[float] = (0.485, 0.456, 0.406),
        image_std: Sequence[float] = (0.229, 0.224, 0.225),
        architecture: Literal["lightweight", "reference"] = "lightweight",
        model_subfolder: str = "model",
        processor_subfolder: str = "processor",
        original_training_options: Mapping[str, LayoutDetrMetadataValue] | None = None,
        conversion_report: Mapping[str, LayoutDetrMetadataValue] | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize LayoutDETR configuration."""
        raw_id2label = id2label or DEFAULT_ID2LABEL
        normalized_id2label = {int(key): value for key, value in raw_id2label.items()}
        super().__init__(id2label=normalized_id2label, **kwargs)  # ty: ignore[invalid-argument-type]

        self.dataset_name = dataset_name
        self.id2label = normalized_id2label
        self.label2id = {value: key for key, value in self.id2label.items()}
        self.max_seq_length = int(max_seq_length)
        self.z_dim = int(z_dim)
        self.img_channels = int(img_channels)
        self.img_height = int(img_height)
        self.img_width = int(img_width)
        self.background_size = int(background_size)

        self.hidden_dim = int(hidden_dim)
        self.bert_f_dim = int(bert_f_dim)
        self.bert_num_encoder_layers = int(bert_num_encoder_layers)
        self.bert_num_decoder_layers = int(bert_num_decoder_layers)
        self.bert_num_heads = int(bert_num_heads)
        self.max_text_length = int(max_text_length)
        self.text_vocab_size = int(text_vocab_size)

        self.med_config = dict(med_config or {})
        self.backbone_name = backbone_name
        self.image_mean = tuple(float(value) for value in image_mean)
        self.image_std = tuple(float(value) for value in image_std)
        self.architecture = architecture
        self.model_subfolder = model_subfolder
        self.processor_subfolder = processor_subfolder
        self.original_training_options = dict(original_training_options or {})
        self.conversion_report = dict(conversion_report or {})

    @property
    def num_labels(self) -> int:
        """Return the number of public semantic labels."""
        return len(cast(dict[int, str], self.id2label))

    @property
    def num_bbox_labels(self) -> int:
        """Return the model bbox-label count."""
        return self.num_labels

    @property
    def pad_label_id(self) -> int:
        """Return the internal padded label id."""
        return 0

    @property
    def max_elements(self) -> int:
        """Return the maximum generated element count."""
        return self.max_seq_length

num_labels property

num_labels: int

Return the number of public semantic labels.

num_bbox_labels property

num_bbox_labels: int

Return the model bbox-label count.

pad_label_id property

pad_label_id: int

Return the internal padded label id.

max_elements property

max_elements: int

Return the maximum generated element count.

__init__

__init__(
    *,
    dataset_name: str = "ad_banner",
    id2label: Mapping[int | str, str] | None = None,
    max_seq_length: int = 9,
    z_dim: int = 4,
    img_channels: int = 3,
    img_height: int = 256,
    img_width: int = 256,
    background_size: int = 256,
    hidden_dim: int = 256,
    bert_f_dim: int = 768,
    bert_num_encoder_layers: int = 12,
    bert_num_decoder_layers: int = 2,
    bert_num_heads: int = 4,
    max_text_length: int = 256,
    text_vocab_size: int = 30522,
    med_config: Mapping[str, LayoutDetrMetadataValue]
    | None = None,
    backbone_name: str = "resnet50",
    image_mean: Sequence[float] = (0.485, 0.456, 0.406),
    image_std: Sequence[float] = (0.229, 0.224, 0.225),
    architecture: Literal[
        "lightweight", "reference"
    ] = "lightweight",
    model_subfolder: str = "model",
    processor_subfolder: str = "processor",
    original_training_options: Mapping[
        str, LayoutDetrMetadataValue
    ]
    | None = None,
    conversion_report: Mapping[str, LayoutDetrMetadataValue]
    | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize LayoutDETR configuration.

Source code in models/layout-detr/src/layout_detr/configuration_layout_detr.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def __init__(
    self,
    *,
    dataset_name: str = "ad_banner",
    id2label: Mapping[int | str, str] | None = None,
    max_seq_length: int = 9,
    z_dim: int = 4,
    img_channels: int = 3,
    img_height: int = 256,
    img_width: int = 256,
    background_size: int = 256,
    hidden_dim: int = 256,
    bert_f_dim: int = 768,
    bert_num_encoder_layers: int = 12,
    bert_num_decoder_layers: int = 2,
    bert_num_heads: int = 4,
    max_text_length: int = 256,
    text_vocab_size: int = 30_522,
    med_config: Mapping[str, LayoutDetrMetadataValue] | None = None,
    backbone_name: str = "resnet50",
    image_mean: Sequence[float] = (0.485, 0.456, 0.406),
    image_std: Sequence[float] = (0.229, 0.224, 0.225),
    architecture: Literal["lightweight", "reference"] = "lightweight",
    model_subfolder: str = "model",
    processor_subfolder: str = "processor",
    original_training_options: Mapping[str, LayoutDetrMetadataValue] | None = None,
    conversion_report: Mapping[str, LayoutDetrMetadataValue] | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize LayoutDETR configuration."""
    raw_id2label = id2label or DEFAULT_ID2LABEL
    normalized_id2label = {int(key): value for key, value in raw_id2label.items()}
    super().__init__(id2label=normalized_id2label, **kwargs)  # ty: ignore[invalid-argument-type]

    self.dataset_name = dataset_name
    self.id2label = normalized_id2label
    self.label2id = {value: key for key, value in self.id2label.items()}
    self.max_seq_length = int(max_seq_length)
    self.z_dim = int(z_dim)
    self.img_channels = int(img_channels)
    self.img_height = int(img_height)
    self.img_width = int(img_width)
    self.background_size = int(background_size)

    self.hidden_dim = int(hidden_dim)
    self.bert_f_dim = int(bert_f_dim)
    self.bert_num_encoder_layers = int(bert_num_encoder_layers)
    self.bert_num_decoder_layers = int(bert_num_decoder_layers)
    self.bert_num_heads = int(bert_num_heads)
    self.max_text_length = int(max_text_length)
    self.text_vocab_size = int(text_vocab_size)

    self.med_config = dict(med_config or {})
    self.backbone_name = backbone_name
    self.image_mean = tuple(float(value) for value in image_mean)
    self.image_std = tuple(float(value) for value in image_std)
    self.architecture = architecture
    self.model_subfolder = model_subfolder
    self.processor_subfolder = processor_subfolder
    self.original_training_options = dict(original_training_options or {})
    self.conversion_report = dict(conversion_report or {})

LayoutDetrImageProcessor

Bases: BaseImageProcessor

Prepare ImageNet-normalized background tensors for LayoutDETR.

Source code in models/layout-detr/src/layout_detr/image_processing_layout_detr.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
class LayoutDetrImageProcessor(BaseImageProcessor):
    """Prepare ImageNet-normalized background tensors for LayoutDETR."""

    model_input_names = ["pixel_values"]

    def __init__(
        self,
        background_size: int = 256,
        image_mean: Sequence[float] = (0.485, 0.456, 0.406),
        image_std: Sequence[float] = (0.229, 0.224, 0.225),
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize image normalization settings."""
        super().__init__(**kwargs)
        self.background_size = int(background_size)
        self.image_mean = tuple(float(value) for value in image_mean)
        self.image_std = tuple(float(value) for value in image_std)

    @classmethod
    def from_config(cls, config: LayoutDetrConfig) -> "LayoutDetrImageProcessor":
        """Build an image processor from a LayoutDETR config."""
        return cls(
            background_size=config.background_size,
            image_mean=config.image_mean,
            image_std=config.image_std,
        )

    def preprocess(
        self,
        images: ImageInput | Sequence[ImageInput],
        *,
        background_preprocessing: BackgroundPreprocessing
        | str = BackgroundPreprocessing.none,
        canvas_size: tuple[int, int] | None = None,
        return_tensors: Literal["pt"] = "pt",
        **kwargs: str | int | float | bool | None,
    ) -> BatchFeature:
        """Preprocess a background image or batch.

        Args:
            images: PIL, NumPy, or torch image input.
            background_preprocessing: Released-checkpoint-compatible mode.
            canvas_size: Optional canvas metadata override.
            return_tensors: Only ``"pt"`` is supported.
            kwargs: Ignored compatibility kwargs.

        Returns:
            ``BatchFeature`` with ``pixel_values`` and ``canvas_size``.
        """
        del kwargs
        if return_tensors != "pt":
            raise ValueError(
                "LayoutDetrImageProcessor only supports return_tensors='pt'"
            )

        mode = normalize_background_preprocessing(background_preprocessing)
        tensors: list[Float[torch.Tensor, "channels height width"]] = []
        sizes: list[tuple[int, int]] = []
        for image in _ensure_pil_batch(images):
            sizes.append(canvas_size or image.size)
            processed = _apply_background_preprocessing(image.convert("RGB"), mode)
            processed = processed.resize(
                (self.background_size, self.background_size),
                Image.Resampling.BILINEAR,
            )
            array = np.asarray(processed, dtype=np.float32) / 255.0
            mean = np.asarray(self.image_mean, dtype=np.float32)
            std = np.asarray(self.image_std, dtype=np.float32)
            tensors.append(torch.from_numpy(((array - mean) / std).transpose(2, 0, 1)))
        return BatchFeature(
            {
                "pixel_values": torch.stack(tensors).float(),
                "canvas_size": torch.tensor(sizes, dtype=torch.long),
            }
        )

__init__

__init__(
    background_size: int = 256,
    image_mean: Sequence[float] = (0.485, 0.456, 0.406),
    image_std: Sequence[float] = (0.229, 0.224, 0.225),
    **kwargs: str | int | float | bool | None,
) -> None

Initialize image normalization settings.

Source code in models/layout-detr/src/layout_detr/image_processing_layout_detr.py
24
25
26
27
28
29
30
31
32
33
34
35
def __init__(
    self,
    background_size: int = 256,
    image_mean: Sequence[float] = (0.485, 0.456, 0.406),
    image_std: Sequence[float] = (0.229, 0.224, 0.225),
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize image normalization settings."""
    super().__init__(**kwargs)
    self.background_size = int(background_size)
    self.image_mean = tuple(float(value) for value in image_mean)
    self.image_std = tuple(float(value) for value in image_std)

from_config classmethod

from_config(
    config: LayoutDetrConfig,
) -> "LayoutDetrImageProcessor"

Build an image processor from a LayoutDETR config.

Source code in models/layout-detr/src/layout_detr/image_processing_layout_detr.py
37
38
39
40
41
42
43
44
@classmethod
def from_config(cls, config: LayoutDetrConfig) -> "LayoutDetrImageProcessor":
    """Build an image processor from a LayoutDETR config."""
    return cls(
        background_size=config.background_size,
        image_mean=config.image_mean,
        image_std=config.image_std,
    )

preprocess

preprocess(
    images: ImageInput | Sequence[ImageInput],
    *,
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
    **kwargs: str | int | float | bool | None,
) -> BatchFeature

Preprocess a background image or batch.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput]

PIL, NumPy, or torch image input.

required
background_preprocessing BackgroundPreprocessing | str

Released-checkpoint-compatible mode.

none
canvas_size tuple[int, int] | None

Optional canvas metadata override.

None
return_tensors Literal['pt']

Only "pt" is supported.

'pt'
kwargs str | int | float | bool | None

Ignored compatibility kwargs.

{}

Returns:

Type Description
BatchFeature

BatchFeature with pixel_values and canvas_size.

Source code in models/layout-detr/src/layout_detr/image_processing_layout_detr.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def preprocess(
    self,
    images: ImageInput | Sequence[ImageInput],
    *,
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
    **kwargs: str | int | float | bool | None,
) -> BatchFeature:
    """Preprocess a background image or batch.

    Args:
        images: PIL, NumPy, or torch image input.
        background_preprocessing: Released-checkpoint-compatible mode.
        canvas_size: Optional canvas metadata override.
        return_tensors: Only ``"pt"`` is supported.
        kwargs: Ignored compatibility kwargs.

    Returns:
        ``BatchFeature`` with ``pixel_values`` and ``canvas_size``.
    """
    del kwargs
    if return_tensors != "pt":
        raise ValueError(
            "LayoutDetrImageProcessor only supports return_tensors='pt'"
        )

    mode = normalize_background_preprocessing(background_preprocessing)
    tensors: list[Float[torch.Tensor, "channels height width"]] = []
    sizes: list[tuple[int, int]] = []
    for image in _ensure_pil_batch(images):
        sizes.append(canvas_size or image.size)
        processed = _apply_background_preprocessing(image.convert("RGB"), mode)
        processed = processed.resize(
            (self.background_size, self.background_size),
            Image.Resampling.BILINEAR,
        )
        array = np.asarray(processed, dtype=np.float32) / 255.0
        mean = np.asarray(self.image_mean, dtype=np.float32)
        std = np.asarray(self.image_std, dtype=np.float32)
        tensors.append(torch.from_numpy(((array - mean) / std).transpose(2, 0, 1)))
    return BatchFeature(
        {
            "pixel_values": torch.stack(tensors).float(),
            "canvas_size": torch.tensor(sizes, dtype=torch.long),
        }
    )

LayoutDetrForConditionalGeneration

Bases: PreTrainedModel

A standard PreTrainedModel wrapper for LayoutDETR forward inference.

Source code in models/layout-detr/src/layout_detr/modeling_layout_detr.py
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
class LayoutDetrForConditionalGeneration(PreTrainedModel):
    """A standard ``PreTrainedModel`` wrapper for LayoutDETR forward inference."""

    config_class = LayoutDetrConfig
    base_model_prefix = "layout_detr"
    main_input_name = "pixel_values"
    supports_gradient_checkpointing = False

    def __init__(self, config: LayoutDetrConfig) -> None:
        """Initialize LayoutDETR layers."""
        super().__init__(config)
        self._is_reference_architecture = config.architecture == "reference"
        if self._is_reference_architecture:  # pragma: no cover
            self._init_reference_layers(config)
        else:
            self._init_lightweight_layers(config)
        self.post_init()

    def _init_lightweight_layers(self, config: LayoutDetrConfig) -> None:
        self.background_encoder = nn.Sequential(
            nn.Conv2d(config.img_channels, config.hidden_dim, kernel_size=3, padding=1),
            nn.GELU(),
            nn.AdaptiveAvgPool2d(1),
            nn.Flatten(),
        )
        self.fc_z = nn.Linear(config.z_dim, config.bert_f_dim)
        self.emb_label = nn.Embedding(config.num_bbox_labels, config.bert_f_dim)
        self.text_embeddings = nn.Embedding(config.text_vocab_size, config.bert_f_dim)
        self.text_len_embeddings = nn.Embedding(
            config.max_text_length, config.bert_f_dim
        )
        self.background_proj = nn.Linear(config.hidden_dim, config.hidden_dim)
        self.fc_in = nn.Sequential(
            nn.Linear(config.bert_f_dim * 4, config.hidden_dim),
            nn.GELU(),
            nn.Linear(config.hidden_dim, config.hidden_dim),
        )
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=config.hidden_dim,
            nhead=max(1, min(8, config.hidden_dim // 8)),
            dim_feedforward=max(config.hidden_dim * 4, 64),
            batch_first=True,
        )
        self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=2)
        self.bbox_embed = nn.Sequential(
            nn.Linear(config.hidden_dim, config.hidden_dim),
            nn.GELU(),
            nn.Linear(config.hidden_dim, 4),
        )

    def _init_reference_layers(
        self, config: LayoutDetrConfig
    ) -> None:  # pragma: no cover
        self.backbone = _build_backbone(config.backbone_name)
        self.input_proj = nn.Conv2d(self.backbone.num_channels, config.hidden_dim, 1)
        self.fc_z = nn.Linear(config.z_dim * config.max_seq_length, config.bert_f_dim)
        self.emb_label = nn.Embedding(config.num_bbox_labels, config.bert_f_dim)
        self.text_encoder = _build_reference_bert_model(
            config,
            num_hidden_layers=config.bert_num_encoder_layers,
            encoder_width=config.bert_f_dim,
            add_pooling_layer=False,
            use_cross_attention=True,
            text_encoder=True,
        )
        self.enc_text_len = nn.Embedding(config.max_text_length, config.bert_f_dim)
        self.fc_in = _ReferenceMLP(
            input_dim=config.bert_f_dim * 4,
            hidden_dim=config.bert_f_dim,
            output_dim=config.hidden_dim,
            num_layers=3,
        )
        self.transformer = _DetrTransformer(
            d_model=config.hidden_dim,
            dropout=0.1,
            nhead=8,
            dim_feedforward=2048,
            num_encoder_layers=6,
            num_decoder_layers=6,
        )
        self.bbox_embed = _ReferenceMLP(
            input_dim=config.hidden_dim,
            hidden_dim=config.hidden_dim,
            output_dim=4,
            num_layers=3,
        )
        self.fc_z_rec = nn.Linear(
            config.hidden_dim, config.z_dim * config.max_seq_length
        )
        self.fc_out_cls = nn.Linear(config.hidden_dim, config.num_bbox_labels)
        self.text_decoder = _build_reference_bert_lm_head(
            config,
            num_hidden_layers=config.bert_num_decoder_layers,
            encoder_width=512,
        )
        self.fc_text_len_rec = nn.Linear(config.hidden_dim, config.max_text_length)

    def forward(
        self,
        *,
        pixel_values: Float[torch.Tensor, "batch channels height width"],
        input_ids: Int[torch.Tensor, "batch elements tokens"],
        text_attention_mask: Bool[torch.Tensor, "batch elements tokens"],
        bbox_labels: Int[torch.Tensor, "batch elements"],
        layout_mask: Bool[torch.Tensor, "batch elements"],
        latents: Float[torch.Tensor, "batch elements latent"],
        text_lengths: Int[torch.Tensor, "batch elements"] | None = None,
        return_dict: bool | None = None,
    ) -> (
        LayoutDetrModelOutput
        | tuple[
            Float[torch.Tensor, "batch elements 4"],
            Int[torch.Tensor, "batch elements"],
            Bool[torch.Tensor, "batch elements"],
        ]
    ):
        """Run the LayoutDETR conditional forward pass."""
        return_dict = (
            self.config.use_return_dict if return_dict is None else return_dict
        )
        if bbox_labels.ndim != 2:
            raise ValueError("bbox_labels must have shape (batch, elements)")

        if (
            latents.shape[:2] != bbox_labels.shape
            or latents.shape[-1] != self.config.z_dim
        ):
            raise ValueError("latents must have shape (batch, elements, z_dim)")

        if input_ids.shape[:2] != bbox_labels.shape:
            raise ValueError("input_ids must have shape (batch, elements, tokens)")

        labels = bbox_labels.to(dtype=torch.long)
        if labels.numel() and (
            int(labels.min().item()) < 0
            or int(labels.max().item()) >= self.config.num_bbox_labels
        ):
            raise ValueError("bbox_labels contain ids outside config.num_bbox_labels")

        device = labels.device
        pixel_values = pixel_values.to(device=device, dtype=self.dtype)
        latents = latents.to(device=device, dtype=self.dtype)
        input_ids = input_ids.to(device=device, dtype=torch.long)
        text_attention_mask = text_attention_mask.to(device=device, dtype=torch.bool)
        layout_mask = layout_mask.to(device=device, dtype=torch.bool)

        if self._is_reference_architecture:
            bbox, hidden = self._forward_reference(
                pixel_values=pixel_values,
                input_ids=input_ids,
                text_attention_mask=text_attention_mask,
                labels=labels,
                layout_mask=layout_mask,
                latents=latents,
                text_lengths=text_lengths,
            )
            if not return_dict:
                return bbox, labels, layout_mask
            return LayoutDetrModelOutput(
                bbox=bbox,
                labels=labels,
                mask=layout_mask,
                latents=latents,
                hidden_states=hidden,
            )

        bg = self.background_proj(self.background_encoder(pixel_values)).unsqueeze(1)
        z = self.fc_z(latents)
        label_features = self.emb_label(labels)
        text_tokens = self.text_embeddings(input_ids)
        token_mask = text_attention_mask.unsqueeze(-1).to(dtype=text_tokens.dtype)
        denom = token_mask.sum(dim=2).clamp_min(1.0)
        text_features = (text_tokens * token_mask).sum(dim=2) / denom
        lengths = text_attention_mask.sum(dim=-1).clamp_max(
            self.config.max_text_length - 1
        )
        text_len_features = self.text_len_embeddings(lengths)
        hidden = self.fc_in(
            torch.cat([z, label_features, text_features, text_len_features], dim=-1)
        )
        hidden = hidden + bg
        hidden = self.transformer(hidden, src_key_padding_mask=~layout_mask)
        bbox = torch.sigmoid(self.bbox_embed(hidden))
        if not return_dict:
            return bbox, labels, layout_mask
        return LayoutDetrModelOutput(
            bbox=bbox,
            labels=labels,
            mask=layout_mask,
            latents=latents,
            hidden_states=hidden,
        )

    def _forward_reference(  # pragma: no cover
        self,
        *,
        pixel_values: Float[torch.Tensor, "batch channels height width"],
        input_ids: Int[torch.Tensor, "batch elements tokens"],
        text_attention_mask: Bool[torch.Tensor, "batch elements tokens"],
        labels: Int[torch.Tensor, "batch elements"],
        layout_mask: Bool[torch.Tensor, "batch elements"],
        latents: Float[torch.Tensor, "batch elements latent"],
        text_lengths: Int[torch.Tensor, "batch elements"] | None = None,
    ) -> tuple[
        Float[torch.Tensor, "batch elements 4"],
        Float[torch.Tensor, "batch elements hidden"],
    ]:
        bg_nested = _NestedTensor(
            pixel_values,
            torch.zeros(
                pixel_values.shape[0],
                pixel_values.shape[2],
                pixel_values.shape[3],
                dtype=torch.bool,
                device=pixel_values.device,
            ),
        )
        bg_feat, pos = self.backbone(bg_nested)
        bg_tensor, bg_mask = bg_feat[-1].decompose()
        z0 = _normalize_2nd_moment(latents.reshape(latents.shape[0], -1))
        z = self.fc_z(z0).unsqueeze(1).expand(-1, labels.shape[1], -1)
        label_features = self.emb_label(labels)
        flat_input_ids = input_ids.reshape(-1, input_ids.shape[-1])
        flat_attention = text_attention_mask.reshape(-1, text_attention_mask.shape[-1])
        text_output = self.text_encoder(
            flat_input_ids,
            attention_mask=flat_attention,
            return_dict=True,
            mode="text",
        )
        text_features = text_output.last_hidden_state[:, 0, :].view(
            labels.shape[0], labels.shape[1], -1
        )
        if text_lengths is None:
            lengths = flat_attention.sum(dim=-1)
        else:
            lengths = text_lengths.to(device=labels.device, dtype=torch.long).reshape(
                -1
            )
        lengths = lengths.clamp_max(self.config.max_text_length - 1)
        text_len_features = self.enc_text_len(lengths.view(labels.shape))
        hidden = torch.cat(
            [z, label_features, text_features, text_len_features], dim=-1
        )
        hidden = torch.relu(self.fc_in(hidden)).permute(1, 0, 2)
        hidden = self.transformer(
            src=self.input_proj(bg_tensor),
            mask=bg_mask,
            pos_embed=pos[-1],
            tgt=hidden,
            tgt_key_padding_mask=~layout_mask,
        )[0]
        return torch.sigmoid(self.bbox_embed(hidden)), hidden

__init__

__init__(config: LayoutDetrConfig) -> None

Initialize LayoutDETR layers.

Source code in models/layout-detr/src/layout_detr/modeling_layout_detr.py
59
60
61
62
63
64
65
66
67
def __init__(self, config: LayoutDetrConfig) -> None:
    """Initialize LayoutDETR layers."""
    super().__init__(config)
    self._is_reference_architecture = config.architecture == "reference"
    if self._is_reference_architecture:  # pragma: no cover
        self._init_reference_layers(config)
    else:
        self._init_lightweight_layers(config)
    self.post_init()

forward

forward(
    *,
    pixel_values: Float[
        Tensor, "batch channels height width"
    ],
    input_ids: Int[Tensor, "batch elements tokens"],
    text_attention_mask: Bool[
        Tensor, "batch elements tokens"
    ],
    bbox_labels: Int[Tensor, "batch elements"],
    layout_mask: Bool[Tensor, "batch elements"],
    latents: Float[Tensor, "batch elements latent"],
    text_lengths: Int[Tensor, "batch elements"]
    | None = None,
    return_dict: bool | None = None,
) -> (
    LayoutDetrModelOutput
    | tuple[
        Float[torch.Tensor, "batch elements 4"],
        Int[torch.Tensor, "batch elements"],
        Bool[torch.Tensor, "batch elements"],
    ]
)

Run the LayoutDETR conditional forward pass.

Source code in models/layout-detr/src/layout_detr/modeling_layout_detr.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
def forward(
    self,
    *,
    pixel_values: Float[torch.Tensor, "batch channels height width"],
    input_ids: Int[torch.Tensor, "batch elements tokens"],
    text_attention_mask: Bool[torch.Tensor, "batch elements tokens"],
    bbox_labels: Int[torch.Tensor, "batch elements"],
    layout_mask: Bool[torch.Tensor, "batch elements"],
    latents: Float[torch.Tensor, "batch elements latent"],
    text_lengths: Int[torch.Tensor, "batch elements"] | None = None,
    return_dict: bool | None = None,
) -> (
    LayoutDetrModelOutput
    | tuple[
        Float[torch.Tensor, "batch elements 4"],
        Int[torch.Tensor, "batch elements"],
        Bool[torch.Tensor, "batch elements"],
    ]
):
    """Run the LayoutDETR conditional forward pass."""
    return_dict = (
        self.config.use_return_dict if return_dict is None else return_dict
    )
    if bbox_labels.ndim != 2:
        raise ValueError("bbox_labels must have shape (batch, elements)")

    if (
        latents.shape[:2] != bbox_labels.shape
        or latents.shape[-1] != self.config.z_dim
    ):
        raise ValueError("latents must have shape (batch, elements, z_dim)")

    if input_ids.shape[:2] != bbox_labels.shape:
        raise ValueError("input_ids must have shape (batch, elements, tokens)")

    labels = bbox_labels.to(dtype=torch.long)
    if labels.numel() and (
        int(labels.min().item()) < 0
        or int(labels.max().item()) >= self.config.num_bbox_labels
    ):
        raise ValueError("bbox_labels contain ids outside config.num_bbox_labels")

    device = labels.device
    pixel_values = pixel_values.to(device=device, dtype=self.dtype)
    latents = latents.to(device=device, dtype=self.dtype)
    input_ids = input_ids.to(device=device, dtype=torch.long)
    text_attention_mask = text_attention_mask.to(device=device, dtype=torch.bool)
    layout_mask = layout_mask.to(device=device, dtype=torch.bool)

    if self._is_reference_architecture:
        bbox, hidden = self._forward_reference(
            pixel_values=pixel_values,
            input_ids=input_ids,
            text_attention_mask=text_attention_mask,
            labels=labels,
            layout_mask=layout_mask,
            latents=latents,
            text_lengths=text_lengths,
        )
        if not return_dict:
            return bbox, labels, layout_mask
        return LayoutDetrModelOutput(
            bbox=bbox,
            labels=labels,
            mask=layout_mask,
            latents=latents,
            hidden_states=hidden,
        )

    bg = self.background_proj(self.background_encoder(pixel_values)).unsqueeze(1)
    z = self.fc_z(latents)
    label_features = self.emb_label(labels)
    text_tokens = self.text_embeddings(input_ids)
    token_mask = text_attention_mask.unsqueeze(-1).to(dtype=text_tokens.dtype)
    denom = token_mask.sum(dim=2).clamp_min(1.0)
    text_features = (text_tokens * token_mask).sum(dim=2) / denom
    lengths = text_attention_mask.sum(dim=-1).clamp_max(
        self.config.max_text_length - 1
    )
    text_len_features = self.text_len_embeddings(lengths)
    hidden = self.fc_in(
        torch.cat([z, label_features, text_features, text_len_features], dim=-1)
    )
    hidden = hidden + bg
    hidden = self.transformer(hidden, src_key_padding_mask=~layout_mask)
    bbox = torch.sigmoid(self.bbox_embed(hidden))
    if not return_dict:
        return bbox, labels, layout_mask
    return LayoutDetrModelOutput(
        bbox=bbox,
        labels=labels,
        mask=layout_mask,
        latents=latents,
        hidden_states=hidden,
    )

LayoutDetrModelOutput dataclass

Bases: ModelOutput

Raw LayoutDETR model output.

Source code in models/layout-detr/src/layout_detr/modeling_layout_detr.py
36
37
38
39
40
41
42
43
44
45
46
47
48
@dataclass
class LayoutDetrModelOutput(ModelOutput):
    """Raw LayoutDETR model output."""

    bbox: Float[torch.Tensor, "batch elements 4"]
    labels: Int[torch.Tensor, "batch elements"] = cast(
        Int[torch.Tensor, "batch elements"], None
    )
    mask: Bool[torch.Tensor, "batch elements"] = cast(
        Bool[torch.Tensor, "batch elements"], None
    )
    latents: Float[torch.Tensor, "batch elements latent"] | None = None
    hidden_states: Float[torch.Tensor, "batch elements hidden"] | None = None

LayoutDetrPipeline

Bases: LayoutGenerationPipeline

Transformers-side LayoutDETR pipeline.

Source code in models/layout-detr/src/layout_detr/pipeline_layout_detr.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
class LayoutDetrPipeline(LayoutGenerationPipeline):
    """Transformers-side LayoutDETR pipeline."""

    config_class: ClassVar[type[PretrainedConfig]] = LayoutDetrConfig
    component_specs: ClassVar[dict[str, PipelineComponentSpec]] = (
        model_processor_component_specs(
            model_loader=_load_model_component,
            processor_loader=_load_processor_component,
        )
    )

    config: LayoutDetrConfig
    model: LayoutDetrForConditionalGeneration
    processor: LayoutDetrProcessor

    def __init__(
        self,
        model: LayoutDetrForConditionalGeneration,
        processor: LayoutDetrProcessor | None = None,
        config: LayoutDetrConfig | None = None,
        device: str | torch.device | None = None,
    ) -> None:
        """Initialize a LayoutDETR pipeline."""
        super().__init__(config or model.config)
        self.config = config or model.config
        self.model = model
        self.processor = processor or LayoutDetrProcessor(config=self.config)
        self.model.eval()
        if device is not None:
            self.to(device)

    @classmethod
    def _from_pretrained_components(
        cls,
        *,
        config: PretrainedConfig,
        components: Mapping[str, PipelineComponent | None],
    ) -> "LayoutDetrPipeline":
        """Build a pipeline from saved components."""
        return cls(
            config=cast(LayoutDetrConfig, config),
            model=cast(LayoutDetrForConditionalGeneration, components["model"]),
            processor=cast(LayoutDetrProcessor, components["processor"]),
        )

    @torch.no_grad()
    def __call__(
        self,
        images: ImageInput
        | Sequence[ImageInput]
        | Shaped[torch.Tensor, "..."]
        | None = None,
        *,
        content: Mapping[
            str,
            ImageInput
            | Sequence[ImageInput]
            | Sequence[Sequence[str]]
            | Sequence[str]
            | Sequence[Sequence[int | str]]
            | Sequence[int | str],
        ]
        | None = None,
        prompt: str | Sequence[str] | None = None,
        texts: Sequence[Sequence[str]] | Sequence[str] | None = None,
        batch_size: int = 1,
        seed: int | None = None,
        generator: torch.Generator | None = None,
        condition_type: ConditionType | str = ConditionType.content_image,
        labels: Int[torch.Tensor, "batch elements"]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"] | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Sequence[Sequence[bool]]
        | Sequence[bool]
        | None = None,
        num_elements: int | Sequence[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_intermediates: bool = False,
        background_preprocessing: BackgroundPreprocessing
        | str = BackgroundPreprocessing.none,
        out_jittering_strength: float = 0.0,
        out_postprocessing: PostprocessingMode | str = PostprocessingMode.none,
        latents: Float[torch.Tensor, "batch elements latent"] | None = None,
    ) -> LayoutGenerationOutput:
        """Generate layouts for a background image and per-element text labels."""
        normalize_condition_type(condition_type)
        if bbox is not None:
            raise ValueError("LayoutDETR does not condition on existing bbox")

        if num_elements is not None:
            raise ValueError("LayoutDETR infers num_elements from labels/mask")

        if box_format != BoxFormat.xywh and box_format != "xywh":
            raise ValueError("LayoutDETR outputs normalized xywh boxes only")

        if not normalized:
            raise ValueError("LayoutDETR expects normalized public boxes")

        if num_inference_steps is not None:
            raise ValueError("LayoutDETR is a single forward pass, not iterative")

        encoded = self.processor(
            images=images,
            content=content,
            prompt=prompt,
            texts=texts,
            labels=labels,
            mask=mask,
            condition_type=str(ConditionType.content_image),
            background_preprocessing=background_preprocessing,
            batch_size=batch_size,
            canvas_size=canvas_size,
        )
        device = self.device or next(self.model.parameters()).device
        encoded = encoded.to(device)
        batch, elements = encoded["bbox_labels"].shape
        runtime_generator = self.prepare_generator(
            generator=generator,
            seed=seed,
            device=device,
        )
        if latents is None:
            latents = torch.randn(
                (batch, elements, self.config.z_dim),
                generator=runtime_generator,
                device=device,
            )
        else:
            latents = latents.to(device=device)
        model_output = self.model(
            pixel_values=encoded["pixel_values"],
            input_ids=encoded["input_ids"],
            text_attention_mask=encoded["text_attention_mask"],
            bbox_labels=encoded["bbox_labels"],
            layout_mask=encoded["layout_mask"],
            latents=latents,
            text_lengths=encoded["text_lengths"],
        )
        bbox_out = apply_postprocessing(
            model_output.bbox,
            model_output.mask,
            mode=out_postprocessing,
            jitter_strength=out_jittering_strength,
            generator=runtime_generator,
        )
        intermediates = {
            "latents": latents.detach().cpu(),
            "texts": encoded["texts"],
            "background_preprocessing": str(background_preprocessing),
            "postprocessing": str(out_postprocessing),
        }
        return cast(
            LayoutGenerationOutput,
            self.processor.post_process_layouts(
                bbox_out,
                model_output.labels,
                model_output.mask,
                output_type=output_type,
                return_intermediates=return_intermediates,
                intermediates=intermediates,
            ),
        )

__init__

__init__(
    model: LayoutDetrForConditionalGeneration,
    processor: LayoutDetrProcessor | None = None,
    config: LayoutDetrConfig | None = None,
    device: str | device | None = None,
) -> None

Initialize a LayoutDETR pipeline.

Source code in models/layout-detr/src/layout_detr/pipeline_layout_detr.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def __init__(
    self,
    model: LayoutDetrForConditionalGeneration,
    processor: LayoutDetrProcessor | None = None,
    config: LayoutDetrConfig | None = None,
    device: str | torch.device | None = None,
) -> None:
    """Initialize a LayoutDETR pipeline."""
    super().__init__(config or model.config)
    self.config = config or model.config
    self.model = model
    self.processor = processor or LayoutDetrProcessor(config=self.config)
    self.model.eval()
    if device is not None:
        self.to(device)

__call__

__call__(
    images: ImageInput
    | Sequence[ImageInput]
    | Shaped[Tensor, "..."]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Sequence[Sequence[str]]
        | Sequence[str]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    texts: Sequence[Sequence[str]]
    | Sequence[str]
    | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.content_image,
    labels: Int[Tensor, "batch elements"]
    | Sequence[Sequence[int | str]]
    | Sequence[int | str]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"] | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    num_elements: int
    | Sequence[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    out_jittering_strength: float = 0.0,
    out_postprocessing: PostprocessingMode
    | str = PostprocessingMode.none,
    latents: Float[Tensor, "batch elements latent"]
    | None = None,
) -> LayoutGenerationOutput

Generate layouts for a background image and per-element text labels.

Source code in models/layout-detr/src/layout_detr/pipeline_layout_detr.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
@torch.no_grad()
def __call__(
    self,
    images: ImageInput
    | Sequence[ImageInput]
    | Shaped[torch.Tensor, "..."]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Sequence[Sequence[str]]
        | Sequence[str]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    texts: Sequence[Sequence[str]] | Sequence[str] | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: torch.Generator | None = None,
    condition_type: ConditionType | str = ConditionType.content_image,
    labels: Int[torch.Tensor, "batch elements"]
    | Sequence[Sequence[int | str]]
    | Sequence[int | str]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"] | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    num_elements: int | Sequence[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    out_jittering_strength: float = 0.0,
    out_postprocessing: PostprocessingMode | str = PostprocessingMode.none,
    latents: Float[torch.Tensor, "batch elements latent"] | None = None,
) -> LayoutGenerationOutput:
    """Generate layouts for a background image and per-element text labels."""
    normalize_condition_type(condition_type)
    if bbox is not None:
        raise ValueError("LayoutDETR does not condition on existing bbox")

    if num_elements is not None:
        raise ValueError("LayoutDETR infers num_elements from labels/mask")

    if box_format != BoxFormat.xywh and box_format != "xywh":
        raise ValueError("LayoutDETR outputs normalized xywh boxes only")

    if not normalized:
        raise ValueError("LayoutDETR expects normalized public boxes")

    if num_inference_steps is not None:
        raise ValueError("LayoutDETR is a single forward pass, not iterative")

    encoded = self.processor(
        images=images,
        content=content,
        prompt=prompt,
        texts=texts,
        labels=labels,
        mask=mask,
        condition_type=str(ConditionType.content_image),
        background_preprocessing=background_preprocessing,
        batch_size=batch_size,
        canvas_size=canvas_size,
    )
    device = self.device or next(self.model.parameters()).device
    encoded = encoded.to(device)
    batch, elements = encoded["bbox_labels"].shape
    runtime_generator = self.prepare_generator(
        generator=generator,
        seed=seed,
        device=device,
    )
    if latents is None:
        latents = torch.randn(
            (batch, elements, self.config.z_dim),
            generator=runtime_generator,
            device=device,
        )
    else:
        latents = latents.to(device=device)
    model_output = self.model(
        pixel_values=encoded["pixel_values"],
        input_ids=encoded["input_ids"],
        text_attention_mask=encoded["text_attention_mask"],
        bbox_labels=encoded["bbox_labels"],
        layout_mask=encoded["layout_mask"],
        latents=latents,
        text_lengths=encoded["text_lengths"],
    )
    bbox_out = apply_postprocessing(
        model_output.bbox,
        model_output.mask,
        mode=out_postprocessing,
        jitter_strength=out_jittering_strength,
        generator=runtime_generator,
    )
    intermediates = {
        "latents": latents.detach().cpu(),
        "texts": encoded["texts"],
        "background_preprocessing": str(background_preprocessing),
        "postprocessing": str(out_postprocessing),
    }
    return cast(
        LayoutGenerationOutput,
        self.processor.post_process_layouts(
            bbox_out,
            model_output.labels,
            model_output.mask,
            output_type=output_type,
            return_intermediates=return_intermediates,
            intermediates=intermediates,
        ),
    )

PostprocessingMode

Bases: StrEnum

Supported LayoutDETR postprocessing modes.

Source code in models/layout-detr/src/layout_detr/postprocessing.py
13
14
15
16
17
18
class PostprocessingMode(StrEnum):
    """Supported LayoutDETR postprocessing modes."""

    none = auto()
    horizontal_center_aligned = auto()
    horizontal_left_aligned = auto()

LayoutDetrProcessor

Bases: ProcessorMixin

Normalize LayoutDETR image, text, label, and mask payloads.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
class LayoutDetrProcessor(ProcessorMixin):
    """Normalize LayoutDETR image, text, label, and mask payloads."""

    attributes = ["image_processor"]
    image_processor_class = "LayoutDetrImageProcessor"
    tokenizer_class = "BertTokenizerFast"
    config_name = "processor_config.json"

    def __init__(
        self,
        *,
        image_processor: LayoutDetrImageProcessor | None = None,
        config: LayoutDetrConfig,
        id2label: Mapping[int | str, str] | None = None,
    ) -> None:
        """Initialize the processor."""
        self.config = config
        self.image_processor = image_processor or LayoutDetrImageProcessor.from_config(
            self.config
        )
        label_source = (
            id2label
            if id2label is not None
            else cast(dict[int, str], self.config.id2label)
        )
        self.id2label = {
            int(k): v for k, v in cast(Mapping[int | str, str], label_source).items()
        }
        self.label2id = {v: k for k, v in self.id2label.items()}
        self.chat_template = None

    def save_pretrained(
        self,
        save_directory: str | Path,
        push_to_hub: bool = False,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Save processor metadata and image-processor config."""
        del push_to_hub, kwargs
        root = _processor_root(save_directory)
        root.mkdir(parents=True, exist_ok=True)
        _write_processor_payload(root / self.config_name, self._metadata_payload())
        self.image_processor.save_pretrained(root)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | PathLike[str],
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        subfolder: str | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> "LayoutDetrProcessor":
        """Load processor metadata from a checkpoint directory."""
        del cache_dir, force_download, local_files_only, token, revision, kwargs
        root = _processor_root(pretrained_model_name_or_path, subfolder=subfolder)
        payload = _read_processor_payload(root / cls.config_name)
        config_payload = payload.get("config", {})
        if not isinstance(config_payload, dict):
            raise TypeError("processor config payload must be a dictionary")

        id2label_payload = payload.get("id2label")
        if id2label_payload is not None and not isinstance(id2label_payload, dict):
            raise TypeError("processor id2label payload must be a dictionary")

        config = LayoutDetrConfig.from_dict(config_payload)
        image_processor = LayoutDetrImageProcessor.from_pretrained(root)
        return cls(
            image_processor=image_processor,
            config=config,
            id2label=cast(Mapping[int | str, str] | None, id2label_payload),
        )

    def _metadata_payload(
        self,
    ) -> dict[
        str,
        Mapping[str, Shaped[torch.Tensor, "..."] | int | str | float | bool | None]
        | dict[int, str]
        | str,
    ]:
        return {
            "config": self.config.to_dict(),
            "id2label": self.id2label,
            "processor_class": self.__class__.__name__,
        }

    def __call__(
        self,
        *,
        images: ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch channels height width"]
        | None = None,
        content: Mapping[
            str,
            ImageInput
            | Sequence[ImageInput]
            | Sequence[Sequence[str]]
            | Sequence[str]
            | Sequence[Sequence[int | str]]
            | Sequence[int | str],
        ]
        | None = None,
        prompt: str | Sequence[str] | None = None,
        texts: Sequence[Sequence[str]] | Sequence[str] | None = None,
        labels: Int[torch.Tensor, "batch elements"]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Sequence[Sequence[bool]]
        | Sequence[bool]
        | None = None,
        condition_type: str = "content_image",
        background_preprocessing: BackgroundPreprocessing
        | str = BackgroundPreprocessing.none,
        batch_size: int = 1,
        return_tensors: Literal["pt"] = "pt",
        canvas_size: tuple[int, int] | None = None,
    ) -> BatchEncoding:
        """Encode public inputs for the LayoutDETR model."""
        if return_tensors != "pt":
            raise ValueError("LayoutDetrProcessor only supports return_tensors='pt'")

        if condition_type not in {"content_image", "content", "image", "visual"}:
            raise NotImplementedError(
                "LayoutDETR supports only condition_type='content_image'"
            )

        content = dict(content or {})
        resolved_images = images or content.get("image") or content.get("images")
        if resolved_images is None:
            raise ValueError("LayoutDETR requires images or content['image']")

        resolved_texts = texts if texts is not None else content.get("texts")
        if resolved_texts is None:
            if prompt is not None:
                raise ValueError(
                    "LayoutDETR requires per-element texts; prompt alone is not supported"
                )

            raise ValueError("LayoutDETR requires per-element texts")

        resolved_labels = labels if labels is not None else content.get("labels")
        if resolved_labels is None:
            raise ValueError("LayoutDETR requires per-element labels")

        text_rows = _normalize_text_rows(
            cast(Sequence[Sequence[str]] | Sequence[str], resolved_texts)
        )
        label_rows = self._normalize_label_rows(
            cast(
                Int[torch.Tensor, "batch elements"]
                | Sequence[Sequence[int | str]]
                | Sequence[int | str],
                resolved_labels,
            )
        )
        if len(text_rows) != len(label_rows):
            raise ValueError("texts and labels must have the same batch size")

        if len(text_rows) == 1 and batch_size > 1:
            text_rows = text_rows * batch_size
            label_rows = label_rows * batch_size
        layout_mask = _normalize_mask_rows(mask, label_rows)
        image_features = self.image_processor.preprocess(
            resolved_images,
            background_preprocessing=background_preprocessing,
            canvas_size=canvas_size,
            return_tensors=return_tensors,
        )
        input_ids, text_attention_mask = self._tokenize_rows(text_rows)
        bbox_labels, padded_mask, padded_texts = self._pad_layout_rows(
            text_rows,
            label_rows,
            layout_mask,
        )
        text_lengths = _pad_text_lengths(text_rows, self.config.max_seq_length)
        image_batch = image_features["pixel_values"].shape[0]
        if image_batch == 1 and bbox_labels.shape[0] > 1:
            image_features["pixel_values"] = image_features["pixel_values"].expand(
                bbox_labels.shape[0], -1, -1, -1
            )
            image_features["canvas_size"] = image_features["canvas_size"].expand(
                bbox_labels.shape[0], -1
            )
        elif image_batch != bbox_labels.shape[0]:
            raise ValueError("image batch size must match texts/labels batch size")

        return BatchEncoding(
            {
                "pixel_values": image_features["pixel_values"],
                "canvas_size": image_features["canvas_size"],
                "input_ids": input_ids,
                "text_attention_mask": text_attention_mask,
                "bbox_labels": bbox_labels,
                "layout_mask": padded_mask,
                "texts": padded_texts,
                "text_lengths": text_lengths,
            }
        )

    def post_process_layouts(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"],
        *,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_intermediates: bool = False,
        intermediates: dict[str, Shaped[torch.Tensor, "..."] | list[list[str]] | str]
        | None = None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | dict[int, str]
            | dict[str, Shaped[torch.Tensor, "..."] | list[list[str]] | str]
            | None,
        ]
    ):
        """Return generated boxes in the shared layout output schema."""
        payload = LayoutGenerationOutput(
            bbox=bbox,
            labels=labels,
            mask=mask,
            id2label=self.id2label,
            intermediates=intermediates if return_intermediates else None,
        )
        if output_type == "dict":
            return dict(payload)
        if output_type != "dataclass":
            raise ValueError(f"Unsupported output_type: {output_type}")

        return payload

    def _normalize_label_rows(
        self,
        labels: Int[torch.Tensor, "batch elements"]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str],
    ) -> list[list[int]]:
        if isinstance(labels, torch.Tensor):
            tensor = labels.detach().cpu().long()
            if tensor.ndim == 1:
                tensor = tensor.unsqueeze(0)
            return [[int(value) for value in row] for row in tensor.tolist()]
        rows = _normalize_label_sequence(
            cast(Sequence[Sequence[int | str]] | Sequence[int | str], labels)
        )
        return [[self._label_to_id(label) for label in row] for row in rows]

    def _label_to_id(self, label: int | str) -> int:
        if isinstance(label, int):
            if label < 0 or label >= len(self.id2label):
                raise ValueError(f"Unknown Ad Banner label id: {label}")

            return label
        try:
            return self.label2id[label]
        except KeyError as exc:
            raise ValueError(f"Unknown Ad Banner label: {label}") from exc

    def _tokenize_rows(
        self,
        text_rows: list[list[str]],
    ) -> tuple[
        Int[torch.Tensor, "batch elements tokens"],
        Bool[torch.Tensor, "batch elements tokens"],
    ]:
        batch_ids = []
        batch_mask = []
        for row in text_rows:
            ids_row = []
            mask_row = []
            for text in row[: self.config.max_seq_length]:
                token_ids = _hash_token_ids(
                    text, self.config.max_text_length, self.config.text_vocab_size
                )
                ids_row.append(token_ids)
                mask_row.append([token_id != 0 for token_id in token_ids])
            while len(ids_row) < self.config.max_seq_length:
                ids_row.append([0] * self.config.max_text_length)
                mask_row.append([False] * self.config.max_text_length)
            batch_ids.append(ids_row)
            batch_mask.append(mask_row)
        return (
            torch.tensor(batch_ids, dtype=torch.long),
            torch.tensor(batch_mask, dtype=torch.bool),
        )

    def _pad_layout_rows(
        self,
        text_rows: list[list[str]],
        label_rows: list[list[int]],
        mask_rows: list[list[bool]],
    ) -> tuple[
        Int[torch.Tensor, "batch elements"],
        Bool[torch.Tensor, "batch elements"],
        list[list[str]],
    ]:
        labels = []
        masks = []
        texts = []
        max_len = self.config.max_seq_length
        for text_row, label_row, mask_row in zip(
            text_rows,
            label_rows,
            mask_rows,
            strict=True,
        ):
            if len(label_row) > max_len:
                raise ValueError(f"LayoutDETR supports at most {max_len} elements")

            pad = max_len - len(label_row)
            labels.append(label_row + [self.config.pad_label_id] * pad)
            masks.append(mask_row + [False] * pad)
            texts.append(text_row + [""] * pad)
        return (
            torch.tensor(labels, dtype=torch.long),
            torch.tensor(masks, dtype=torch.bool),
            texts,
        )

__init__

__init__(
    *,
    image_processor: LayoutDetrImageProcessor | None = None,
    config: LayoutDetrConfig,
    id2label: Mapping[int | str, str] | None = None,
) -> None

Initialize the processor.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def __init__(
    self,
    *,
    image_processor: LayoutDetrImageProcessor | None = None,
    config: LayoutDetrConfig,
    id2label: Mapping[int | str, str] | None = None,
) -> None:
    """Initialize the processor."""
    self.config = config
    self.image_processor = image_processor or LayoutDetrImageProcessor.from_config(
        self.config
    )
    label_source = (
        id2label
        if id2label is not None
        else cast(dict[int, str], self.config.id2label)
    )
    self.id2label = {
        int(k): v for k, v in cast(Mapping[int | str, str], label_source).items()
    }
    self.label2id = {v: k for k, v in self.id2label.items()}
    self.chat_template = None

save_pretrained

save_pretrained(
    save_directory: str | Path,
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> None

Save processor metadata and image-processor config.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
58
59
60
61
62
63
64
65
66
67
68
69
def save_pretrained(
    self,
    save_directory: str | Path,
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Save processor metadata and image-processor config."""
    del push_to_hub, kwargs
    root = _processor_root(save_directory)
    root.mkdir(parents=True, exist_ok=True)
    _write_processor_payload(root / self.config_name, self._metadata_payload())
    self.image_processor.save_pretrained(root)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    subfolder: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> "LayoutDetrProcessor"

Load processor metadata from a checkpoint directory.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    subfolder: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> "LayoutDetrProcessor":
    """Load processor metadata from a checkpoint directory."""
    del cache_dir, force_download, local_files_only, token, revision, kwargs
    root = _processor_root(pretrained_model_name_or_path, subfolder=subfolder)
    payload = _read_processor_payload(root / cls.config_name)
    config_payload = payload.get("config", {})
    if not isinstance(config_payload, dict):
        raise TypeError("processor config payload must be a dictionary")

    id2label_payload = payload.get("id2label")
    if id2label_payload is not None and not isinstance(id2label_payload, dict):
        raise TypeError("processor id2label payload must be a dictionary")

    config = LayoutDetrConfig.from_dict(config_payload)
    image_processor = LayoutDetrImageProcessor.from_pretrained(root)
    return cls(
        image_processor=image_processor,
        config=config,
        id2label=cast(Mapping[int | str, str] | None, id2label_payload),
    )

__call__

__call__(
    *,
    images: ImageInput
    | Sequence[ImageInput]
    | Float[Tensor, "batch channels height width"]
    | None = None,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Sequence[Sequence[str]]
        | Sequence[str]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    texts: Sequence[Sequence[str]]
    | Sequence[str]
    | None = None,
    labels: Int[Tensor, "batch elements"]
    | Sequence[Sequence[int | str]]
    | Sequence[int | str]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    condition_type: str = "content_image",
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    batch_size: int = 1,
    return_tensors: Literal["pt"] = "pt",
    canvas_size: tuple[int, int] | None = None,
) -> BatchEncoding

Encode public inputs for the LayoutDETR model.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
def __call__(
    self,
    *,
    images: ImageInput
    | Sequence[ImageInput]
    | Float[torch.Tensor, "batch channels height width"]
    | None = None,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Sequence[Sequence[str]]
        | Sequence[str]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    texts: Sequence[Sequence[str]] | Sequence[str] | None = None,
    labels: Int[torch.Tensor, "batch elements"]
    | Sequence[Sequence[int | str]]
    | Sequence[int | str]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    condition_type: str = "content_image",
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    batch_size: int = 1,
    return_tensors: Literal["pt"] = "pt",
    canvas_size: tuple[int, int] | None = None,
) -> BatchEncoding:
    """Encode public inputs for the LayoutDETR model."""
    if return_tensors != "pt":
        raise ValueError("LayoutDetrProcessor only supports return_tensors='pt'")

    if condition_type not in {"content_image", "content", "image", "visual"}:
        raise NotImplementedError(
            "LayoutDETR supports only condition_type='content_image'"
        )

    content = dict(content or {})
    resolved_images = images or content.get("image") or content.get("images")
    if resolved_images is None:
        raise ValueError("LayoutDETR requires images or content['image']")

    resolved_texts = texts if texts is not None else content.get("texts")
    if resolved_texts is None:
        if prompt is not None:
            raise ValueError(
                "LayoutDETR requires per-element texts; prompt alone is not supported"
            )

        raise ValueError("LayoutDETR requires per-element texts")

    resolved_labels = labels if labels is not None else content.get("labels")
    if resolved_labels is None:
        raise ValueError("LayoutDETR requires per-element labels")

    text_rows = _normalize_text_rows(
        cast(Sequence[Sequence[str]] | Sequence[str], resolved_texts)
    )
    label_rows = self._normalize_label_rows(
        cast(
            Int[torch.Tensor, "batch elements"]
            | Sequence[Sequence[int | str]]
            | Sequence[int | str],
            resolved_labels,
        )
    )
    if len(text_rows) != len(label_rows):
        raise ValueError("texts and labels must have the same batch size")

    if len(text_rows) == 1 and batch_size > 1:
        text_rows = text_rows * batch_size
        label_rows = label_rows * batch_size
    layout_mask = _normalize_mask_rows(mask, label_rows)
    image_features = self.image_processor.preprocess(
        resolved_images,
        background_preprocessing=background_preprocessing,
        canvas_size=canvas_size,
        return_tensors=return_tensors,
    )
    input_ids, text_attention_mask = self._tokenize_rows(text_rows)
    bbox_labels, padded_mask, padded_texts = self._pad_layout_rows(
        text_rows,
        label_rows,
        layout_mask,
    )
    text_lengths = _pad_text_lengths(text_rows, self.config.max_seq_length)
    image_batch = image_features["pixel_values"].shape[0]
    if image_batch == 1 and bbox_labels.shape[0] > 1:
        image_features["pixel_values"] = image_features["pixel_values"].expand(
            bbox_labels.shape[0], -1, -1, -1
        )
        image_features["canvas_size"] = image_features["canvas_size"].expand(
            bbox_labels.shape[0], -1
        )
    elif image_batch != bbox_labels.shape[0]:
        raise ValueError("image batch size must match texts/labels batch size")

    return BatchEncoding(
        {
            "pixel_values": image_features["pixel_values"],
            "canvas_size": image_features["canvas_size"],
            "input_ids": input_ids,
            "text_attention_mask": text_attention_mask,
            "bbox_labels": bbox_labels,
            "layout_mask": padded_mask,
            "texts": padded_texts,
            "text_lengths": text_lengths,
        }
    )

post_process_layouts

post_process_layouts(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"],
    *,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    intermediates: dict[
        str, Shaped[Tensor, "..."] | list[list[str]] | str
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | list[list[str]]
            | str,
        ]
        | None,
    ]
)

Return generated boxes in the shared layout output schema.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
def post_process_layouts(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"],
    *,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    intermediates: dict[str, Shaped[torch.Tensor, "..."] | list[list[str]] | str]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[str, Shaped[torch.Tensor, "..."] | list[list[str]] | str]
        | None,
    ]
):
    """Return generated boxes in the shared layout output schema."""
    payload = LayoutGenerationOutput(
        bbox=bbox,
        labels=labels,
        mask=mask,
        id2label=self.id2label,
        intermediates=intermediates if return_intermediates else None,
    )
    if output_type == "dict":
        return dict(payload)
    if output_type != "dataclass":
        raise ValueError(f"Unsupported output_type: {output_type}")

    return payload

id2label_for_ad_banner

id2label_for_ad_banner() -> dict[int, str]

Return the LayoutDETR Ad Banner label vocabulary.

Source code in models/layout-detr/src/layout_detr/datasets.py
43
44
45
def id2label_for_ad_banner() -> dict[int, str]:
    """Return the LayoutDETR Ad Banner label vocabulary."""
    return dict(enumerate(AD_BANNER_LABELS))

configuration_layout_detr

Configuration for the Transformers-style LayoutDETR generator.

BackgroundPreprocessing

Bases: StrEnum

Supported public background preprocessing modes.

Source code in models/layout-detr/src/layout_detr/configuration_layout_detr.py
14
15
16
17
18
19
20
21
class BackgroundPreprocessing(StrEnum):
    """Supported public background preprocessing modes."""

    none = auto()
    resize_256 = "256"
    resize_128 = "128"
    blur = auto()
    edge = auto()

LayoutDetrConfig

Bases: PretrainedConfig

Configuration for LayoutDETR model, processor, and pipeline.

Source code in models/layout-detr/src/layout_detr/configuration_layout_detr.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
class LayoutDetrConfig(PretrainedConfig):
    """Configuration for LayoutDETR model, processor, and pipeline."""

    model_type = "layout-detr"

    def __init__(
        self,
        *,
        dataset_name: str = "ad_banner",
        id2label: Mapping[int | str, str] | None = None,
        max_seq_length: int = 9,
        z_dim: int = 4,
        img_channels: int = 3,
        img_height: int = 256,
        img_width: int = 256,
        background_size: int = 256,
        hidden_dim: int = 256,
        bert_f_dim: int = 768,
        bert_num_encoder_layers: int = 12,
        bert_num_decoder_layers: int = 2,
        bert_num_heads: int = 4,
        max_text_length: int = 256,
        text_vocab_size: int = 30_522,
        med_config: Mapping[str, LayoutDetrMetadataValue] | None = None,
        backbone_name: str = "resnet50",
        image_mean: Sequence[float] = (0.485, 0.456, 0.406),
        image_std: Sequence[float] = (0.229, 0.224, 0.225),
        architecture: Literal["lightweight", "reference"] = "lightweight",
        model_subfolder: str = "model",
        processor_subfolder: str = "processor",
        original_training_options: Mapping[str, LayoutDetrMetadataValue] | None = None,
        conversion_report: Mapping[str, LayoutDetrMetadataValue] | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize LayoutDETR configuration."""
        raw_id2label = id2label or DEFAULT_ID2LABEL
        normalized_id2label = {int(key): value for key, value in raw_id2label.items()}
        super().__init__(id2label=normalized_id2label, **kwargs)  # ty: ignore[invalid-argument-type]

        self.dataset_name = dataset_name
        self.id2label = normalized_id2label
        self.label2id = {value: key for key, value in self.id2label.items()}
        self.max_seq_length = int(max_seq_length)
        self.z_dim = int(z_dim)
        self.img_channels = int(img_channels)
        self.img_height = int(img_height)
        self.img_width = int(img_width)
        self.background_size = int(background_size)

        self.hidden_dim = int(hidden_dim)
        self.bert_f_dim = int(bert_f_dim)
        self.bert_num_encoder_layers = int(bert_num_encoder_layers)
        self.bert_num_decoder_layers = int(bert_num_decoder_layers)
        self.bert_num_heads = int(bert_num_heads)
        self.max_text_length = int(max_text_length)
        self.text_vocab_size = int(text_vocab_size)

        self.med_config = dict(med_config or {})
        self.backbone_name = backbone_name
        self.image_mean = tuple(float(value) for value in image_mean)
        self.image_std = tuple(float(value) for value in image_std)
        self.architecture = architecture
        self.model_subfolder = model_subfolder
        self.processor_subfolder = processor_subfolder
        self.original_training_options = dict(original_training_options or {})
        self.conversion_report = dict(conversion_report or {})

    @property
    def num_labels(self) -> int:
        """Return the number of public semantic labels."""
        return len(cast(dict[int, str], self.id2label))

    @property
    def num_bbox_labels(self) -> int:
        """Return the model bbox-label count."""
        return self.num_labels

    @property
    def pad_label_id(self) -> int:
        """Return the internal padded label id."""
        return 0

    @property
    def max_elements(self) -> int:
        """Return the maximum generated element count."""
        return self.max_seq_length

num_labels property

num_labels: int

Return the number of public semantic labels.

num_bbox_labels property

num_bbox_labels: int

Return the model bbox-label count.

pad_label_id property

pad_label_id: int

Return the internal padded label id.

max_elements property

max_elements: int

Return the maximum generated element count.

__init__

__init__(
    *,
    dataset_name: str = "ad_banner",
    id2label: Mapping[int | str, str] | None = None,
    max_seq_length: int = 9,
    z_dim: int = 4,
    img_channels: int = 3,
    img_height: int = 256,
    img_width: int = 256,
    background_size: int = 256,
    hidden_dim: int = 256,
    bert_f_dim: int = 768,
    bert_num_encoder_layers: int = 12,
    bert_num_decoder_layers: int = 2,
    bert_num_heads: int = 4,
    max_text_length: int = 256,
    text_vocab_size: int = 30522,
    med_config: Mapping[str, LayoutDetrMetadataValue]
    | None = None,
    backbone_name: str = "resnet50",
    image_mean: Sequence[float] = (0.485, 0.456, 0.406),
    image_std: Sequence[float] = (0.229, 0.224, 0.225),
    architecture: Literal[
        "lightweight", "reference"
    ] = "lightweight",
    model_subfolder: str = "model",
    processor_subfolder: str = "processor",
    original_training_options: Mapping[
        str, LayoutDetrMetadataValue
    ]
    | None = None,
    conversion_report: Mapping[str, LayoutDetrMetadataValue]
    | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize LayoutDETR configuration.

Source code in models/layout-detr/src/layout_detr/configuration_layout_detr.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def __init__(
    self,
    *,
    dataset_name: str = "ad_banner",
    id2label: Mapping[int | str, str] | None = None,
    max_seq_length: int = 9,
    z_dim: int = 4,
    img_channels: int = 3,
    img_height: int = 256,
    img_width: int = 256,
    background_size: int = 256,
    hidden_dim: int = 256,
    bert_f_dim: int = 768,
    bert_num_encoder_layers: int = 12,
    bert_num_decoder_layers: int = 2,
    bert_num_heads: int = 4,
    max_text_length: int = 256,
    text_vocab_size: int = 30_522,
    med_config: Mapping[str, LayoutDetrMetadataValue] | None = None,
    backbone_name: str = "resnet50",
    image_mean: Sequence[float] = (0.485, 0.456, 0.406),
    image_std: Sequence[float] = (0.229, 0.224, 0.225),
    architecture: Literal["lightweight", "reference"] = "lightweight",
    model_subfolder: str = "model",
    processor_subfolder: str = "processor",
    original_training_options: Mapping[str, LayoutDetrMetadataValue] | None = None,
    conversion_report: Mapping[str, LayoutDetrMetadataValue] | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize LayoutDETR configuration."""
    raw_id2label = id2label or DEFAULT_ID2LABEL
    normalized_id2label = {int(key): value for key, value in raw_id2label.items()}
    super().__init__(id2label=normalized_id2label, **kwargs)  # ty: ignore[invalid-argument-type]

    self.dataset_name = dataset_name
    self.id2label = normalized_id2label
    self.label2id = {value: key for key, value in self.id2label.items()}
    self.max_seq_length = int(max_seq_length)
    self.z_dim = int(z_dim)
    self.img_channels = int(img_channels)
    self.img_height = int(img_height)
    self.img_width = int(img_width)
    self.background_size = int(background_size)

    self.hidden_dim = int(hidden_dim)
    self.bert_f_dim = int(bert_f_dim)
    self.bert_num_encoder_layers = int(bert_num_encoder_layers)
    self.bert_num_decoder_layers = int(bert_num_decoder_layers)
    self.bert_num_heads = int(bert_num_heads)
    self.max_text_length = int(max_text_length)
    self.text_vocab_size = int(text_vocab_size)

    self.med_config = dict(med_config or {})
    self.backbone_name = backbone_name
    self.image_mean = tuple(float(value) for value in image_mean)
    self.image_std = tuple(float(value) for value in image_std)
    self.architecture = architecture
    self.model_subfolder = model_subfolder
    self.processor_subfolder = processor_subfolder
    self.original_training_options = dict(original_training_options or {})
    self.conversion_report = dict(conversion_report or {})

datasets

Dataset helpers for the LayoutDETR Ad Banner checkpoint.

LayoutDetrDatasetRow

Bases: TypedDict

One local Ad Banner JSON file row.

Source code in models/layout-detr/src/layout_detr/datasets.py
28
29
30
31
32
class LayoutDetrDatasetRow(TypedDict):
    """One local Ad Banner JSON file row."""

    path: str
    elements: Sequence[Mapping[str, AdBannerAnnotationValue]]

NormalizedAdBannerAnnotation

Bases: TypedDict

Normalized Ad Banner annotation row.

Source code in models/layout-detr/src/layout_detr/datasets.py
35
36
37
38
39
40
class NormalizedAdBannerAnnotation(TypedDict):
    """Normalized Ad Banner annotation row."""

    bbox: list[float]
    label: int
    text: str

id2label_for_ad_banner

id2label_for_ad_banner() -> dict[int, str]

Return the LayoutDETR Ad Banner label vocabulary.

Source code in models/layout-detr/src/layout_detr/datasets.py
43
44
45
def id2label_for_ad_banner() -> dict[int, str]:
    """Return the LayoutDETR Ad Banner label vocabulary."""
    return dict(enumerate(AD_BANNER_LABELS))

label2id_for_ad_banner

label2id_for_ad_banner() -> dict[str, int]

Return the inverse Ad Banner label mapping.

Source code in models/layout-detr/src/layout_detr/datasets.py
48
49
50
def label2id_for_ad_banner() -> dict[str, int]:
    """Return the inverse Ad Banner label mapping."""
    return {label: index for index, label in id2label_for_ad_banner().items()}

normalize_ad_banner_annotation

normalize_ad_banner_annotation(
    sample: Mapping[str, AdBannerAnnotationValue],
) -> NormalizedAdBannerAnnotation

Normalize one Ad Banner annotation row to public center xywh.

Parameters:

Name Type Description Default
sample Mapping[str, AdBannerAnnotationValue]

Ad Banner element with xyxy_word_fit, label, str, width, and height values.

required

Returns:

Type Description
NormalizedAdBannerAnnotation

A normalized row with bbox, integer label, and text.

Raises:

Type Description
ValueError

If the label or canvas metadata is invalid.

Source code in models/layout-detr/src/layout_detr/datasets.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def normalize_ad_banner_annotation(
    sample: Mapping[str, AdBannerAnnotationValue],
) -> NormalizedAdBannerAnnotation:
    """Normalize one Ad Banner annotation row to public center ``xywh``.

    Args:
        sample: Ad Banner element with ``xyxy_word_fit``, ``label``, ``str``,
            ``width``, and ``height`` values.

    Returns:
        A normalized row with ``bbox``, integer ``label``, and ``text``.

    Raises:
        ValueError: If the label or canvas metadata is invalid.
    """
    label = str(sample["label"])
    label2id = label2id_for_ad_banner()
    if label not in label2id:
        raise ValueError(f"Unknown Ad Banner label: {label}")

    width = int(cast(int | str, sample["width"]))
    height = int(cast(int | str, sample["height"]))
    xyxy = torch.tensor(
        cast(Sequence[int | float], sample["xyxy_word_fit"]), dtype=torch.float32
    ).view(1, 1, 4)
    bbox = normalize_boxes(xyxy, canvas_size=(width, height), box_format="ltrb")
    return {
        "bbox": bbox[0, 0].tolist(),
        "label": label2id[label],
        "text": str(sample.get("str", "")),
    }

load_ad_banner_dataset

load_ad_banner_dataset(
    root: str | Path,
    *,
    split: Literal["train", "validation"],
    source: Literal["ad_banner"] = "ad_banner",
) -> Iterable[LayoutDetrDatasetRow]

Iterate a local Ad Banner directory without downloading assets.

TODO: switch this adapter to a creative-graphic-design Hugging Face dataset once Ad Banner is imported into the org.

Source code in models/layout-detr/src/layout_detr/datasets.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def load_ad_banner_dataset(
    root: str | Path,
    *,
    split: Literal["train", "validation"],
    source: Literal["ad_banner"] = "ad_banner",
) -> Iterable[LayoutDetrDatasetRow]:
    """Iterate a local Ad Banner directory without downloading assets.

    TODO: switch this adapter to a ``creative-graphic-design`` Hugging Face
    dataset once Ad Banner is imported into the org.
    """
    if source != "ad_banner":
        raise ValueError("LayoutDETR currently supports only source='ad_banner'")

    split_name = "val" if split == "validation" else "train"
    root_path = Path(root)
    json_paths = sorted(root_path.glob(f"{split_name}/**/*.json"))
    if not json_paths:
        json_paths = sorted(root_path.glob("*.json"))
    for path in json_paths:
        payload = json.loads(path.read_text(encoding="utf-8"))
        rows = payload if isinstance(payload, list) else payload.get("elements", [])
        yield {
            "path": str(path),
            "elements": cast(Sequence[Mapping[str, AdBannerAnnotationValue]], rows),
        }

image_processing_layout_detr

Image processor for LayoutDETR background images.

LayoutDetrImageProcessor

Bases: BaseImageProcessor

Prepare ImageNet-normalized background tensors for LayoutDETR.

Source code in models/layout-detr/src/layout_detr/image_processing_layout_detr.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
class LayoutDetrImageProcessor(BaseImageProcessor):
    """Prepare ImageNet-normalized background tensors for LayoutDETR."""

    model_input_names = ["pixel_values"]

    def __init__(
        self,
        background_size: int = 256,
        image_mean: Sequence[float] = (0.485, 0.456, 0.406),
        image_std: Sequence[float] = (0.229, 0.224, 0.225),
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize image normalization settings."""
        super().__init__(**kwargs)
        self.background_size = int(background_size)
        self.image_mean = tuple(float(value) for value in image_mean)
        self.image_std = tuple(float(value) for value in image_std)

    @classmethod
    def from_config(cls, config: LayoutDetrConfig) -> "LayoutDetrImageProcessor":
        """Build an image processor from a LayoutDETR config."""
        return cls(
            background_size=config.background_size,
            image_mean=config.image_mean,
            image_std=config.image_std,
        )

    def preprocess(
        self,
        images: ImageInput | Sequence[ImageInput],
        *,
        background_preprocessing: BackgroundPreprocessing
        | str = BackgroundPreprocessing.none,
        canvas_size: tuple[int, int] | None = None,
        return_tensors: Literal["pt"] = "pt",
        **kwargs: str | int | float | bool | None,
    ) -> BatchFeature:
        """Preprocess a background image or batch.

        Args:
            images: PIL, NumPy, or torch image input.
            background_preprocessing: Released-checkpoint-compatible mode.
            canvas_size: Optional canvas metadata override.
            return_tensors: Only ``"pt"`` is supported.
            kwargs: Ignored compatibility kwargs.

        Returns:
            ``BatchFeature`` with ``pixel_values`` and ``canvas_size``.
        """
        del kwargs
        if return_tensors != "pt":
            raise ValueError(
                "LayoutDetrImageProcessor only supports return_tensors='pt'"
            )

        mode = normalize_background_preprocessing(background_preprocessing)
        tensors: list[Float[torch.Tensor, "channels height width"]] = []
        sizes: list[tuple[int, int]] = []
        for image in _ensure_pil_batch(images):
            sizes.append(canvas_size or image.size)
            processed = _apply_background_preprocessing(image.convert("RGB"), mode)
            processed = processed.resize(
                (self.background_size, self.background_size),
                Image.Resampling.BILINEAR,
            )
            array = np.asarray(processed, dtype=np.float32) / 255.0
            mean = np.asarray(self.image_mean, dtype=np.float32)
            std = np.asarray(self.image_std, dtype=np.float32)
            tensors.append(torch.from_numpy(((array - mean) / std).transpose(2, 0, 1)))
        return BatchFeature(
            {
                "pixel_values": torch.stack(tensors).float(),
                "canvas_size": torch.tensor(sizes, dtype=torch.long),
            }
        )

__init__

__init__(
    background_size: int = 256,
    image_mean: Sequence[float] = (0.485, 0.456, 0.406),
    image_std: Sequence[float] = (0.229, 0.224, 0.225),
    **kwargs: str | int | float | bool | None,
) -> None

Initialize image normalization settings.

Source code in models/layout-detr/src/layout_detr/image_processing_layout_detr.py
24
25
26
27
28
29
30
31
32
33
34
35
def __init__(
    self,
    background_size: int = 256,
    image_mean: Sequence[float] = (0.485, 0.456, 0.406),
    image_std: Sequence[float] = (0.229, 0.224, 0.225),
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize image normalization settings."""
    super().__init__(**kwargs)
    self.background_size = int(background_size)
    self.image_mean = tuple(float(value) for value in image_mean)
    self.image_std = tuple(float(value) for value in image_std)

from_config classmethod

from_config(
    config: LayoutDetrConfig,
) -> "LayoutDetrImageProcessor"

Build an image processor from a LayoutDETR config.

Source code in models/layout-detr/src/layout_detr/image_processing_layout_detr.py
37
38
39
40
41
42
43
44
@classmethod
def from_config(cls, config: LayoutDetrConfig) -> "LayoutDetrImageProcessor":
    """Build an image processor from a LayoutDETR config."""
    return cls(
        background_size=config.background_size,
        image_mean=config.image_mean,
        image_std=config.image_std,
    )

preprocess

preprocess(
    images: ImageInput | Sequence[ImageInput],
    *,
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
    **kwargs: str | int | float | bool | None,
) -> BatchFeature

Preprocess a background image or batch.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput]

PIL, NumPy, or torch image input.

required
background_preprocessing BackgroundPreprocessing | str

Released-checkpoint-compatible mode.

none
canvas_size tuple[int, int] | None

Optional canvas metadata override.

None
return_tensors Literal['pt']

Only "pt" is supported.

'pt'
kwargs str | int | float | bool | None

Ignored compatibility kwargs.

{}

Returns:

Type Description
BatchFeature

BatchFeature with pixel_values and canvas_size.

Source code in models/layout-detr/src/layout_detr/image_processing_layout_detr.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def preprocess(
    self,
    images: ImageInput | Sequence[ImageInput],
    *,
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
    **kwargs: str | int | float | bool | None,
) -> BatchFeature:
    """Preprocess a background image or batch.

    Args:
        images: PIL, NumPy, or torch image input.
        background_preprocessing: Released-checkpoint-compatible mode.
        canvas_size: Optional canvas metadata override.
        return_tensors: Only ``"pt"`` is supported.
        kwargs: Ignored compatibility kwargs.

    Returns:
        ``BatchFeature`` with ``pixel_values`` and ``canvas_size``.
    """
    del kwargs
    if return_tensors != "pt":
        raise ValueError(
            "LayoutDetrImageProcessor only supports return_tensors='pt'"
        )

    mode = normalize_background_preprocessing(background_preprocessing)
    tensors: list[Float[torch.Tensor, "channels height width"]] = []
    sizes: list[tuple[int, int]] = []
    for image in _ensure_pil_batch(images):
        sizes.append(canvas_size or image.size)
        processed = _apply_background_preprocessing(image.convert("RGB"), mode)
        processed = processed.resize(
            (self.background_size, self.background_size),
            Image.Resampling.BILINEAR,
        )
        array = np.asarray(processed, dtype=np.float32) / 255.0
        mean = np.asarray(self.image_mean, dtype=np.float32)
        std = np.asarray(self.image_std, dtype=np.float32)
        tensors.append(torch.from_numpy(((array - mean) / std).transpose(2, 0, 1)))
    return BatchFeature(
        {
            "pixel_values": torch.stack(tensors).float(),
            "canvas_size": torch.tensor(sizes, dtype=torch.long),
        }
    )

normalize_background_preprocessing

normalize_background_preprocessing(
    mode: BackgroundPreprocessing | str,
) -> BackgroundPreprocessing

Normalize a public background preprocessing mode.

Source code in models/layout-detr/src/layout_detr/image_processing_layout_detr.py
 96
 97
 98
 99
100
101
102
103
104
105
def normalize_background_preprocessing(
    mode: BackgroundPreprocessing | str,
) -> BackgroundPreprocessing:
    """Normalize a public background preprocessing mode."""
    if isinstance(mode, BackgroundPreprocessing):
        return mode
    try:
        return BackgroundPreprocessing(mode)
    except ValueError as exc:
        raise ValueError(f"Unsupported background_preprocessing: {mode}") from exc

modeling_layout_detr

Transformers-compatible LayoutDETR model.

LayoutDetrModelOutput dataclass

Bases: ModelOutput

Raw LayoutDETR model output.

Source code in models/layout-detr/src/layout_detr/modeling_layout_detr.py
36
37
38
39
40
41
42
43
44
45
46
47
48
@dataclass
class LayoutDetrModelOutput(ModelOutput):
    """Raw LayoutDETR model output."""

    bbox: Float[torch.Tensor, "batch elements 4"]
    labels: Int[torch.Tensor, "batch elements"] = cast(
        Int[torch.Tensor, "batch elements"], None
    )
    mask: Bool[torch.Tensor, "batch elements"] = cast(
        Bool[torch.Tensor, "batch elements"], None
    )
    latents: Float[torch.Tensor, "batch elements latent"] | None = None
    hidden_states: Float[torch.Tensor, "batch elements hidden"] | None = None

LayoutDetrForConditionalGeneration

Bases: PreTrainedModel

A standard PreTrainedModel wrapper for LayoutDETR forward inference.

Source code in models/layout-detr/src/layout_detr/modeling_layout_detr.py
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
class LayoutDetrForConditionalGeneration(PreTrainedModel):
    """A standard ``PreTrainedModel`` wrapper for LayoutDETR forward inference."""

    config_class = LayoutDetrConfig
    base_model_prefix = "layout_detr"
    main_input_name = "pixel_values"
    supports_gradient_checkpointing = False

    def __init__(self, config: LayoutDetrConfig) -> None:
        """Initialize LayoutDETR layers."""
        super().__init__(config)
        self._is_reference_architecture = config.architecture == "reference"
        if self._is_reference_architecture:  # pragma: no cover
            self._init_reference_layers(config)
        else:
            self._init_lightweight_layers(config)
        self.post_init()

    def _init_lightweight_layers(self, config: LayoutDetrConfig) -> None:
        self.background_encoder = nn.Sequential(
            nn.Conv2d(config.img_channels, config.hidden_dim, kernel_size=3, padding=1),
            nn.GELU(),
            nn.AdaptiveAvgPool2d(1),
            nn.Flatten(),
        )
        self.fc_z = nn.Linear(config.z_dim, config.bert_f_dim)
        self.emb_label = nn.Embedding(config.num_bbox_labels, config.bert_f_dim)
        self.text_embeddings = nn.Embedding(config.text_vocab_size, config.bert_f_dim)
        self.text_len_embeddings = nn.Embedding(
            config.max_text_length, config.bert_f_dim
        )
        self.background_proj = nn.Linear(config.hidden_dim, config.hidden_dim)
        self.fc_in = nn.Sequential(
            nn.Linear(config.bert_f_dim * 4, config.hidden_dim),
            nn.GELU(),
            nn.Linear(config.hidden_dim, config.hidden_dim),
        )
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=config.hidden_dim,
            nhead=max(1, min(8, config.hidden_dim // 8)),
            dim_feedforward=max(config.hidden_dim * 4, 64),
            batch_first=True,
        )
        self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=2)
        self.bbox_embed = nn.Sequential(
            nn.Linear(config.hidden_dim, config.hidden_dim),
            nn.GELU(),
            nn.Linear(config.hidden_dim, 4),
        )

    def _init_reference_layers(
        self, config: LayoutDetrConfig
    ) -> None:  # pragma: no cover
        self.backbone = _build_backbone(config.backbone_name)
        self.input_proj = nn.Conv2d(self.backbone.num_channels, config.hidden_dim, 1)
        self.fc_z = nn.Linear(config.z_dim * config.max_seq_length, config.bert_f_dim)
        self.emb_label = nn.Embedding(config.num_bbox_labels, config.bert_f_dim)
        self.text_encoder = _build_reference_bert_model(
            config,
            num_hidden_layers=config.bert_num_encoder_layers,
            encoder_width=config.bert_f_dim,
            add_pooling_layer=False,
            use_cross_attention=True,
            text_encoder=True,
        )
        self.enc_text_len = nn.Embedding(config.max_text_length, config.bert_f_dim)
        self.fc_in = _ReferenceMLP(
            input_dim=config.bert_f_dim * 4,
            hidden_dim=config.bert_f_dim,
            output_dim=config.hidden_dim,
            num_layers=3,
        )
        self.transformer = _DetrTransformer(
            d_model=config.hidden_dim,
            dropout=0.1,
            nhead=8,
            dim_feedforward=2048,
            num_encoder_layers=6,
            num_decoder_layers=6,
        )
        self.bbox_embed = _ReferenceMLP(
            input_dim=config.hidden_dim,
            hidden_dim=config.hidden_dim,
            output_dim=4,
            num_layers=3,
        )
        self.fc_z_rec = nn.Linear(
            config.hidden_dim, config.z_dim * config.max_seq_length
        )
        self.fc_out_cls = nn.Linear(config.hidden_dim, config.num_bbox_labels)
        self.text_decoder = _build_reference_bert_lm_head(
            config,
            num_hidden_layers=config.bert_num_decoder_layers,
            encoder_width=512,
        )
        self.fc_text_len_rec = nn.Linear(config.hidden_dim, config.max_text_length)

    def forward(
        self,
        *,
        pixel_values: Float[torch.Tensor, "batch channels height width"],
        input_ids: Int[torch.Tensor, "batch elements tokens"],
        text_attention_mask: Bool[torch.Tensor, "batch elements tokens"],
        bbox_labels: Int[torch.Tensor, "batch elements"],
        layout_mask: Bool[torch.Tensor, "batch elements"],
        latents: Float[torch.Tensor, "batch elements latent"],
        text_lengths: Int[torch.Tensor, "batch elements"] | None = None,
        return_dict: bool | None = None,
    ) -> (
        LayoutDetrModelOutput
        | tuple[
            Float[torch.Tensor, "batch elements 4"],
            Int[torch.Tensor, "batch elements"],
            Bool[torch.Tensor, "batch elements"],
        ]
    ):
        """Run the LayoutDETR conditional forward pass."""
        return_dict = (
            self.config.use_return_dict if return_dict is None else return_dict
        )
        if bbox_labels.ndim != 2:
            raise ValueError("bbox_labels must have shape (batch, elements)")

        if (
            latents.shape[:2] != bbox_labels.shape
            or latents.shape[-1] != self.config.z_dim
        ):
            raise ValueError("latents must have shape (batch, elements, z_dim)")

        if input_ids.shape[:2] != bbox_labels.shape:
            raise ValueError("input_ids must have shape (batch, elements, tokens)")

        labels = bbox_labels.to(dtype=torch.long)
        if labels.numel() and (
            int(labels.min().item()) < 0
            or int(labels.max().item()) >= self.config.num_bbox_labels
        ):
            raise ValueError("bbox_labels contain ids outside config.num_bbox_labels")

        device = labels.device
        pixel_values = pixel_values.to(device=device, dtype=self.dtype)
        latents = latents.to(device=device, dtype=self.dtype)
        input_ids = input_ids.to(device=device, dtype=torch.long)
        text_attention_mask = text_attention_mask.to(device=device, dtype=torch.bool)
        layout_mask = layout_mask.to(device=device, dtype=torch.bool)

        if self._is_reference_architecture:
            bbox, hidden = self._forward_reference(
                pixel_values=pixel_values,
                input_ids=input_ids,
                text_attention_mask=text_attention_mask,
                labels=labels,
                layout_mask=layout_mask,
                latents=latents,
                text_lengths=text_lengths,
            )
            if not return_dict:
                return bbox, labels, layout_mask
            return LayoutDetrModelOutput(
                bbox=bbox,
                labels=labels,
                mask=layout_mask,
                latents=latents,
                hidden_states=hidden,
            )

        bg = self.background_proj(self.background_encoder(pixel_values)).unsqueeze(1)
        z = self.fc_z(latents)
        label_features = self.emb_label(labels)
        text_tokens = self.text_embeddings(input_ids)
        token_mask = text_attention_mask.unsqueeze(-1).to(dtype=text_tokens.dtype)
        denom = token_mask.sum(dim=2).clamp_min(1.0)
        text_features = (text_tokens * token_mask).sum(dim=2) / denom
        lengths = text_attention_mask.sum(dim=-1).clamp_max(
            self.config.max_text_length - 1
        )
        text_len_features = self.text_len_embeddings(lengths)
        hidden = self.fc_in(
            torch.cat([z, label_features, text_features, text_len_features], dim=-1)
        )
        hidden = hidden + bg
        hidden = self.transformer(hidden, src_key_padding_mask=~layout_mask)
        bbox = torch.sigmoid(self.bbox_embed(hidden))
        if not return_dict:
            return bbox, labels, layout_mask
        return LayoutDetrModelOutput(
            bbox=bbox,
            labels=labels,
            mask=layout_mask,
            latents=latents,
            hidden_states=hidden,
        )

    def _forward_reference(  # pragma: no cover
        self,
        *,
        pixel_values: Float[torch.Tensor, "batch channels height width"],
        input_ids: Int[torch.Tensor, "batch elements tokens"],
        text_attention_mask: Bool[torch.Tensor, "batch elements tokens"],
        labels: Int[torch.Tensor, "batch elements"],
        layout_mask: Bool[torch.Tensor, "batch elements"],
        latents: Float[torch.Tensor, "batch elements latent"],
        text_lengths: Int[torch.Tensor, "batch elements"] | None = None,
    ) -> tuple[
        Float[torch.Tensor, "batch elements 4"],
        Float[torch.Tensor, "batch elements hidden"],
    ]:
        bg_nested = _NestedTensor(
            pixel_values,
            torch.zeros(
                pixel_values.shape[0],
                pixel_values.shape[2],
                pixel_values.shape[3],
                dtype=torch.bool,
                device=pixel_values.device,
            ),
        )
        bg_feat, pos = self.backbone(bg_nested)
        bg_tensor, bg_mask = bg_feat[-1].decompose()
        z0 = _normalize_2nd_moment(latents.reshape(latents.shape[0], -1))
        z = self.fc_z(z0).unsqueeze(1).expand(-1, labels.shape[1], -1)
        label_features = self.emb_label(labels)
        flat_input_ids = input_ids.reshape(-1, input_ids.shape[-1])
        flat_attention = text_attention_mask.reshape(-1, text_attention_mask.shape[-1])
        text_output = self.text_encoder(
            flat_input_ids,
            attention_mask=flat_attention,
            return_dict=True,
            mode="text",
        )
        text_features = text_output.last_hidden_state[:, 0, :].view(
            labels.shape[0], labels.shape[1], -1
        )
        if text_lengths is None:
            lengths = flat_attention.sum(dim=-1)
        else:
            lengths = text_lengths.to(device=labels.device, dtype=torch.long).reshape(
                -1
            )
        lengths = lengths.clamp_max(self.config.max_text_length - 1)
        text_len_features = self.enc_text_len(lengths.view(labels.shape))
        hidden = torch.cat(
            [z, label_features, text_features, text_len_features], dim=-1
        )
        hidden = torch.relu(self.fc_in(hidden)).permute(1, 0, 2)
        hidden = self.transformer(
            src=self.input_proj(bg_tensor),
            mask=bg_mask,
            pos_embed=pos[-1],
            tgt=hidden,
            tgt_key_padding_mask=~layout_mask,
        )[0]
        return torch.sigmoid(self.bbox_embed(hidden)), hidden

__init__

__init__(config: LayoutDetrConfig) -> None

Initialize LayoutDETR layers.

Source code in models/layout-detr/src/layout_detr/modeling_layout_detr.py
59
60
61
62
63
64
65
66
67
def __init__(self, config: LayoutDetrConfig) -> None:
    """Initialize LayoutDETR layers."""
    super().__init__(config)
    self._is_reference_architecture = config.architecture == "reference"
    if self._is_reference_architecture:  # pragma: no cover
        self._init_reference_layers(config)
    else:
        self._init_lightweight_layers(config)
    self.post_init()

forward

forward(
    *,
    pixel_values: Float[
        Tensor, "batch channels height width"
    ],
    input_ids: Int[Tensor, "batch elements tokens"],
    text_attention_mask: Bool[
        Tensor, "batch elements tokens"
    ],
    bbox_labels: Int[Tensor, "batch elements"],
    layout_mask: Bool[Tensor, "batch elements"],
    latents: Float[Tensor, "batch elements latent"],
    text_lengths: Int[Tensor, "batch elements"]
    | None = None,
    return_dict: bool | None = None,
) -> (
    LayoutDetrModelOutput
    | tuple[
        Float[torch.Tensor, "batch elements 4"],
        Int[torch.Tensor, "batch elements"],
        Bool[torch.Tensor, "batch elements"],
    ]
)

Run the LayoutDETR conditional forward pass.

Source code in models/layout-detr/src/layout_detr/modeling_layout_detr.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
def forward(
    self,
    *,
    pixel_values: Float[torch.Tensor, "batch channels height width"],
    input_ids: Int[torch.Tensor, "batch elements tokens"],
    text_attention_mask: Bool[torch.Tensor, "batch elements tokens"],
    bbox_labels: Int[torch.Tensor, "batch elements"],
    layout_mask: Bool[torch.Tensor, "batch elements"],
    latents: Float[torch.Tensor, "batch elements latent"],
    text_lengths: Int[torch.Tensor, "batch elements"] | None = None,
    return_dict: bool | None = None,
) -> (
    LayoutDetrModelOutput
    | tuple[
        Float[torch.Tensor, "batch elements 4"],
        Int[torch.Tensor, "batch elements"],
        Bool[torch.Tensor, "batch elements"],
    ]
):
    """Run the LayoutDETR conditional forward pass."""
    return_dict = (
        self.config.use_return_dict if return_dict is None else return_dict
    )
    if bbox_labels.ndim != 2:
        raise ValueError("bbox_labels must have shape (batch, elements)")

    if (
        latents.shape[:2] != bbox_labels.shape
        or latents.shape[-1] != self.config.z_dim
    ):
        raise ValueError("latents must have shape (batch, elements, z_dim)")

    if input_ids.shape[:2] != bbox_labels.shape:
        raise ValueError("input_ids must have shape (batch, elements, tokens)")

    labels = bbox_labels.to(dtype=torch.long)
    if labels.numel() and (
        int(labels.min().item()) < 0
        or int(labels.max().item()) >= self.config.num_bbox_labels
    ):
        raise ValueError("bbox_labels contain ids outside config.num_bbox_labels")

    device = labels.device
    pixel_values = pixel_values.to(device=device, dtype=self.dtype)
    latents = latents.to(device=device, dtype=self.dtype)
    input_ids = input_ids.to(device=device, dtype=torch.long)
    text_attention_mask = text_attention_mask.to(device=device, dtype=torch.bool)
    layout_mask = layout_mask.to(device=device, dtype=torch.bool)

    if self._is_reference_architecture:
        bbox, hidden = self._forward_reference(
            pixel_values=pixel_values,
            input_ids=input_ids,
            text_attention_mask=text_attention_mask,
            labels=labels,
            layout_mask=layout_mask,
            latents=latents,
            text_lengths=text_lengths,
        )
        if not return_dict:
            return bbox, labels, layout_mask
        return LayoutDetrModelOutput(
            bbox=bbox,
            labels=labels,
            mask=layout_mask,
            latents=latents,
            hidden_states=hidden,
        )

    bg = self.background_proj(self.background_encoder(pixel_values)).unsqueeze(1)
    z = self.fc_z(latents)
    label_features = self.emb_label(labels)
    text_tokens = self.text_embeddings(input_ids)
    token_mask = text_attention_mask.unsqueeze(-1).to(dtype=text_tokens.dtype)
    denom = token_mask.sum(dim=2).clamp_min(1.0)
    text_features = (text_tokens * token_mask).sum(dim=2) / denom
    lengths = text_attention_mask.sum(dim=-1).clamp_max(
        self.config.max_text_length - 1
    )
    text_len_features = self.text_len_embeddings(lengths)
    hidden = self.fc_in(
        torch.cat([z, label_features, text_features, text_len_features], dim=-1)
    )
    hidden = hidden + bg
    hidden = self.transformer(hidden, src_key_padding_mask=~layout_mask)
    bbox = torch.sigmoid(self.bbox_embed(hidden))
    if not return_dict:
        return bbox, labels, layout_mask
    return LayoutDetrModelOutput(
        bbox=bbox,
        labels=labels,
        mask=layout_mask,
        latents=latents,
        hidden_states=hidden,
    )

pipeline_layout_detr

Pipeline interface for LayoutDETR content-image layout generation.

LayoutDetrPipeline

Bases: LayoutGenerationPipeline

Transformers-side LayoutDETR pipeline.

Source code in models/layout-detr/src/layout_detr/pipeline_layout_detr.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
class LayoutDetrPipeline(LayoutGenerationPipeline):
    """Transformers-side LayoutDETR pipeline."""

    config_class: ClassVar[type[PretrainedConfig]] = LayoutDetrConfig
    component_specs: ClassVar[dict[str, PipelineComponentSpec]] = (
        model_processor_component_specs(
            model_loader=_load_model_component,
            processor_loader=_load_processor_component,
        )
    )

    config: LayoutDetrConfig
    model: LayoutDetrForConditionalGeneration
    processor: LayoutDetrProcessor

    def __init__(
        self,
        model: LayoutDetrForConditionalGeneration,
        processor: LayoutDetrProcessor | None = None,
        config: LayoutDetrConfig | None = None,
        device: str | torch.device | None = None,
    ) -> None:
        """Initialize a LayoutDETR pipeline."""
        super().__init__(config or model.config)
        self.config = config or model.config
        self.model = model
        self.processor = processor or LayoutDetrProcessor(config=self.config)
        self.model.eval()
        if device is not None:
            self.to(device)

    @classmethod
    def _from_pretrained_components(
        cls,
        *,
        config: PretrainedConfig,
        components: Mapping[str, PipelineComponent | None],
    ) -> "LayoutDetrPipeline":
        """Build a pipeline from saved components."""
        return cls(
            config=cast(LayoutDetrConfig, config),
            model=cast(LayoutDetrForConditionalGeneration, components["model"]),
            processor=cast(LayoutDetrProcessor, components["processor"]),
        )

    @torch.no_grad()
    def __call__(
        self,
        images: ImageInput
        | Sequence[ImageInput]
        | Shaped[torch.Tensor, "..."]
        | None = None,
        *,
        content: Mapping[
            str,
            ImageInput
            | Sequence[ImageInput]
            | Sequence[Sequence[str]]
            | Sequence[str]
            | Sequence[Sequence[int | str]]
            | Sequence[int | str],
        ]
        | None = None,
        prompt: str | Sequence[str] | None = None,
        texts: Sequence[Sequence[str]] | Sequence[str] | None = None,
        batch_size: int = 1,
        seed: int | None = None,
        generator: torch.Generator | None = None,
        condition_type: ConditionType | str = ConditionType.content_image,
        labels: Int[torch.Tensor, "batch elements"]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"] | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Sequence[Sequence[bool]]
        | Sequence[bool]
        | None = None,
        num_elements: int | Sequence[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_intermediates: bool = False,
        background_preprocessing: BackgroundPreprocessing
        | str = BackgroundPreprocessing.none,
        out_jittering_strength: float = 0.0,
        out_postprocessing: PostprocessingMode | str = PostprocessingMode.none,
        latents: Float[torch.Tensor, "batch elements latent"] | None = None,
    ) -> LayoutGenerationOutput:
        """Generate layouts for a background image and per-element text labels."""
        normalize_condition_type(condition_type)
        if bbox is not None:
            raise ValueError("LayoutDETR does not condition on existing bbox")

        if num_elements is not None:
            raise ValueError("LayoutDETR infers num_elements from labels/mask")

        if box_format != BoxFormat.xywh and box_format != "xywh":
            raise ValueError("LayoutDETR outputs normalized xywh boxes only")

        if not normalized:
            raise ValueError("LayoutDETR expects normalized public boxes")

        if num_inference_steps is not None:
            raise ValueError("LayoutDETR is a single forward pass, not iterative")

        encoded = self.processor(
            images=images,
            content=content,
            prompt=prompt,
            texts=texts,
            labels=labels,
            mask=mask,
            condition_type=str(ConditionType.content_image),
            background_preprocessing=background_preprocessing,
            batch_size=batch_size,
            canvas_size=canvas_size,
        )
        device = self.device or next(self.model.parameters()).device
        encoded = encoded.to(device)
        batch, elements = encoded["bbox_labels"].shape
        runtime_generator = self.prepare_generator(
            generator=generator,
            seed=seed,
            device=device,
        )
        if latents is None:
            latents = torch.randn(
                (batch, elements, self.config.z_dim),
                generator=runtime_generator,
                device=device,
            )
        else:
            latents = latents.to(device=device)
        model_output = self.model(
            pixel_values=encoded["pixel_values"],
            input_ids=encoded["input_ids"],
            text_attention_mask=encoded["text_attention_mask"],
            bbox_labels=encoded["bbox_labels"],
            layout_mask=encoded["layout_mask"],
            latents=latents,
            text_lengths=encoded["text_lengths"],
        )
        bbox_out = apply_postprocessing(
            model_output.bbox,
            model_output.mask,
            mode=out_postprocessing,
            jitter_strength=out_jittering_strength,
            generator=runtime_generator,
        )
        intermediates = {
            "latents": latents.detach().cpu(),
            "texts": encoded["texts"],
            "background_preprocessing": str(background_preprocessing),
            "postprocessing": str(out_postprocessing),
        }
        return cast(
            LayoutGenerationOutput,
            self.processor.post_process_layouts(
                bbox_out,
                model_output.labels,
                model_output.mask,
                output_type=output_type,
                return_intermediates=return_intermediates,
                intermediates=intermediates,
            ),
        )

__init__

__init__(
    model: LayoutDetrForConditionalGeneration,
    processor: LayoutDetrProcessor | None = None,
    config: LayoutDetrConfig | None = None,
    device: str | device | None = None,
) -> None

Initialize a LayoutDETR pipeline.

Source code in models/layout-detr/src/layout_detr/pipeline_layout_detr.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def __init__(
    self,
    model: LayoutDetrForConditionalGeneration,
    processor: LayoutDetrProcessor | None = None,
    config: LayoutDetrConfig | None = None,
    device: str | torch.device | None = None,
) -> None:
    """Initialize a LayoutDETR pipeline."""
    super().__init__(config or model.config)
    self.config = config or model.config
    self.model = model
    self.processor = processor or LayoutDetrProcessor(config=self.config)
    self.model.eval()
    if device is not None:
        self.to(device)

__call__

__call__(
    images: ImageInput
    | Sequence[ImageInput]
    | Shaped[Tensor, "..."]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Sequence[Sequence[str]]
        | Sequence[str]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    texts: Sequence[Sequence[str]]
    | Sequence[str]
    | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.content_image,
    labels: Int[Tensor, "batch elements"]
    | Sequence[Sequence[int | str]]
    | Sequence[int | str]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"] | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    num_elements: int
    | Sequence[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    out_jittering_strength: float = 0.0,
    out_postprocessing: PostprocessingMode
    | str = PostprocessingMode.none,
    latents: Float[Tensor, "batch elements latent"]
    | None = None,
) -> LayoutGenerationOutput

Generate layouts for a background image and per-element text labels.

Source code in models/layout-detr/src/layout_detr/pipeline_layout_detr.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
@torch.no_grad()
def __call__(
    self,
    images: ImageInput
    | Sequence[ImageInput]
    | Shaped[torch.Tensor, "..."]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Sequence[Sequence[str]]
        | Sequence[str]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    texts: Sequence[Sequence[str]] | Sequence[str] | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: torch.Generator | None = None,
    condition_type: ConditionType | str = ConditionType.content_image,
    labels: Int[torch.Tensor, "batch elements"]
    | Sequence[Sequence[int | str]]
    | Sequence[int | str]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"] | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    num_elements: int | Sequence[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    out_jittering_strength: float = 0.0,
    out_postprocessing: PostprocessingMode | str = PostprocessingMode.none,
    latents: Float[torch.Tensor, "batch elements latent"] | None = None,
) -> LayoutGenerationOutput:
    """Generate layouts for a background image and per-element text labels."""
    normalize_condition_type(condition_type)
    if bbox is not None:
        raise ValueError("LayoutDETR does not condition on existing bbox")

    if num_elements is not None:
        raise ValueError("LayoutDETR infers num_elements from labels/mask")

    if box_format != BoxFormat.xywh and box_format != "xywh":
        raise ValueError("LayoutDETR outputs normalized xywh boxes only")

    if not normalized:
        raise ValueError("LayoutDETR expects normalized public boxes")

    if num_inference_steps is not None:
        raise ValueError("LayoutDETR is a single forward pass, not iterative")

    encoded = self.processor(
        images=images,
        content=content,
        prompt=prompt,
        texts=texts,
        labels=labels,
        mask=mask,
        condition_type=str(ConditionType.content_image),
        background_preprocessing=background_preprocessing,
        batch_size=batch_size,
        canvas_size=canvas_size,
    )
    device = self.device or next(self.model.parameters()).device
    encoded = encoded.to(device)
    batch, elements = encoded["bbox_labels"].shape
    runtime_generator = self.prepare_generator(
        generator=generator,
        seed=seed,
        device=device,
    )
    if latents is None:
        latents = torch.randn(
            (batch, elements, self.config.z_dim),
            generator=runtime_generator,
            device=device,
        )
    else:
        latents = latents.to(device=device)
    model_output = self.model(
        pixel_values=encoded["pixel_values"],
        input_ids=encoded["input_ids"],
        text_attention_mask=encoded["text_attention_mask"],
        bbox_labels=encoded["bbox_labels"],
        layout_mask=encoded["layout_mask"],
        latents=latents,
        text_lengths=encoded["text_lengths"],
    )
    bbox_out = apply_postprocessing(
        model_output.bbox,
        model_output.mask,
        mode=out_postprocessing,
        jitter_strength=out_jittering_strength,
        generator=runtime_generator,
    )
    intermediates = {
        "latents": latents.detach().cpu(),
        "texts": encoded["texts"],
        "background_preprocessing": str(background_preprocessing),
        "postprocessing": str(out_postprocessing),
    }
    return cast(
        LayoutGenerationOutput,
        self.processor.post_process_layouts(
            bbox_out,
            model_output.labels,
            model_output.mask,
            output_type=output_type,
            return_intermediates=return_intermediates,
            intermediates=intermediates,
        ),
    )

normalize_condition_type

normalize_condition_type(
    condition_type: ConditionType | str,
) -> ConditionType

Normalize LayoutDETR condition modes.

Source code in models/layout-detr/src/layout_detr/pipeline_layout_detr.py
74
75
76
77
78
79
80
81
82
def normalize_condition_type(condition_type: ConditionType | str) -> ConditionType:
    """Normalize LayoutDETR condition modes."""
    canonical = normalize_shared_condition_type(condition_type)
    if canonical is not ConditionType.content_image:
        raise NotImplementedError(
            "LayoutDETR supports only condition_type='content_image' with image, texts, and labels"
        )

    return canonical

postprocessing

Pure tensor LayoutDETR postprocessing helpers.

PostprocessingMode

Bases: StrEnum

Supported LayoutDETR postprocessing modes.

Source code in models/layout-detr/src/layout_detr/postprocessing.py
13
14
15
16
17
18
class PostprocessingMode(StrEnum):
    """Supported LayoutDETR postprocessing modes."""

    none = auto()
    horizontal_center_aligned = auto()
    horizontal_left_aligned = auto()

normalize_postprocessing_mode

normalize_postprocessing_mode(
    mode: PostprocessingMode | str,
) -> PostprocessingMode

Normalize a public postprocessing mode value.

Source code in models/layout-detr/src/layout_detr/postprocessing.py
21
22
23
24
25
26
27
28
29
30
def normalize_postprocessing_mode(
    mode: PostprocessingMode | str,
) -> PostprocessingMode:
    """Normalize a public postprocessing mode value."""
    if isinstance(mode, PostprocessingMode):
        return mode
    try:
        return PostprocessingMode(mode)
    except ValueError as exc:
        raise ValueError(f"Unsupported out_postprocessing: {mode}") from exc

jitter_boxes

jitter_boxes(
    bbox: Float[Tensor, "batch elements 4"],
    *,
    strength: float,
    generator: Generator | None,
) -> Float[torch.Tensor, "batch elements 4"]

Apply multiplicative jitter to generated boxes.

Source code in models/layout-detr/src/layout_detr/postprocessing.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def jitter_boxes(
    bbox: Float[torch.Tensor, "batch elements 4"],
    *,
    strength: float,
    generator: torch.Generator | None,
) -> Float[torch.Tensor, "batch elements 4"]:
    """Apply multiplicative jitter to generated boxes."""
    if strength == 0.0:
        return bbox
    if strength < 0.0 or strength >= 1.0:
        raise ValueError("strength must be in [0, 1)")

    low = torch.log(bbox.new_tensor(1.0 - strength))
    high = torch.log(bbox.new_tensor(1.0 + strength))
    noise = torch.rand(
        bbox.shape,
        generator=generator,
        device=bbox.device,
        dtype=bbox.dtype,
    )
    return bbox * torch.exp(low + (high - low) * noise)

horizontal_center_aligned

horizontal_center_aligned(
    bbox: Float[Tensor, "batch elements 4"],
    mask: Bool[Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements 4"]

Align valid boxes to the mean center-x coordinate.

Source code in models/layout-detr/src/layout_detr/postprocessing.py
56
57
58
59
60
61
62
63
64
65
66
def horizontal_center_aligned(
    bbox: Float[torch.Tensor, "batch elements 4"],
    mask: Bool[torch.Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements 4"]:
    """Align valid boxes to the mean center-x coordinate."""
    out = bbox.clone()
    for batch in range(out.shape[0]):
        valid = mask[batch]
        if valid.any():
            out[batch, valid, 0] = out[batch, valid, 0].mean()
    return out

horizontal_left_aligned

horizontal_left_aligned(
    bbox: Float[Tensor, "batch elements 4"],
    mask: Bool[Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements 4"]

Align valid boxes to the mean left edge.

Source code in models/layout-detr/src/layout_detr/postprocessing.py
69
70
71
72
73
74
75
76
77
78
79
80
81
def horizontal_left_aligned(
    bbox: Float[torch.Tensor, "batch elements 4"],
    mask: Bool[torch.Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements 4"]:
    """Align valid boxes to the mean left edge."""
    out = bbox.clone()
    left_edges = xywh_to_ltrb(out)[..., 0]
    for batch in range(out.shape[0]):
        valid = mask[batch]
        if valid.any():
            target_left = left_edges[batch, valid].mean()
            out[batch, valid, 0] -= left_edges[batch, valid] - target_left
    return out

de_overlap

de_overlap(
    bbox: Float[Tensor, "batch elements 4"],
    mask: Bool[Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements 4"]

Reduce vertical overlaps with deterministic LayoutDETR arithmetic.

Source code in models/layout-detr/src/layout_detr/postprocessing.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def de_overlap(
    bbox: Float[torch.Tensor, "batch elements 4"],
    mask: Bool[torch.Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements 4"]:
    """Reduce vertical overlaps with deterministic LayoutDETR arithmetic."""
    out = bbox.clone()
    for batch in range(out.shape[0]):
        indexes = torch.nonzero(mask[batch], as_tuple=False).flatten().tolist()
        for i in indexes:
            for j in indexes:
                if i == j:
                    continue
                yc1, h1 = out[batch, i, 1], out[batch, i, 3]
                yc2, h2 = out[batch, j, 1], out[batch, j, 3]
                overlap = h1 / 2 + h2 / 2 - torch.abs(yc2 - yc1)
                if overlap > 0:
                    if yc1 < yc2:
                        out[batch, i, 1] -= overlap / 2
                        out[batch, j, 1] += overlap / 2
                    else:
                        out[batch, i, 1] += overlap / 2
                        out[batch, j, 1] -= overlap / 2
    return out

apply_postprocessing

apply_postprocessing(
    bbox: Float[Tensor, "batch elements 4"],
    mask: Bool[Tensor, "batch elements"],
    *,
    mode: PostprocessingMode
    | str = PostprocessingMode.none,
    jitter_strength: float = 0.0,
    generator: Generator | None = None,
) -> Float[torch.Tensor, "batch elements 4"]

Apply LayoutDETR jitter/alignment/de-overlap without rendering.

Source code in models/layout-detr/src/layout_detr/postprocessing.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def apply_postprocessing(
    bbox: Float[torch.Tensor, "batch elements 4"],
    mask: Bool[torch.Tensor, "batch elements"],
    *,
    mode: PostprocessingMode | str = PostprocessingMode.none,
    jitter_strength: float = 0.0,
    generator: torch.Generator | None = None,
) -> Float[torch.Tensor, "batch elements 4"]:
    """Apply LayoutDETR jitter/alignment/de-overlap without rendering."""
    out = jitter_boxes(bbox, strength=jitter_strength, generator=generator)
    normalized_mode = normalize_postprocessing_mode(mode)
    # Random postprocessing is intentionally omitted because the source behavior
    # is assignment-order dependent; public modes stay deterministic and explicit.
    if normalized_mode is PostprocessingMode.horizontal_center_aligned:
        out = horizontal_center_aligned(out, mask)
    elif normalized_mode is PostprocessingMode.horizontal_left_aligned:
        out = horizontal_left_aligned(out, mask)
    elif normalized_mode is not PostprocessingMode.none:
        raise ValueError(f"Unsupported out_postprocessing: {mode}")

    return clamp_boxes(de_overlap(out, mask))

processing_layout_detr

Processor for LayoutDETR content-image conditions.

LayoutDetrProcessor

Bases: ProcessorMixin

Normalize LayoutDETR image, text, label, and mask payloads.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
class LayoutDetrProcessor(ProcessorMixin):
    """Normalize LayoutDETR image, text, label, and mask payloads."""

    attributes = ["image_processor"]
    image_processor_class = "LayoutDetrImageProcessor"
    tokenizer_class = "BertTokenizerFast"
    config_name = "processor_config.json"

    def __init__(
        self,
        *,
        image_processor: LayoutDetrImageProcessor | None = None,
        config: LayoutDetrConfig,
        id2label: Mapping[int | str, str] | None = None,
    ) -> None:
        """Initialize the processor."""
        self.config = config
        self.image_processor = image_processor or LayoutDetrImageProcessor.from_config(
            self.config
        )
        label_source = (
            id2label
            if id2label is not None
            else cast(dict[int, str], self.config.id2label)
        )
        self.id2label = {
            int(k): v for k, v in cast(Mapping[int | str, str], label_source).items()
        }
        self.label2id = {v: k for k, v in self.id2label.items()}
        self.chat_template = None

    def save_pretrained(
        self,
        save_directory: str | Path,
        push_to_hub: bool = False,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Save processor metadata and image-processor config."""
        del push_to_hub, kwargs
        root = _processor_root(save_directory)
        root.mkdir(parents=True, exist_ok=True)
        _write_processor_payload(root / self.config_name, self._metadata_payload())
        self.image_processor.save_pretrained(root)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | PathLike[str],
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        subfolder: str | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> "LayoutDetrProcessor":
        """Load processor metadata from a checkpoint directory."""
        del cache_dir, force_download, local_files_only, token, revision, kwargs
        root = _processor_root(pretrained_model_name_or_path, subfolder=subfolder)
        payload = _read_processor_payload(root / cls.config_name)
        config_payload = payload.get("config", {})
        if not isinstance(config_payload, dict):
            raise TypeError("processor config payload must be a dictionary")

        id2label_payload = payload.get("id2label")
        if id2label_payload is not None and not isinstance(id2label_payload, dict):
            raise TypeError("processor id2label payload must be a dictionary")

        config = LayoutDetrConfig.from_dict(config_payload)
        image_processor = LayoutDetrImageProcessor.from_pretrained(root)
        return cls(
            image_processor=image_processor,
            config=config,
            id2label=cast(Mapping[int | str, str] | None, id2label_payload),
        )

    def _metadata_payload(
        self,
    ) -> dict[
        str,
        Mapping[str, Shaped[torch.Tensor, "..."] | int | str | float | bool | None]
        | dict[int, str]
        | str,
    ]:
        return {
            "config": self.config.to_dict(),
            "id2label": self.id2label,
            "processor_class": self.__class__.__name__,
        }

    def __call__(
        self,
        *,
        images: ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch channels height width"]
        | None = None,
        content: Mapping[
            str,
            ImageInput
            | Sequence[ImageInput]
            | Sequence[Sequence[str]]
            | Sequence[str]
            | Sequence[Sequence[int | str]]
            | Sequence[int | str],
        ]
        | None = None,
        prompt: str | Sequence[str] | None = None,
        texts: Sequence[Sequence[str]] | Sequence[str] | None = None,
        labels: Int[torch.Tensor, "batch elements"]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Sequence[Sequence[bool]]
        | Sequence[bool]
        | None = None,
        condition_type: str = "content_image",
        background_preprocessing: BackgroundPreprocessing
        | str = BackgroundPreprocessing.none,
        batch_size: int = 1,
        return_tensors: Literal["pt"] = "pt",
        canvas_size: tuple[int, int] | None = None,
    ) -> BatchEncoding:
        """Encode public inputs for the LayoutDETR model."""
        if return_tensors != "pt":
            raise ValueError("LayoutDetrProcessor only supports return_tensors='pt'")

        if condition_type not in {"content_image", "content", "image", "visual"}:
            raise NotImplementedError(
                "LayoutDETR supports only condition_type='content_image'"
            )

        content = dict(content or {})
        resolved_images = images or content.get("image") or content.get("images")
        if resolved_images is None:
            raise ValueError("LayoutDETR requires images or content['image']")

        resolved_texts = texts if texts is not None else content.get("texts")
        if resolved_texts is None:
            if prompt is not None:
                raise ValueError(
                    "LayoutDETR requires per-element texts; prompt alone is not supported"
                )

            raise ValueError("LayoutDETR requires per-element texts")

        resolved_labels = labels if labels is not None else content.get("labels")
        if resolved_labels is None:
            raise ValueError("LayoutDETR requires per-element labels")

        text_rows = _normalize_text_rows(
            cast(Sequence[Sequence[str]] | Sequence[str], resolved_texts)
        )
        label_rows = self._normalize_label_rows(
            cast(
                Int[torch.Tensor, "batch elements"]
                | Sequence[Sequence[int | str]]
                | Sequence[int | str],
                resolved_labels,
            )
        )
        if len(text_rows) != len(label_rows):
            raise ValueError("texts and labels must have the same batch size")

        if len(text_rows) == 1 and batch_size > 1:
            text_rows = text_rows * batch_size
            label_rows = label_rows * batch_size
        layout_mask = _normalize_mask_rows(mask, label_rows)
        image_features = self.image_processor.preprocess(
            resolved_images,
            background_preprocessing=background_preprocessing,
            canvas_size=canvas_size,
            return_tensors=return_tensors,
        )
        input_ids, text_attention_mask = self._tokenize_rows(text_rows)
        bbox_labels, padded_mask, padded_texts = self._pad_layout_rows(
            text_rows,
            label_rows,
            layout_mask,
        )
        text_lengths = _pad_text_lengths(text_rows, self.config.max_seq_length)
        image_batch = image_features["pixel_values"].shape[0]
        if image_batch == 1 and bbox_labels.shape[0] > 1:
            image_features["pixel_values"] = image_features["pixel_values"].expand(
                bbox_labels.shape[0], -1, -1, -1
            )
            image_features["canvas_size"] = image_features["canvas_size"].expand(
                bbox_labels.shape[0], -1
            )
        elif image_batch != bbox_labels.shape[0]:
            raise ValueError("image batch size must match texts/labels batch size")

        return BatchEncoding(
            {
                "pixel_values": image_features["pixel_values"],
                "canvas_size": image_features["canvas_size"],
                "input_ids": input_ids,
                "text_attention_mask": text_attention_mask,
                "bbox_labels": bbox_labels,
                "layout_mask": padded_mask,
                "texts": padded_texts,
                "text_lengths": text_lengths,
            }
        )

    def post_process_layouts(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"],
        *,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_intermediates: bool = False,
        intermediates: dict[str, Shaped[torch.Tensor, "..."] | list[list[str]] | str]
        | None = None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | dict[int, str]
            | dict[str, Shaped[torch.Tensor, "..."] | list[list[str]] | str]
            | None,
        ]
    ):
        """Return generated boxes in the shared layout output schema."""
        payload = LayoutGenerationOutput(
            bbox=bbox,
            labels=labels,
            mask=mask,
            id2label=self.id2label,
            intermediates=intermediates if return_intermediates else None,
        )
        if output_type == "dict":
            return dict(payload)
        if output_type != "dataclass":
            raise ValueError(f"Unsupported output_type: {output_type}")

        return payload

    def _normalize_label_rows(
        self,
        labels: Int[torch.Tensor, "batch elements"]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str],
    ) -> list[list[int]]:
        if isinstance(labels, torch.Tensor):
            tensor = labels.detach().cpu().long()
            if tensor.ndim == 1:
                tensor = tensor.unsqueeze(0)
            return [[int(value) for value in row] for row in tensor.tolist()]
        rows = _normalize_label_sequence(
            cast(Sequence[Sequence[int | str]] | Sequence[int | str], labels)
        )
        return [[self._label_to_id(label) for label in row] for row in rows]

    def _label_to_id(self, label: int | str) -> int:
        if isinstance(label, int):
            if label < 0 or label >= len(self.id2label):
                raise ValueError(f"Unknown Ad Banner label id: {label}")

            return label
        try:
            return self.label2id[label]
        except KeyError as exc:
            raise ValueError(f"Unknown Ad Banner label: {label}") from exc

    def _tokenize_rows(
        self,
        text_rows: list[list[str]],
    ) -> tuple[
        Int[torch.Tensor, "batch elements tokens"],
        Bool[torch.Tensor, "batch elements tokens"],
    ]:
        batch_ids = []
        batch_mask = []
        for row in text_rows:
            ids_row = []
            mask_row = []
            for text in row[: self.config.max_seq_length]:
                token_ids = _hash_token_ids(
                    text, self.config.max_text_length, self.config.text_vocab_size
                )
                ids_row.append(token_ids)
                mask_row.append([token_id != 0 for token_id in token_ids])
            while len(ids_row) < self.config.max_seq_length:
                ids_row.append([0] * self.config.max_text_length)
                mask_row.append([False] * self.config.max_text_length)
            batch_ids.append(ids_row)
            batch_mask.append(mask_row)
        return (
            torch.tensor(batch_ids, dtype=torch.long),
            torch.tensor(batch_mask, dtype=torch.bool),
        )

    def _pad_layout_rows(
        self,
        text_rows: list[list[str]],
        label_rows: list[list[int]],
        mask_rows: list[list[bool]],
    ) -> tuple[
        Int[torch.Tensor, "batch elements"],
        Bool[torch.Tensor, "batch elements"],
        list[list[str]],
    ]:
        labels = []
        masks = []
        texts = []
        max_len = self.config.max_seq_length
        for text_row, label_row, mask_row in zip(
            text_rows,
            label_rows,
            mask_rows,
            strict=True,
        ):
            if len(label_row) > max_len:
                raise ValueError(f"LayoutDETR supports at most {max_len} elements")

            pad = max_len - len(label_row)
            labels.append(label_row + [self.config.pad_label_id] * pad)
            masks.append(mask_row + [False] * pad)
            texts.append(text_row + [""] * pad)
        return (
            torch.tensor(labels, dtype=torch.long),
            torch.tensor(masks, dtype=torch.bool),
            texts,
        )

__init__

__init__(
    *,
    image_processor: LayoutDetrImageProcessor | None = None,
    config: LayoutDetrConfig,
    id2label: Mapping[int | str, str] | None = None,
) -> None

Initialize the processor.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def __init__(
    self,
    *,
    image_processor: LayoutDetrImageProcessor | None = None,
    config: LayoutDetrConfig,
    id2label: Mapping[int | str, str] | None = None,
) -> None:
    """Initialize the processor."""
    self.config = config
    self.image_processor = image_processor or LayoutDetrImageProcessor.from_config(
        self.config
    )
    label_source = (
        id2label
        if id2label is not None
        else cast(dict[int, str], self.config.id2label)
    )
    self.id2label = {
        int(k): v for k, v in cast(Mapping[int | str, str], label_source).items()
    }
    self.label2id = {v: k for k, v in self.id2label.items()}
    self.chat_template = None

save_pretrained

save_pretrained(
    save_directory: str | Path,
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> None

Save processor metadata and image-processor config.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
58
59
60
61
62
63
64
65
66
67
68
69
def save_pretrained(
    self,
    save_directory: str | Path,
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Save processor metadata and image-processor config."""
    del push_to_hub, kwargs
    root = _processor_root(save_directory)
    root.mkdir(parents=True, exist_ok=True)
    _write_processor_payload(root / self.config_name, self._metadata_payload())
    self.image_processor.save_pretrained(root)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    subfolder: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> "LayoutDetrProcessor"

Load processor metadata from a checkpoint directory.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    subfolder: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> "LayoutDetrProcessor":
    """Load processor metadata from a checkpoint directory."""
    del cache_dir, force_download, local_files_only, token, revision, kwargs
    root = _processor_root(pretrained_model_name_or_path, subfolder=subfolder)
    payload = _read_processor_payload(root / cls.config_name)
    config_payload = payload.get("config", {})
    if not isinstance(config_payload, dict):
        raise TypeError("processor config payload must be a dictionary")

    id2label_payload = payload.get("id2label")
    if id2label_payload is not None and not isinstance(id2label_payload, dict):
        raise TypeError("processor id2label payload must be a dictionary")

    config = LayoutDetrConfig.from_dict(config_payload)
    image_processor = LayoutDetrImageProcessor.from_pretrained(root)
    return cls(
        image_processor=image_processor,
        config=config,
        id2label=cast(Mapping[int | str, str] | None, id2label_payload),
    )

__call__

__call__(
    *,
    images: ImageInput
    | Sequence[ImageInput]
    | Float[Tensor, "batch channels height width"]
    | None = None,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Sequence[Sequence[str]]
        | Sequence[str]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    texts: Sequence[Sequence[str]]
    | Sequence[str]
    | None = None,
    labels: Int[Tensor, "batch elements"]
    | Sequence[Sequence[int | str]]
    | Sequence[int | str]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    condition_type: str = "content_image",
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    batch_size: int = 1,
    return_tensors: Literal["pt"] = "pt",
    canvas_size: tuple[int, int] | None = None,
) -> BatchEncoding

Encode public inputs for the LayoutDETR model.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
def __call__(
    self,
    *,
    images: ImageInput
    | Sequence[ImageInput]
    | Float[torch.Tensor, "batch channels height width"]
    | None = None,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Sequence[Sequence[str]]
        | Sequence[str]
        | Sequence[Sequence[int | str]]
        | Sequence[int | str],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    texts: Sequence[Sequence[str]] | Sequence[str] | None = None,
    labels: Int[torch.Tensor, "batch elements"]
    | Sequence[Sequence[int | str]]
    | Sequence[int | str]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    condition_type: str = "content_image",
    background_preprocessing: BackgroundPreprocessing
    | str = BackgroundPreprocessing.none,
    batch_size: int = 1,
    return_tensors: Literal["pt"] = "pt",
    canvas_size: tuple[int, int] | None = None,
) -> BatchEncoding:
    """Encode public inputs for the LayoutDETR model."""
    if return_tensors != "pt":
        raise ValueError("LayoutDetrProcessor only supports return_tensors='pt'")

    if condition_type not in {"content_image", "content", "image", "visual"}:
        raise NotImplementedError(
            "LayoutDETR supports only condition_type='content_image'"
        )

    content = dict(content or {})
    resolved_images = images or content.get("image") or content.get("images")
    if resolved_images is None:
        raise ValueError("LayoutDETR requires images or content['image']")

    resolved_texts = texts if texts is not None else content.get("texts")
    if resolved_texts is None:
        if prompt is not None:
            raise ValueError(
                "LayoutDETR requires per-element texts; prompt alone is not supported"
            )

        raise ValueError("LayoutDETR requires per-element texts")

    resolved_labels = labels if labels is not None else content.get("labels")
    if resolved_labels is None:
        raise ValueError("LayoutDETR requires per-element labels")

    text_rows = _normalize_text_rows(
        cast(Sequence[Sequence[str]] | Sequence[str], resolved_texts)
    )
    label_rows = self._normalize_label_rows(
        cast(
            Int[torch.Tensor, "batch elements"]
            | Sequence[Sequence[int | str]]
            | Sequence[int | str],
            resolved_labels,
        )
    )
    if len(text_rows) != len(label_rows):
        raise ValueError("texts and labels must have the same batch size")

    if len(text_rows) == 1 and batch_size > 1:
        text_rows = text_rows * batch_size
        label_rows = label_rows * batch_size
    layout_mask = _normalize_mask_rows(mask, label_rows)
    image_features = self.image_processor.preprocess(
        resolved_images,
        background_preprocessing=background_preprocessing,
        canvas_size=canvas_size,
        return_tensors=return_tensors,
    )
    input_ids, text_attention_mask = self._tokenize_rows(text_rows)
    bbox_labels, padded_mask, padded_texts = self._pad_layout_rows(
        text_rows,
        label_rows,
        layout_mask,
    )
    text_lengths = _pad_text_lengths(text_rows, self.config.max_seq_length)
    image_batch = image_features["pixel_values"].shape[0]
    if image_batch == 1 and bbox_labels.shape[0] > 1:
        image_features["pixel_values"] = image_features["pixel_values"].expand(
            bbox_labels.shape[0], -1, -1, -1
        )
        image_features["canvas_size"] = image_features["canvas_size"].expand(
            bbox_labels.shape[0], -1
        )
    elif image_batch != bbox_labels.shape[0]:
        raise ValueError("image batch size must match texts/labels batch size")

    return BatchEncoding(
        {
            "pixel_values": image_features["pixel_values"],
            "canvas_size": image_features["canvas_size"],
            "input_ids": input_ids,
            "text_attention_mask": text_attention_mask,
            "bbox_labels": bbox_labels,
            "layout_mask": padded_mask,
            "texts": padded_texts,
            "text_lengths": text_lengths,
        }
    )

post_process_layouts

post_process_layouts(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"],
    *,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    intermediates: dict[
        str, Shaped[Tensor, "..."] | list[list[str]] | str
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | list[list[str]]
            | str,
        ]
        | None,
    ]
)

Return generated boxes in the shared layout output schema.

Source code in models/layout-detr/src/layout_detr/processing_layout_detr.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
def post_process_layouts(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"],
    *,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    intermediates: dict[str, Shaped[torch.Tensor, "..."] | list[list[str]] | str]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[str, Shaped[torch.Tensor, "..."] | list[list[str]] | str]
        | None,
    ]
):
    """Return generated boxes in the shared layout output schema."""
    payload = LayoutGenerationOutput(
        bbox=bbox,
        labels=labels,
        mask=mask,
        id2label=self.id2label,
        intermediates=intermediates if return_intermediates else None,
    )
    if output_type == "dict":
        return dict(payload)
    if output_type != "dataclass":
        raise ValueError(f"Unsupported output_type: {output_type}")

    return payload

vendor_state

Vendor checkpoint extraction and state-dict conversion helpers.

LayoutDetrConversionReport

Bases: TypedDict

Structured conversion metadata persisted with converted checkpoints.

Source code in models/layout-detr/src/layout_detr/vendor_state.py
19
20
21
22
23
24
25
26
27
28
class LayoutDetrConversionReport(TypedDict):
    """Structured conversion metadata persisted with converted checkpoints."""

    source_key_count: int
    target_key_count: int
    loaded_key_count: int
    missing_keys: list[str]
    unexpected_keys: list[str]
    mismatched_shapes: list[tuple[str, tuple[int, ...], tuple[int, ...]]]
    custom_op_import_required: bool

remap_generator_key

remap_generator_key(source_key: str) -> str

Map a vendor G_ema key to the local model key when possible.

Source code in models/layout-detr/src/layout_detr/vendor_state.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def remap_generator_key(source_key: str) -> str:
    """Map a vendor ``G_ema`` key to the local model key when possible."""
    if source_key.startswith("module."):
        source_key = source_key.removeprefix("module.")
    exact = {
        "emb_label.weight": "emb_label.weight",
        "fc_z.weight": "fc_z.weight",
        "fc_z.bias": "fc_z.bias",
        "bbox_embed.layers.0.weight": "bbox_embed.0.weight",
        "bbox_embed.layers.0.bias": "bbox_embed.0.bias",
        "bbox_embed.layers.2.weight": "bbox_embed.2.weight",
        "bbox_embed.layers.2.bias": "bbox_embed.2.bias",
    }
    return exact.get(source_key, source_key)

build_conversion_report

build_conversion_report(
    source_state: Mapping[str, Shaped[Tensor, "..."]],
    target_state: Mapping[str, Shaped[Tensor, "..."]],
    remapped_state: Mapping[str, Shaped[Tensor, "..."]],
    *,
    custom_op_import_required: bool,
) -> LayoutDetrConversionReport

Build strict-load diagnostics for a remapped state dict.

Source code in models/layout-detr/src/layout_detr/vendor_state.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def build_conversion_report(
    source_state: Mapping[str, Shaped[torch.Tensor, "..."]],
    target_state: Mapping[str, Shaped[torch.Tensor, "..."]],
    remapped_state: Mapping[str, Shaped[torch.Tensor, "..."]],
    *,
    custom_op_import_required: bool,
) -> LayoutDetrConversionReport:
    """Build strict-load diagnostics for a remapped state dict."""
    missing = sorted(set(target_state).difference(remapped_state))
    unexpected = sorted(set(remapped_state).difference(target_state))
    mismatched = []
    loaded = 0
    for key, tensor in remapped_state.items():
        if key not in target_state:
            continue
        target_shape = tuple(target_state[key].shape)
        source_shape = tuple(tensor.shape)
        if source_shape != target_shape:
            mismatched.append((key, source_shape, target_shape))
        else:
            loaded += 1
    return {
        "source_key_count": len(source_state),
        "target_key_count": len(target_state),
        "loaded_key_count": loaded,
        "missing_keys": missing,
        "unexpected_keys": unexpected,
        "mismatched_shapes": mismatched,
        "custom_op_import_required": custom_op_import_required,
    }

extract_generator_state

extract_generator_state(
    pickle_path: str | Path,
    *,
    vendor_root: str | Path,
    device: str = "cpu",
) -> tuple[
    dict[str, Shaped[torch.Tensor, "..."]],
    LayoutDetrConfig,
    LayoutDetrConversionReport,
]

Extract G_ema from the original LayoutDETR pickle.

The import is isolated to the conversion path. Normal converted from_pretrained inference never imports vendor/layout-detr or torch_utils.ops.

Source code in models/layout-detr/src/layout_detr/vendor_state.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def extract_generator_state(
    pickle_path: str | Path,
    *,
    vendor_root: str | Path,
    device: str = "cpu",
) -> tuple[
    dict[str, Shaped[torch.Tensor, "..."]], LayoutDetrConfig, LayoutDetrConversionReport
]:  # pragma: no cover
    """Extract ``G_ema`` from the original LayoutDETR pickle.

    The import is isolated to the conversion path. Normal converted
    ``from_pretrained`` inference never imports ``vendor/layout-detr`` or
    ``torch_utils.ops``.
    """
    generator, custom_op_import_required = load_vendor_generator(
        pickle_path,
        vendor_root=vendor_root,
        device=device,
    )
    source_state = {
        key: value.detach().cpu() for key, value in generator.state_dict().items()
    }
    init_kwargs = dict(getattr(generator, "init_kwargs", {}) or {})
    config = LayoutDetrConfig(
        z_dim=int(getattr(generator, "z_dim", init_kwargs.get("z_dim", 4))),
        architecture="reference",
        text_vocab_size=30_524,
        bert_num_heads=4,
        bert_num_decoder_layers=2,
        max_text_length=int(
            getattr(
                generator, "max_text_length", init_kwargs.get("max_text_length", 256)
            )
        ),
        original_training_options=init_kwargs,
    )
    target_state = LayoutDetrForConditionalGeneration(config).state_dict()
    if config.architecture == "reference":
        remapped = source_state
    else:
        remapped = {
            remap_generator_key(key): value for key, value in source_state.items()
        }
    report = build_conversion_report(
        source_state,
        target_state,
        remapped,
        custom_op_import_required=custom_op_import_required,
    )
    config.conversion_report = dict(report)
    return remapped, config, report

load_vendor_generator

load_vendor_generator(
    pickle_path: str | Path,
    *,
    vendor_root: str | Path,
    device: str | device = "cpu",
) -> tuple[_VendorGeneratorStateProtocol, bool]

Load the original G_ema generator with conversion-only shims.

Source code in models/layout-detr/src/layout_detr/vendor_state.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def load_vendor_generator(
    pickle_path: str | Path,
    *,
    vendor_root: str | Path,
    device: str | torch.device = "cpu",
) -> tuple[_VendorGeneratorStateProtocol, bool]:  # pragma: no cover
    """Load the original ``G_ema`` generator with conversion-only shims."""
    vendor_path = Path(vendor_root).resolve()
    before_modules = set(sys.modules)
    with temporary_sys_path(vendor_path):
        _install_transformers_vendor_compat()
        import dnnlib  # type: ignore[import-not-found]
        import legacy  # type: ignore[import-not-found]

        _patch_legacy_unpickler(legacy)
        with dnnlib.util.open_url(str(pickle_path)) as handle:
            generator = cast(
                _VendorGeneratorStateProtocol,
                legacy.load_network_pkl(handle)["G_ema"].to(device),
            )
    custom_op_import_required = any(
        name.startswith("torch_utils.ops")
        for name in set(sys.modules).difference(before_modules)
    )
    return generator, custom_op_import_required

strict_load_converted_state

strict_load_converted_state(
    model: LayoutDetrForConditionalGeneration,
    state: Mapping[str, Shaped[Tensor, "..."]],
) -> LayoutDetrConversionReport

Strict-load a remapped state dict and return diagnostics.

Source code in models/layout-detr/src/layout_detr/vendor_state.py
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
def strict_load_converted_state(
    model: LayoutDetrForConditionalGeneration,
    state: Mapping[str, Shaped[torch.Tensor, "..."]],
) -> LayoutDetrConversionReport:
    """Strict-load a remapped state dict and return diagnostics."""
    report = build_conversion_report(
        state,
        model.state_dict(),
        state,
        custom_op_import_required=False,
    )
    if (
        report["missing_keys"]
        or report["unexpected_keys"]
        or report["mismatched_shapes"]
    ):
        raise RuntimeError(f"LayoutDETR state dict is not strict-loadable: {report}")

    model.load_state_dict(dict(state), strict=True)
    return report

temporary_sys_path

temporary_sys_path(path: Path) -> Iterator[None]

Temporarily prepend a vendor path during conversion-only imports.

Source code in models/layout-detr/src/layout_detr/vendor_state.py
287
288
289
290
291
292
293
294
295
296
297
298
@contextmanager
def temporary_sys_path(path: Path) -> Iterator[None]:
    """Temporarily prepend a vendor path during conversion-only imports."""
    raw = str(path)
    sys.path.insert(0, raw)
    try:
        yield
    finally:
        try:
            sys.path.remove(raw)
        except ValueError:
            pass