Skip to content

Ds gan

Transformers-style DS-GAN components for PosterLayout generation.

ConditionType

Bases: StrEnum

Canonical condition names used by layout generation interfaces.

Source code in lib/laygen/src/laygen/common/conditions.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
class ConditionType(StrEnum):
    """Canonical condition names used by layout generation interfaces."""

    unconditional = auto()
    label = auto()
    label_size = auto()
    completion = auto()
    refinement = auto()
    text = auto()
    content_image = auto()
    relation = auto()
    hierarchical = auto()
    retrieval = auto()

LayoutGenerationOutput dataclass

Bases: ModelOutput

Canonical layout-generation output for Transformers-style APIs.

Attributes:

Name Type Description
bbox Float[ndarray, 'batch elements 4'] | Float[Tensor, 'batch elements 4']

Normalized center xywh boxes with shape (batch, elements, 4).

labels Int[ndarray, 'batch elements'] | Int[Tensor, 'batch elements']

Dataset-local integer labels with shape (batch, elements).

mask Bool[ndarray, 'batch elements'] | Bool[Tensor, 'batch elements']

Boolean valid-element mask with shape (batch, elements).

id2label dict[int, str]

Mapping from integer label ids to display names.

sequences object | None

Optional raw token sequences.

scores object | None

Optional per-token or per-element scores.

trajectory object | None

Optional sampling trajectory.

intermediates object | None

Optional model-specific debug or auxiliary data.

Examples:

>>> import numpy as np
>>> output = LayoutGenerationOutput(
...     bbox=np.zeros((1, 1, 4), dtype=np.float32),
...     labels=np.zeros((1, 1), dtype=np.int64),
...     mask=np.ones((1, 1), dtype=bool),
...     id2label={0: "text"},
... )
>>> output["bbox"].shape
(1, 1, 4)
Source code in lib/laygen/src/laygen/modeling_outputs.py
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
@dataclass
class LayoutGenerationOutput(ModelOutput):
    """Canonical layout-generation output for Transformers-style APIs.

    Attributes:
        bbox: Normalized center ``xywh`` boxes with shape
            ``(batch, elements, 4)``.
        labels: Dataset-local integer labels with shape ``(batch, elements)``.
        mask: Boolean valid-element mask with shape ``(batch, elements)``.
        id2label: Mapping from integer label ids to display names.
        sequences: Optional raw token sequences.
        scores: Optional per-token or per-element scores.
        trajectory: Optional sampling trajectory.
        intermediates: Optional model-specific debug or auxiliary data.

    Examples:
        >>> import numpy as np
        >>> output = LayoutGenerationOutput(
        ...     bbox=np.zeros((1, 1, 4), dtype=np.float32),
        ...     labels=np.zeros((1, 1), dtype=np.int64),
        ...     mask=np.ones((1, 1), dtype=bool),
        ...     id2label={0: "text"},
        ... )
        >>> output["bbox"].shape
        (1, 1, 4)
    """

    bbox: (
        Float[np.ndarray, "batch elements 4"] | Float[torch.Tensor, "batch elements 4"]
    )
    labels: Int[np.ndarray, "batch elements"] | Int[torch.Tensor, "batch elements"] = (
        cast(
            'Int[np.ndarray, "batch elements"] | Int[torch.Tensor, "batch elements"]',
            None,
        )
    )
    mask: Bool[np.ndarray, "batch elements"] | Bool[torch.Tensor, "batch elements"] = (
        cast(
            'Bool[np.ndarray, "batch elements"] | Bool[torch.Tensor, "batch elements"]',
            None,
        )
    )
    id2label: dict[int, str] = cast(dict[int, str], None)
    sequences: object | None = None
    scores: object | None = None
    trajectory: object | None = None
    intermediates: object | None = None

DSGANConfig

Bases: PretrainedConfig

Store DS-GAN architecture and dataset metadata.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset key. DS-GAN currently supports PKU PosterLayout.

pku_posterlayout
backbone str

Timm ResNet backbone name used by the reference checkpoint.

'resnet50'
max_elem int

Maximum number of generated elements.

32
in_channels int

CNN-LSTM input channels after flattening class and box planes.

8
out_channels int

CNN-LSTM convolution output channels.

32
hidden_size int | None

Bidirectional LSTM hidden size.

None
num_layers int

Number of LSTM layers.

4
output_size int

Combined class and box output width in the internal model.

8
image_size tuple[int, int] | list[int]

Processor/model input size as (height, width).

(350, 240)
reference_canvas_size tuple[int, int] | list[int]

Reference normalization canvas as (width, height).

(513, 750)
backbone_feature_size int

Flattened ResNet-FPN spatial size. The reference default is 22 * 15 = 330 for image_size=(350, 240).

330
model_num_classes int

Internal class channels including 0 = no object.

4
id2label Id2LabelMapping | None

Public zero-based semantic label mapping.

None
model_subfolder str

Pipeline subfolder for the model component.

'model'
processor_subfolder str

Pipeline subfolder for the processor component.

'processor'

Examples:

>>> DSGANConfig().max_elem
32
Source code in models/ds-gan/src/ds_gan/configuration_ds_gan.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
class DSGANConfig(PretrainedConfig):
    """Store DS-GAN architecture and dataset metadata.

    Args:
        dataset_name: Dataset key. DS-GAN currently supports PKU PosterLayout.
        backbone: Timm ResNet backbone name used by the reference checkpoint.
        max_elem: Maximum number of generated elements.
        in_channels: CNN-LSTM input channels after flattening class and box planes.
        out_channels: CNN-LSTM convolution output channels.
        hidden_size: Bidirectional LSTM hidden size.
        num_layers: Number of LSTM layers.
        output_size: Combined class and box output width in the internal model.
        image_size: Processor/model input size as ``(height, width)``.
        reference_canvas_size: Reference normalization canvas as ``(width, height)``.
        backbone_feature_size: Flattened ResNet-FPN spatial size. The reference
            default is ``22 * 15 = 330`` for ``image_size=(350, 240)``.
        model_num_classes: Internal class channels including ``0 = no object``.
        id2label: Public zero-based semantic label mapping.
        model_subfolder: Pipeline subfolder for the model component.
        processor_subfolder: Pipeline subfolder for the processor component.

    Examples:
        >>> DSGANConfig().max_elem
        32
    """

    model_type = "ds_gan"

    def __init__(
        self,
        dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
        backbone: str = "resnet50",
        max_elem: int = 32,
        in_channels: int = 8,
        out_channels: int = 32,
        hidden_size: int | None = None,
        num_layers: int = 4,
        output_size: int = 8,
        image_size: tuple[int, int] | list[int] = (350, 240),
        reference_canvas_size: tuple[int, int] | list[int] = (513, 750),
        backbone_feature_size: int = 330,
        model_num_classes: int = 4,
        id2label: Id2LabelMapping | None = None,
        label2id: dict[str, int] | None = None,
        model_subfolder: str = "model",
        processor_subfolder: str = "processor",
        condition_types: list[str] | tuple[str, ...] | None = None,
        architectures: list[str] | None = None,
        model_type: str | None = None,
        transformers_version: str | None = None,
        torch_dtype: str | None = None,
        dtype: str | None = None,
        name_or_path: str = "",
        _commit_hash: str | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize DS-GAN configuration."""
        _ = (model_type, transformers_version)
        dataset = normalize_dataset_name(dataset_name)
        if dataset is not DatasetName.pku_posterlayout:
            raise ValueError(f"Unsupported DS-GAN dataset_name: {dataset_name}")

        public_id2label = {
            int(k): v for k, v in (id2label or _semantic_pku_id2label()).items()
        }
        public_label2id = label2id or {v: k for k, v in public_id2label.items()}
        super().__init__(
            id2label=public_id2label,
            label2id=public_label2id,
            architectures=architectures or ["DSGANModel"],
            torch_dtype=torch_dtype,  # ty: ignore[unknown-argument]
            dtype=dtype,
            name_or_path=name_or_path,  # ty: ignore[unknown-argument]
            _commit_hash=_commit_hash,  # ty: ignore[unknown-argument]
            **kwargs,  # ty: ignore[invalid-argument-type]
        )
        self.dataset_name = str(dataset)
        self.backbone = backbone
        self.max_elem = max_elem
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.hidden_size = hidden_size if hidden_size is not None else max_elem * 8
        self.num_layers = num_layers
        self.output_size = output_size
        self.image_size = tuple(int(v) for v in image_size)
        self.reference_canvas_size = tuple(int(v) for v in reference_canvas_size)
        self.backbone_feature_size = backbone_feature_size
        self.model_num_classes = model_num_classes
        self.model_subfolder = model_subfolder
        self.processor_subfolder = processor_subfolder
        self.condition_types = list(condition_types or ["content_image"])

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

public_num_labels property

public_num_labels: int

Return the number of public semantic labels.

__init__

__init__(
    dataset_name: DatasetName
    | str = DatasetName.pku_posterlayout,
    backbone: str = "resnet50",
    max_elem: int = 32,
    in_channels: int = 8,
    out_channels: int = 32,
    hidden_size: int | None = None,
    num_layers: int = 4,
    output_size: int = 8,
    image_size: tuple[int, int] | list[int] = (350, 240),
    reference_canvas_size: tuple[int, int] | list[int] = (
        513,
        750,
    ),
    backbone_feature_size: int = 330,
    model_num_classes: int = 4,
    id2label: Id2LabelMapping | None = None,
    label2id: dict[str, int] | None = None,
    model_subfolder: str = "model",
    processor_subfolder: str = "processor",
    condition_types: list[str]
    | tuple[str, ...]
    | None = None,
    architectures: list[str] | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    torch_dtype: str | None = None,
    dtype: str | None = None,
    name_or_path: str = "",
    _commit_hash: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize DS-GAN configuration.

Source code in models/ds-gan/src/ds_gan/configuration_ds_gan.py
 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
def __init__(
    self,
    dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
    backbone: str = "resnet50",
    max_elem: int = 32,
    in_channels: int = 8,
    out_channels: int = 32,
    hidden_size: int | None = None,
    num_layers: int = 4,
    output_size: int = 8,
    image_size: tuple[int, int] | list[int] = (350, 240),
    reference_canvas_size: tuple[int, int] | list[int] = (513, 750),
    backbone_feature_size: int = 330,
    model_num_classes: int = 4,
    id2label: Id2LabelMapping | None = None,
    label2id: dict[str, int] | None = None,
    model_subfolder: str = "model",
    processor_subfolder: str = "processor",
    condition_types: list[str] | tuple[str, ...] | None = None,
    architectures: list[str] | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    torch_dtype: str | None = None,
    dtype: str | None = None,
    name_or_path: str = "",
    _commit_hash: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize DS-GAN configuration."""
    _ = (model_type, transformers_version)
    dataset = normalize_dataset_name(dataset_name)
    if dataset is not DatasetName.pku_posterlayout:
        raise ValueError(f"Unsupported DS-GAN dataset_name: {dataset_name}")

    public_id2label = {
        int(k): v for k, v in (id2label or _semantic_pku_id2label()).items()
    }
    public_label2id = label2id or {v: k for k, v in public_id2label.items()}
    super().__init__(
        id2label=public_id2label,
        label2id=public_label2id,
        architectures=architectures or ["DSGANModel"],
        torch_dtype=torch_dtype,  # ty: ignore[unknown-argument]
        dtype=dtype,
        name_or_path=name_or_path,  # ty: ignore[unknown-argument]
        _commit_hash=_commit_hash,  # ty: ignore[unknown-argument]
        **kwargs,  # ty: ignore[invalid-argument-type]
    )
    self.dataset_name = str(dataset)
    self.backbone = backbone
    self.max_elem = max_elem
    self.in_channels = in_channels
    self.out_channels = out_channels
    self.hidden_size = hidden_size if hidden_size is not None else max_elem * 8
    self.num_layers = num_layers
    self.output_size = output_size
    self.image_size = tuple(int(v) for v in image_size)
    self.reference_canvas_size = tuple(int(v) for v in reference_canvas_size)
    self.backbone_feature_size = backbone_feature_size
    self.model_num_classes = model_num_classes
    self.model_subfolder = model_subfolder
    self.processor_subfolder = processor_subfolder
    self.condition_types = list(condition_types or ["content_image"])

DSGANModel

Bases: PreTrainedModel

Transformers-compatible DS-GAN generator.

Parameters:

Name Type Description Default
config DSGANConfig

DS-GAN model configuration.

required

Examples:

>>> config = DSGANConfig(backbone="resnet18", max_elem=4, hidden_size=32, num_layers=2, image_size=(64, 64), backbone_feature_size=16)
>>> model = DSGANModel(config)
>>> model.config.model_type
'ds_gan'
Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
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
class DSGANModel(PreTrainedModel):
    """Transformers-compatible DS-GAN generator.

    Args:
        config: DS-GAN model configuration.

    Examples:
        >>> config = DSGANConfig(backbone="resnet18", max_elem=4, hidden_size=32, num_layers=2, image_size=(64, 64), backbone_feature_size=16)
        >>> model = DSGANModel(config)
        >>> model.config.model_type
        'ds_gan'
    """

    config_class = DSGANConfig
    base_model_prefix = "ds_gan"
    supports_gradient_checkpointing = False

    def __init__(self, config: DSGANConfig) -> None:
        """Initialize DS-GAN generator layers."""
        super().__init__(config)
        self.resnet_fpn = ResnetBackbone(config)
        self.cnnlstm = CNNLSTM(config)
        self.fc1 = nn.Linear(2 * config.hidden_size, config.output_size // 2)
        self.fc2 = nn.Linear(2 * config.hidden_size, config.output_size // 2)
        self.post_init()

    def forward(
        self,
        pixel_values: Float[torch.Tensor, "batch 4 height width"],
        layout: Float[torch.Tensor, "batch elements 2 4"],
        return_dict: bool = True,
    ) -> (
        DSGANModelOutput
        | tuple[
            Float[torch.Tensor, "batch elements 4"],
            Float[torch.Tensor, "batch elements 4"],
        ]
    ):
        """Run a DS-GAN generator forward pass.

        Args:
            pixel_values: RGB plus saliency tensor shaped ``(B, 4, H, W)``.
            layout: Initial internal layout shaped ``(B, max_elem, 2, 4)``.
            return_dict: Whether to return a dataclass output.

        Returns:
            Raw class probabilities and normalized center ``xywh`` boxes.

        Raises:
            ValueError: If tensor shapes do not match the config.
        """
        if pixel_values.ndim != 4 or pixel_values.shape[1] != 4:
            raise ValueError("pixel_values must have shape (batch, 4, height, width)")

        expected_layout = (pixel_values.shape[0], self.config.max_elem, 2, 4)
        if tuple(layout.shape) != expected_layout:
            raise ValueError(f"layout must have shape {expected_layout}")

        pixel_values = pixel_values.to(dtype=self.dtype)
        layout = layout.to(device=pixel_values.device, dtype=self.dtype)
        h0 = self.resnet_fpn(pixel_values)
        lstm_output = self.cnnlstm(layout, h0)
        class_probs = torch.softmax(self.fc1(lstm_output), dim=-1)
        bbox = torch.sigmoid(self.fc2(lstm_output))
        if not return_dict:
            return class_probs, bbox
        return DSGANModelOutput(
            class_probs=class_probs,
            bbox=bbox,
            initial_layout=layout,
        )

__init__

__init__(config: DSGANConfig) -> None

Initialize DS-GAN generator layers.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
150
151
152
153
154
155
156
157
def __init__(self, config: DSGANConfig) -> None:
    """Initialize DS-GAN generator layers."""
    super().__init__(config)
    self.resnet_fpn = ResnetBackbone(config)
    self.cnnlstm = CNNLSTM(config)
    self.fc1 = nn.Linear(2 * config.hidden_size, config.output_size // 2)
    self.fc2 = nn.Linear(2 * config.hidden_size, config.output_size // 2)
    self.post_init()

forward

forward(
    pixel_values: Float[Tensor, "batch 4 height width"],
    layout: Float[Tensor, "batch elements 2 4"],
    return_dict: bool = True,
) -> (
    DSGANModelOutput
    | tuple[
        Float[torch.Tensor, "batch elements 4"],
        Float[torch.Tensor, "batch elements 4"],
    ]
)

Run a DS-GAN generator forward pass.

Parameters:

Name Type Description Default
pixel_values Float[Tensor, 'batch 4 height width']

RGB plus saliency tensor shaped (B, 4, H, W).

required
layout Float[Tensor, 'batch elements 2 4']

Initial internal layout shaped (B, max_elem, 2, 4).

required
return_dict bool

Whether to return a dataclass output.

True

Returns:

Type Description
DSGANModelOutput | tuple[Float[Tensor, 'batch elements 4'], Float[Tensor, 'batch elements 4']]

Raw class probabilities and normalized center xywh boxes.

Raises:

Type Description
ValueError

If tensor shapes do not match the config.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
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
def forward(
    self,
    pixel_values: Float[torch.Tensor, "batch 4 height width"],
    layout: Float[torch.Tensor, "batch elements 2 4"],
    return_dict: bool = True,
) -> (
    DSGANModelOutput
    | tuple[
        Float[torch.Tensor, "batch elements 4"],
        Float[torch.Tensor, "batch elements 4"],
    ]
):
    """Run a DS-GAN generator forward pass.

    Args:
        pixel_values: RGB plus saliency tensor shaped ``(B, 4, H, W)``.
        layout: Initial internal layout shaped ``(B, max_elem, 2, 4)``.
        return_dict: Whether to return a dataclass output.

    Returns:
        Raw class probabilities and normalized center ``xywh`` boxes.

    Raises:
        ValueError: If tensor shapes do not match the config.
    """
    if pixel_values.ndim != 4 or pixel_values.shape[1] != 4:
        raise ValueError("pixel_values must have shape (batch, 4, height, width)")

    expected_layout = (pixel_values.shape[0], self.config.max_elem, 2, 4)
    if tuple(layout.shape) != expected_layout:
        raise ValueError(f"layout must have shape {expected_layout}")

    pixel_values = pixel_values.to(dtype=self.dtype)
    layout = layout.to(device=pixel_values.device, dtype=self.dtype)
    h0 = self.resnet_fpn(pixel_values)
    lstm_output = self.cnnlstm(layout, h0)
    class_probs = torch.softmax(self.fc1(lstm_output), dim=-1)
    bbox = torch.sigmoid(self.fc2(lstm_output))
    if not return_dict:
        return class_probs, bbox
    return DSGANModelOutput(
        class_probs=class_probs,
        bbox=bbox,
        initial_layout=layout,
    )

DSGANModelOutput dataclass

Bases: ModelOutput

Raw DS-GAN generator output.

Attributes:

Name Type Description
class_probs Float[Tensor, 'batch elements 4']

Internal class probabilities with shape (batch, elements, 4) where id 0 is no object.

bbox Float[Tensor, 'batch elements 4'] | None

Normalized center xywh boxes with shape (batch, elements, 4).

initial_layout Float[Tensor, 'batch elements 2 4'] | None

Initial class/box layout passed to the generator.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@dataclass
class DSGANModelOutput(ModelOutput):
    """Raw DS-GAN generator output.

    Attributes:
        class_probs: Internal class probabilities with shape
            ``(batch, elements, 4)`` where id 0 is ``no object``.
        bbox: Normalized center ``xywh`` boxes with shape
            ``(batch, elements, 4)``.
        initial_layout: Initial class/box layout passed to the generator.
    """

    class_probs: Float[torch.Tensor, "batch elements 4"]
    bbox: Float[torch.Tensor, "batch elements 4"] | None = None
    initial_layout: Float[torch.Tensor, "batch elements 2 4"] | None = None

DSGANPipeline

Bases: LayoutGenerationPipeline

Transformers-side pipeline for content-aware PosterLayout generation.

Parameters:

Name Type Description Default
model DSGANModel

DS-GAN generator.

required
processor DSGANProcessor | None

Optional processor for images and output decoding.

None
config DSGANConfig | None

Optional root pipeline config.

None
device str | device | None

Optional runtime device.

None

Examples:

>>> config = DSGANConfig(backbone="resnet18", max_elem=4, hidden_size=32, num_layers=2, image_size=(64, 64), backbone_feature_size=16)
>>> pipe = DSGANPipeline(DSGANModel(config))
>>> pipe.config.model_type
'ds_gan'
Source code in models/ds-gan/src/ds_gan/pipeline_ds_gan.py
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
class DSGANPipeline(LayoutGenerationPipeline):
    """Transformers-side pipeline for content-aware PosterLayout generation.

    Args:
        model: DS-GAN generator.
        processor: Optional processor for images and output decoding.
        config: Optional root pipeline config.
        device: Optional runtime device.

    Examples:
        >>> config = DSGANConfig(backbone="resnet18", max_elem=4, hidden_size=32, num_layers=2, image_size=(64, 64), backbone_feature_size=16)
        >>> pipe = DSGANPipeline(DSGANModel(config))
        >>> pipe.config.model_type
        'ds_gan'
    """

    config_class: ClassVar[type[PretrainedConfig]] = DSGANConfig
    component_specs: ClassVar[dict[str, PipelineComponentSpec]] = {
        "model": PipelineComponentSpec(
            attribute_name="model",
            loader=_load_model_component,
            marker_file="config.json",
            config_subfolder_attribute="model_subfolder",
        ),
        "processor": PipelineComponentSpec(
            attribute_name="processor",
            loader=_load_processor_component,
            marker_file="processor_config.json",
            save_with_is_main_process=False,
            config_subfolder_attribute="processor_subfolder",
        ),
    }

    config: DSGANConfig
    model: DSGANModel
    processor: DSGANProcessor

    def __init__(
        self,
        model: DSGANModel,
        processor: DSGANProcessor | None = None,
        config: DSGANConfig | None = None,
        device: str | torch.device | None = None,
    ) -> None:
        """Initialize DS-GAN pipeline."""
        super().__init__(config or model.config)
        self.config = config or model.config
        self.model = model
        self.processor = processor or DSGANProcessor(
            dataset_name=self.config.dataset_name,
            id2label=cast(dict[int | str, str], self.config.id2label),
            image_size=cast(tuple[int, int], self.config.image_size),
        )
        self.model.eval()
        if device is not None:
            self.to(device)

    @classmethod
    def _from_pretrained_components(
        cls,
        *,
        config: PretrainedConfig,
        components: Mapping[str, DSGANPipelineComponent | None],
    ) -> "DSGANPipeline":
        """Build a pipeline from loaded root components."""
        return cls(
            config=cast(DSGANConfig, config),
            model=cast(DSGANModel, components["model"]),
            processor=cast(DSGANProcessor, components["processor"]),
        )

    @torch.no_grad()
    def __call__(  # ty: ignore[invalid-method-override]
        self,
        images: ImageInput
        | list[ImageInput]
        | Float[torch.Tensor, "..."]
        | 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[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[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: OutputType | str = OutputType.dataclass,
        return_intermediates: bool = False,
        saliency: ImageInput
        | list[ImageInput]
        | Float[torch.Tensor, "..."]
        | None = None,
        saliency_pfpnet: ImageInput
        | list[ImageInput]
        | Float[torch.Tensor, "..."]
        | None = None,
        saliency_basnet: ImageInput
        | list[ImageInput]
        | Float[torch.Tensor, "..."]
        | None = None,
        pixel_values: Float[torch.Tensor, "batch 4 height width"] | None = None,
        initial_layout: Float[torch.Tensor, "batch elements 2 4"] | None = None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | dict[int, str]
            | Mapping[str, Shaped[torch.Tensor, "..."] | ConditionType | str | bool]
            | None,
        ]
    ):
        """Generate layouts from content images and saliency maps.

        Args:
            images: RGB image or batch. Required unless ``pixel_values`` is given.
            batch_size: Batch size used when ``pixel_values`` is supplied.
            seed: Convenience seed. Ignored when ``generator`` is supplied.
            generator: Explicit torch generator.
            condition_type: Must normalize to ``content_image``.
            labels: Optional public labels used only with ``bbox`` to provide a
                fixed initial layout.
            bbox: Optional public boxes used with ``labels`` for a fixed layout.
            mask: Optional valid-element mask for fixed initial layouts.
            num_elements: Reserved compatibility argument.
            box_format: Format of optional ``bbox``.
            normalized: Whether optional ``bbox`` is normalized.
            canvas_size: Pixel canvas size for optional unnormalized ``bbox``.
            num_inference_steps: Reserved compatibility argument.
            output_type: ``dataclass`` or ``dict``.
            return_intermediates: Whether to include raw model tensors.
            saliency: Optional single merged saliency map.
            saliency_pfpnet: Optional PFPNet saliency map.
            saliency_basnet: Optional BASNet saliency map.
            pixel_values: Preprocessed ``(B, 4, H, W)`` tensor.
            initial_layout: Optional internal layout ``(B, max_elem, 2, 4)``.

        Returns:
            Shared layout-generation output.

        Raises:
            ValueError: If the condition, image inputs, or fixed layout are invalid.
        """
        del num_elements, num_inference_steps
        canonical = normalize_condition_type(condition_type)
        resolved_output_type = normalize_output_type(output_type)
        device = self.device or next(self.model.parameters()).device
        if pixel_values is None:
            if images is None:
                raise ValueError("images or pixel_values are required for DS-GAN")

            encoded = self.processor(
                images,
                saliency=saliency,
                saliency_pfpnet=saliency_pfpnet,
                saliency_basnet=saliency_basnet,
            )
            pixel_values = cast(torch.Tensor, encoded["pixel_values"])
        pixel_values = pixel_values.to(device=device, dtype=self.model.dtype)
        batch_size = pixel_values.shape[0] if pixel_values is not None else batch_size
        if initial_layout is None and bbox is not None and labels is not None:
            encoded_layout = self.processor.encode_layout(
                bbox=bbox,
                labels=labels,
                mask=mask,
                box_format=box_format,
                normalized=normalized,
                canvas_size=canvas_size,
                max_elem=self.config.max_elem,
            )
            initial_layout = encoded_layout["layout"]
        if initial_layout is None:
            prepared = self.prepare_generator(
                generator=generator, seed=seed, device=device
            )
            initial_layout = random_initial_layout(
                batch_size,
                self.config.max_elem,
                generator=prepared,
                device=device,
                dtype=self.model.dtype,
            )
        else:
            initial_layout = initial_layout.to(device=device, dtype=self.model.dtype)
        model_output = self.model(
            pixel_values=pixel_values,
            layout=initial_layout,
            return_dict=True,
        )
        assert isinstance(model_output, DSGANModelOutput)
        intermediates = None
        if return_intermediates:
            intermediates = {
                "condition_type": canonical,
                "initial_layout": initial_layout.detach().cpu(),
                "class_probs": model_output.class_probs.detach().cpu(),
            }
        return self.processor.decode(
            class_probs=model_output.class_probs,
            bbox=model_output.bbox,
            output_type=resolved_output_type.value,
            intermediates=intermediates,
        )

__init__

__init__(
    model: DSGANModel,
    processor: DSGANProcessor | None = None,
    config: DSGANConfig | None = None,
    device: str | device | None = None,
) -> None

Initialize DS-GAN pipeline.

Source code in models/ds-gan/src/ds_gan/pipeline_ds_gan.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def __init__(
    self,
    model: DSGANModel,
    processor: DSGANProcessor | None = None,
    config: DSGANConfig | None = None,
    device: str | torch.device | None = None,
) -> None:
    """Initialize DS-GAN pipeline."""
    super().__init__(config or model.config)
    self.config = config or model.config
    self.model = model
    self.processor = processor or DSGANProcessor(
        dataset_name=self.config.dataset_name,
        id2label=cast(dict[int | str, str], self.config.id2label),
        image_size=cast(tuple[int, int], self.config.image_size),
    )
    self.model.eval()
    if device is not None:
        self.to(device)

__call__

__call__(
    images: ImageInput
    | list[ImageInput]
    | Float[Tensor, "..."]
    | 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[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[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: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
    saliency: ImageInput
    | list[ImageInput]
    | Float[Tensor, "..."]
    | None = None,
    saliency_pfpnet: ImageInput
    | list[ImageInput]
    | Float[Tensor, "..."]
    | None = None,
    saliency_basnet: ImageInput
    | list[ImageInput]
    | Float[Tensor, "..."]
    | None = None,
    pixel_values: Float[Tensor, "batch 4 height width"]
    | None = None,
    initial_layout: Float[Tensor, "batch elements 2 4"]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | Mapping[
            str,
            Shaped[torch.Tensor, "..."]
            | ConditionType
            | str
            | bool,
        ]
        | None,
    ]
)

Generate layouts from content images and saliency maps.

Parameters:

Name Type Description Default
images ImageInput | list[ImageInput] | Float[Tensor, '...'] | None

RGB image or batch. Required unless pixel_values is given.

None
batch_size int

Batch size used when pixel_values is supplied.

1
seed int | None

Convenience seed. Ignored when generator is supplied.

None
generator Generator | None

Explicit torch generator.

None
condition_type ConditionType | str

Must normalize to content_image.

content_image
labels Int[Tensor, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional public labels used only with bbox to provide a fixed initial layout.

None
bbox Float[Tensor, 'batch elements 4'] | Sequence[ArrayLikeInput] | None

Optional public boxes used with labels for a fixed layout.

None
mask Bool[Tensor, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional valid-element mask for fixed initial layouts.

None
num_elements int | list[int] | Int[Tensor, 'batch'] | None

Reserved compatibility argument.

None
box_format BoxFormat | str

Format of optional bbox.

xywh
normalized bool

Whether optional bbox is normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size for optional unnormalized bbox.

None
num_inference_steps int | None

Reserved compatibility argument.

None
output_type OutputType | str

dataclass or dict.

dataclass
return_intermediates bool

Whether to include raw model tensors.

False
saliency ImageInput | list[ImageInput] | Float[Tensor, '...'] | None

Optional single merged saliency map.

None
saliency_pfpnet ImageInput | list[ImageInput] | Float[Tensor, '...'] | None

Optional PFPNet saliency map.

None
saliency_basnet ImageInput | list[ImageInput] | Float[Tensor, '...'] | None

Optional BASNet saliency map.

None
pixel_values Float[Tensor, 'batch 4 height width'] | None

Preprocessed (B, 4, H, W) tensor.

None
initial_layout Float[Tensor, 'batch elements 2 4'] | None

Optional internal layout (B, max_elem, 2, 4).

None

Returns:

Type Description
LayoutGenerationOutput | dict[str, Shaped[Tensor, '...'] | dict[int, str] | Mapping[str, Shaped[Tensor, '...'] | ConditionType | str | bool] | None]

Shared layout-generation output.

Raises:

Type Description
ValueError

If the condition, image inputs, or fixed layout are invalid.

Source code in models/ds-gan/src/ds_gan/pipeline_ds_gan.py
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
@torch.no_grad()
def __call__(  # ty: ignore[invalid-method-override]
    self,
    images: ImageInput
    | list[ImageInput]
    | Float[torch.Tensor, "..."]
    | 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[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[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: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
    saliency: ImageInput
    | list[ImageInput]
    | Float[torch.Tensor, "..."]
    | None = None,
    saliency_pfpnet: ImageInput
    | list[ImageInput]
    | Float[torch.Tensor, "..."]
    | None = None,
    saliency_basnet: ImageInput
    | list[ImageInput]
    | Float[torch.Tensor, "..."]
    | None = None,
    pixel_values: Float[torch.Tensor, "batch 4 height width"] | None = None,
    initial_layout: Float[torch.Tensor, "batch elements 2 4"] | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | Mapping[str, Shaped[torch.Tensor, "..."] | ConditionType | str | bool]
        | None,
    ]
):
    """Generate layouts from content images and saliency maps.

    Args:
        images: RGB image or batch. Required unless ``pixel_values`` is given.
        batch_size: Batch size used when ``pixel_values`` is supplied.
        seed: Convenience seed. Ignored when ``generator`` is supplied.
        generator: Explicit torch generator.
        condition_type: Must normalize to ``content_image``.
        labels: Optional public labels used only with ``bbox`` to provide a
            fixed initial layout.
        bbox: Optional public boxes used with ``labels`` for a fixed layout.
        mask: Optional valid-element mask for fixed initial layouts.
        num_elements: Reserved compatibility argument.
        box_format: Format of optional ``bbox``.
        normalized: Whether optional ``bbox`` is normalized.
        canvas_size: Pixel canvas size for optional unnormalized ``bbox``.
        num_inference_steps: Reserved compatibility argument.
        output_type: ``dataclass`` or ``dict``.
        return_intermediates: Whether to include raw model tensors.
        saliency: Optional single merged saliency map.
        saliency_pfpnet: Optional PFPNet saliency map.
        saliency_basnet: Optional BASNet saliency map.
        pixel_values: Preprocessed ``(B, 4, H, W)`` tensor.
        initial_layout: Optional internal layout ``(B, max_elem, 2, 4)``.

    Returns:
        Shared layout-generation output.

    Raises:
        ValueError: If the condition, image inputs, or fixed layout are invalid.
    """
    del num_elements, num_inference_steps
    canonical = normalize_condition_type(condition_type)
    resolved_output_type = normalize_output_type(output_type)
    device = self.device or next(self.model.parameters()).device
    if pixel_values is None:
        if images is None:
            raise ValueError("images or pixel_values are required for DS-GAN")

        encoded = self.processor(
            images,
            saliency=saliency,
            saliency_pfpnet=saliency_pfpnet,
            saliency_basnet=saliency_basnet,
        )
        pixel_values = cast(torch.Tensor, encoded["pixel_values"])
    pixel_values = pixel_values.to(device=device, dtype=self.model.dtype)
    batch_size = pixel_values.shape[0] if pixel_values is not None else batch_size
    if initial_layout is None and bbox is not None and labels is not None:
        encoded_layout = self.processor.encode_layout(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
            max_elem=self.config.max_elem,
        )
        initial_layout = encoded_layout["layout"]
    if initial_layout is None:
        prepared = self.prepare_generator(
            generator=generator, seed=seed, device=device
        )
        initial_layout = random_initial_layout(
            batch_size,
            self.config.max_elem,
            generator=prepared,
            device=device,
            dtype=self.model.dtype,
        )
    else:
        initial_layout = initial_layout.to(device=device, dtype=self.model.dtype)
    model_output = self.model(
        pixel_values=pixel_values,
        layout=initial_layout,
        return_dict=True,
    )
    assert isinstance(model_output, DSGANModelOutput)
    intermediates = None
    if return_intermediates:
        intermediates = {
            "condition_type": canonical,
            "initial_layout": initial_layout.detach().cpu(),
            "class_probs": model_output.class_probs.detach().cpu(),
        }
    return self.processor.decode(
        class_probs=model_output.class_probs,
        bbox=model_output.bbox,
        output_type=resolved_output_type.value,
        intermediates=intermediates,
    )

OutputType

Bases: StrEnum

Supported DS-GAN pipeline output containers.

Source code in models/ds-gan/src/ds_gan/pipeline_ds_gan.py
36
37
38
39
40
class OutputType(StrEnum):
    """Supported DS-GAN pipeline output containers."""

    dataclass = auto()
    dict = auto()

DSGANProcessor

Bases: ProcessorMixin

Prepare PosterLayout RGB/saliency inputs and decode DS-GAN outputs.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset key. Only PKU PosterLayout is supported.

pku_posterlayout
id2label dict[int | str, str] | None

Public semantic labels excluding model no object.

None
image_size tuple[int, int] | list[int]

Resize target as (height, width).

(350, 240)

Examples:

>>> processor = DSGANProcessor()
>>> processor.id2label[0]
'text'
Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
 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
class DSGANProcessor(ProcessorMixin):
    """Prepare PosterLayout RGB/saliency inputs and decode DS-GAN outputs.

    Args:
        dataset_name: Dataset key. Only PKU PosterLayout is supported.
        id2label: Public semantic labels excluding model ``no object``.
        image_size: Resize target as ``(height, width)``.

    Examples:
        >>> processor = DSGANProcessor()
        >>> processor.id2label[0]
        'text'
    """

    config_name = "processor_config.json"

    def __init__(
        self,
        dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
        id2label: dict[int | str, str] | None = None,
        image_size: tuple[int, int] | list[int] = (350, 240),
    ) -> None:
        """Initialize processor metadata."""
        self.chat_template = None
        dataset = normalize_dataset_name(dataset_name)
        if dataset is not DatasetName.pku_posterlayout:
            raise ValueError(f"Unsupported DS-GAN dataset_name: {dataset_name}")

        self.dataset_name = str(dataset)
        default_id2label = {0: "text", 1: "logo", 2: "underlay"}
        raw_id2label = id2label or default_id2label
        self.id2label = {int(k): v for k, v in raw_id2label.items()}
        self.label2id = {v: k for k, v in self.id2label.items()}
        height, width = image_size
        self.image_size: tuple[int, int] = (int(height), int(width))

    def __call__(
        self,
        images: DSGANImageInput | Sequence[DSGANImageInput],
        *,
        saliency: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
        saliency_pfpnet: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
        saliency_basnet: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
        return_tensors: Literal["pt"] = "pt",
    ) -> BatchEncoding:
        """Encode content images into ``pixel_values``.

        Args:
            images: RGB image or batch of RGB images.
            saliency: Optional saliency image or batch. If omitted and both
                saliency maps are given, the maps are merged by pixelwise max.
            saliency_pfpnet: Optional PFPNet saliency map.
            saliency_basnet: Optional BASNet saliency map.
            return_tensors: Tensor framework. Only ``pt`` is supported.

        Returns:
            Batch encoding containing ``pixel_values`` shaped ``(B, 4, H, W)``.
        """
        if return_tensors != "pt":
            raise ValueError("DSGANProcessor only supports return_tensors='pt'")

        image_rows = _ensure_batch(images)
        saliency_rows = self._resolve_saliency(
            len(image_rows),
            saliency=saliency,
            saliency_pfpnet=saliency_pfpnet,
            saliency_basnet=saliency_basnet,
        )
        tensors = []
        for image, sal in zip(image_rows, saliency_rows, strict=True):
            rgb = _to_rgb_tensor(image, self.image_size)
            sal_tensor = (
                torch.zeros(1, *self.image_size, dtype=torch.float32)
                if sal is None
                else _to_l_tensor(sal, self.image_size)
            )
            tensors.append(torch.cat((rgb, sal_tensor), dim=0))
        return BatchEncoding({"pixel_values": torch.stack(tensors)})

    def decode(
        self,
        *,
        class_probs: Float[torch.Tensor, "batch elements 4"],
        bbox: Float[torch.Tensor, "batch elements 4"],
        output_type: Literal["dataclass", "dict"] = "dataclass",
        scores: Float[torch.Tensor, "batch elements"] | None = None,
        intermediates: Mapping[str, Shaped[torch.Tensor, "..."] | str | bool]
        | None = None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | dict[int, str]
            | Mapping[str, Shaped[torch.Tensor, "..."] | str | bool]
            | None,
        ]
    ):
        """Decode raw DS-GAN class probabilities and boxes.

        Args:
            class_probs: Internal class probabilities shaped ``(B, E, 4)``.
            bbox: Normalized center ``xywh`` boxes shaped ``(B, E, 4)``.
            output_type: Return format.
            scores: Optional per-element class scores.
            intermediates: Optional model-specific intermediate tensors.

        Returns:
            Shared layout output with public labels and mask semantics.
        """
        class_ids = torch.argmax(class_probs, dim=-1)
        mask = class_ids != 0
        public_labels = (class_ids - 1).clamp_min(0).long()
        resolved_scores = scores
        if resolved_scores is None:
            resolved_scores = class_probs.max(dim=-1).values
        output = LayoutGenerationOutput(
            bbox=bbox.detach().cpu().clamp(0.0, 1.0),
            labels=public_labels.detach().cpu(),
            mask=mask.detach().cpu(),
            id2label=dict(self.id2label),
            scores=resolved_scores.detach().cpu(),
            intermediates=intermediates,
        )
        if output_type == "dict":
            return dict(output)
        if output_type == "dataclass":
            return output
        raise ValueError(f"Unsupported output_type: {output_type}")

    def encode_layout(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput],
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput],
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        max_elem: int = 32,
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Encode public boxes/labels into the internal layout tensor.

        Args:
            bbox: Public boxes.
            labels: Public zero-based semantic labels.
            mask: Optional valid-element mask.
            box_format: Input box format.
            normalized: Whether the boxes are normalized.
            canvas_size: Pixel canvas size used when ``normalized=False``.
            max_elem: Output slot count.

        Returns:
            Dictionary with internal ``layout``, normalized ``bbox``, labels, and mask.
        """
        bbox_t, labels_t, mask_t = prepare_layout_tensors(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
            clamp_converted_normalized=True,
        )
        bbox_t, labels_t, mask_t = self.pad(bbox_t, labels_t, mask_t, max_elem=max_elem)
        model_labels = torch.zeros_like(labels_t)
        model_labels[mask_t] = labels_t[mask_t] + 1
        class_one_hot = torch.nn.functional.one_hot(model_labels, num_classes=4).to(
            dtype=bbox_t.dtype
        )
        layout = torch.stack((class_one_hot, bbox_t), dim=2)
        return {"layout": layout, "bbox": bbox_t, "labels": labels_t, "mask": mask_t}

    def pad(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"],
        *,
        max_elem: int,
    ) -> tuple[
        Float[torch.Tensor, "batch padded_elements 4"],
        Int[torch.Tensor, "batch padded_elements"],
        Bool[torch.Tensor, "batch padded_elements"],
    ]:
        """Pad layout tensors to DS-GAN ``max_elem`` slots."""
        if bbox.shape[1] > max_elem:
            raise ValueError(f"DS-GAN supports at most {max_elem} elements")

        pad_count = max_elem - bbox.shape[1]
        if pad_count:
            bbox = torch.cat((bbox, torch.zeros(bbox.shape[0], pad_count, 4)), dim=1)
            labels = torch.cat(
                (labels, torch.zeros(labels.shape[0], pad_count, dtype=torch.long)),
                dim=1,
            )
            mask = torch.cat(
                (mask, torch.zeros(mask.shape[0], pad_count, dtype=torch.bool)),
                dim=1,
            )
        labels = labels.clone()
        labels[~mask] = 0
        return bbox, labels, mask

    def _resolve_saliency(
        self,
        batch_size: int,
        *,
        saliency: DSGANImageInput | Sequence[DSGANImageInput] | None,
        saliency_pfpnet: DSGANImageInput | Sequence[DSGANImageInput] | None,
        saliency_basnet: DSGANImageInput | Sequence[DSGANImageInput] | None,
    ) -> list[DSGANImageInput | Float[torch.Tensor, "1 height width"] | None]:
        if saliency is not None:
            rows = _ensure_batch(saliency)
            if len(rows) != batch_size:
                raise ValueError("saliency batch size must match images")

            return cast(
                list[DSGANImageInput | Float[torch.Tensor, "1 height width"] | None],
                rows,
            )
        if saliency_pfpnet is None and saliency_basnet is None:
            return [None] * batch_size
        first = (
            _ensure_batch(saliency_pfpnet)
            if saliency_pfpnet is not None
            else [None] * batch_size
        )
        second = (
            _ensure_batch(saliency_basnet)
            if saliency_basnet is not None
            else [None] * batch_size
        )
        if len(first) != batch_size or len(second) != batch_size:
            raise ValueError("saliency batch size must match images")

        merged: list[Float[torch.Tensor, "1 height width"]] = []
        for left, right in zip(first, second, strict=True):
            if left is None:
                merged.append(_to_l_tensor(right, self.image_size))
            elif right is None:
                merged.append(_to_l_tensor(left, self.image_size))
            else:
                merged.append(_merge_saliency_native(left, right, self.image_size))
        return cast(
            list[DSGANImageInput | Float[torch.Tensor, "1 height width"] | None], merged
        )

__init__

__init__(
    dataset_name: DatasetName
    | str = DatasetName.pku_posterlayout,
    id2label: dict[int | str, str] | None = None,
    image_size: tuple[int, int] | list[int] = (350, 240),
) -> None

Initialize processor metadata.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __init__(
    self,
    dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
    id2label: dict[int | str, str] | None = None,
    image_size: tuple[int, int] | list[int] = (350, 240),
) -> None:
    """Initialize processor metadata."""
    self.chat_template = None
    dataset = normalize_dataset_name(dataset_name)
    if dataset is not DatasetName.pku_posterlayout:
        raise ValueError(f"Unsupported DS-GAN dataset_name: {dataset_name}")

    self.dataset_name = str(dataset)
    default_id2label = {0: "text", 1: "logo", 2: "underlay"}
    raw_id2label = id2label or default_id2label
    self.id2label = {int(k): v for k, v in raw_id2label.items()}
    self.label2id = {v: k for k, v in self.id2label.items()}
    height, width = image_size
    self.image_size: tuple[int, int] = (int(height), int(width))

__call__

__call__(
    images: DSGANImageInput | Sequence[DSGANImageInput],
    *,
    saliency: DSGANImageInput
    | Sequence[DSGANImageInput]
    | None = None,
    saliency_pfpnet: DSGANImageInput
    | Sequence[DSGANImageInput]
    | None = None,
    saliency_basnet: DSGANImageInput
    | Sequence[DSGANImageInput]
    | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding

Encode content images into pixel_values.

Parameters:

Name Type Description Default
images DSGANImageInput | Sequence[DSGANImageInput]

RGB image or batch of RGB images.

required
saliency DSGANImageInput | Sequence[DSGANImageInput] | None

Optional saliency image or batch. If omitted and both saliency maps are given, the maps are merged by pixelwise max.

None
saliency_pfpnet DSGANImageInput | Sequence[DSGANImageInput] | None

Optional PFPNet saliency map.

None
saliency_basnet DSGANImageInput | Sequence[DSGANImageInput] | None

Optional BASNet saliency map.

None
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

'pt'

Returns:

Type Description
BatchEncoding

Batch encoding containing pixel_values shaped (B, 4, H, W).

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
 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
def __call__(
    self,
    images: DSGANImageInput | Sequence[DSGANImageInput],
    *,
    saliency: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
    saliency_pfpnet: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
    saliency_basnet: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding:
    """Encode content images into ``pixel_values``.

    Args:
        images: RGB image or batch of RGB images.
        saliency: Optional saliency image or batch. If omitted and both
            saliency maps are given, the maps are merged by pixelwise max.
        saliency_pfpnet: Optional PFPNet saliency map.
        saliency_basnet: Optional BASNet saliency map.
        return_tensors: Tensor framework. Only ``pt`` is supported.

    Returns:
        Batch encoding containing ``pixel_values`` shaped ``(B, 4, H, W)``.
    """
    if return_tensors != "pt":
        raise ValueError("DSGANProcessor only supports return_tensors='pt'")

    image_rows = _ensure_batch(images)
    saliency_rows = self._resolve_saliency(
        len(image_rows),
        saliency=saliency,
        saliency_pfpnet=saliency_pfpnet,
        saliency_basnet=saliency_basnet,
    )
    tensors = []
    for image, sal in zip(image_rows, saliency_rows, strict=True):
        rgb = _to_rgb_tensor(image, self.image_size)
        sal_tensor = (
            torch.zeros(1, *self.image_size, dtype=torch.float32)
            if sal is None
            else _to_l_tensor(sal, self.image_size)
        )
        tensors.append(torch.cat((rgb, sal_tensor), dim=0))
    return BatchEncoding({"pixel_values": torch.stack(tensors)})

decode

decode(
    *,
    class_probs: Float[Tensor, "batch elements 4"],
    bbox: Float[Tensor, "batch elements 4"],
    output_type: Literal["dataclass", "dict"] = "dataclass",
    scores: Float[Tensor, "batch elements"] | None = None,
    intermediates: Mapping[
        str, Shaped[Tensor, "..."] | str | bool
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | Mapping[
            str, Shaped[torch.Tensor, "..."] | str | bool
        ]
        | None,
    ]
)

Decode raw DS-GAN class probabilities and boxes.

Parameters:

Name Type Description Default
class_probs Float[Tensor, 'batch elements 4']

Internal class probabilities shaped (B, E, 4).

required
bbox Float[Tensor, 'batch elements 4']

Normalized center xywh boxes shaped (B, E, 4).

required
output_type Literal['dataclass', 'dict']

Return format.

'dataclass'
scores Float[Tensor, 'batch elements'] | None

Optional per-element class scores.

None
intermediates Mapping[str, Shaped[Tensor, '...'] | str | bool] | None

Optional model-specific intermediate tensors.

None

Returns:

Type Description
LayoutGenerationOutput | dict[str, Shaped[Tensor, '...'] | dict[int, str] | Mapping[str, Shaped[Tensor, '...'] | str | bool] | None]

Shared layout output with public labels and mask semantics.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
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
def decode(
    self,
    *,
    class_probs: Float[torch.Tensor, "batch elements 4"],
    bbox: Float[torch.Tensor, "batch elements 4"],
    output_type: Literal["dataclass", "dict"] = "dataclass",
    scores: Float[torch.Tensor, "batch elements"] | None = None,
    intermediates: Mapping[str, Shaped[torch.Tensor, "..."] | str | bool]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | Mapping[str, Shaped[torch.Tensor, "..."] | str | bool]
        | None,
    ]
):
    """Decode raw DS-GAN class probabilities and boxes.

    Args:
        class_probs: Internal class probabilities shaped ``(B, E, 4)``.
        bbox: Normalized center ``xywh`` boxes shaped ``(B, E, 4)``.
        output_type: Return format.
        scores: Optional per-element class scores.
        intermediates: Optional model-specific intermediate tensors.

    Returns:
        Shared layout output with public labels and mask semantics.
    """
    class_ids = torch.argmax(class_probs, dim=-1)
    mask = class_ids != 0
    public_labels = (class_ids - 1).clamp_min(0).long()
    resolved_scores = scores
    if resolved_scores is None:
        resolved_scores = class_probs.max(dim=-1).values
    output = LayoutGenerationOutput(
        bbox=bbox.detach().cpu().clamp(0.0, 1.0),
        labels=public_labels.detach().cpu(),
        mask=mask.detach().cpu(),
        id2label=dict(self.id2label),
        scores=resolved_scores.detach().cpu(),
        intermediates=intermediates,
    )
    if output_type == "dict":
        return dict(output)
    if output_type == "dataclass":
        return output
    raise ValueError(f"Unsupported output_type: {output_type}")

encode_layout

encode_layout(
    *,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    max_elem: int = 32,
) -> dict[str, Shaped[torch.Tensor, "..."]]

Encode public boxes/labels into the internal layout tensor.

Parameters:

Name Type Description Default
bbox Float[Tensor, 'batch elements 4'] | Float[ndarray, 'batch elements 4'] | Sequence[ArrayLikeInput]

Public boxes.

required
labels Int[Tensor, 'batch elements'] | Int[ndarray, 'batch elements'] | Sequence[ArrayLikeInput]

Public zero-based semantic labels.

required
mask Bool[Tensor, 'batch elements'] | Bool[ndarray, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional valid-element mask.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether the boxes are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size used when normalized=False.

None
max_elem int

Output slot count.

32

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Dictionary with internal layout, normalized bbox, labels, and mask.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
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
def encode_layout(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    max_elem: int = 32,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Encode public boxes/labels into the internal layout tensor.

    Args:
        bbox: Public boxes.
        labels: Public zero-based semantic labels.
        mask: Optional valid-element mask.
        box_format: Input box format.
        normalized: Whether the boxes are normalized.
        canvas_size: Pixel canvas size used when ``normalized=False``.
        max_elem: Output slot count.

    Returns:
        Dictionary with internal ``layout``, normalized ``bbox``, labels, and mask.
    """
    bbox_t, labels_t, mask_t = prepare_layout_tensors(
        bbox=bbox,
        labels=labels,
        mask=mask,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
        clamp_converted_normalized=True,
    )
    bbox_t, labels_t, mask_t = self.pad(bbox_t, labels_t, mask_t, max_elem=max_elem)
    model_labels = torch.zeros_like(labels_t)
    model_labels[mask_t] = labels_t[mask_t] + 1
    class_one_hot = torch.nn.functional.one_hot(model_labels, num_classes=4).to(
        dtype=bbox_t.dtype
    )
    layout = torch.stack((class_one_hot, bbox_t), dim=2)
    return {"layout": layout, "bbox": bbox_t, "labels": labels_t, "mask": mask_t}

pad

pad(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"],
    *,
    max_elem: int,
) -> tuple[
    Float[torch.Tensor, "batch padded_elements 4"],
    Int[torch.Tensor, "batch padded_elements"],
    Bool[torch.Tensor, "batch padded_elements"],
]

Pad layout tensors to DS-GAN max_elem slots.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
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
def pad(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"],
    *,
    max_elem: int,
) -> tuple[
    Float[torch.Tensor, "batch padded_elements 4"],
    Int[torch.Tensor, "batch padded_elements"],
    Bool[torch.Tensor, "batch padded_elements"],
]:
    """Pad layout tensors to DS-GAN ``max_elem`` slots."""
    if bbox.shape[1] > max_elem:
        raise ValueError(f"DS-GAN supports at most {max_elem} elements")

    pad_count = max_elem - bbox.shape[1]
    if pad_count:
        bbox = torch.cat((bbox, torch.zeros(bbox.shape[0], pad_count, 4)), dim=1)
        labels = torch.cat(
            (labels, torch.zeros(labels.shape[0], pad_count, dtype=torch.long)),
            dim=1,
        )
        mask = torch.cat(
            (mask, torch.zeros(mask.shape[0], pad_count, dtype=torch.bool)),
            dim=1,
        )
    labels = labels.clone()
    labels[~mask] = 0
    return bbox, labels, mask

default_ds_gan_config

default_ds_gan_config() -> DSGANConfig

Return the reference-compatible DS-GAN default configuration.

Examples:

>>> default_ds_gan_config().backbone
'resnet50'
Source code in models/ds-gan/src/ds_gan/configuration_ds_gan.py
125
126
127
128
129
130
131
132
def default_ds_gan_config() -> DSGANConfig:
    """Return the reference-compatible DS-GAN default configuration.

    Examples:
        >>> default_ds_gan_config().backbone
        'resnet50'
    """
    return DSGANConfig()

convert_vendor_state_dict

convert_vendor_state_dict(
    state_dict: Mapping[str, Shaped[Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]

Convert vendor DS-GAN generator keys to DSGANModel keys.

The released checkpoint was commonly saved from torch.nn.DataParallel; this helper strips the leading module. prefix and keeps all generator module names otherwise unchanged.

Parameters:

Name Type Description Default
state_dict Mapping[str, Shaped[Tensor, '...']]

Original checkpoint mapping.

required

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Converted state dictionary.

Examples:

>>> convert_vendor_state_dict({"module.fc1.weight": torch.zeros(1)})["fc1.weight"].shape
torch.Size([1])
Source code in models/ds-gan/src/ds_gan/conversion.py
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
def convert_vendor_state_dict(
    state_dict: Mapping[str, Shaped[torch.Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Convert vendor DS-GAN generator keys to ``DSGANModel`` keys.

    The released checkpoint was commonly saved from ``torch.nn.DataParallel``;
    this helper strips the leading ``module.`` prefix and keeps all generator
    module names otherwise unchanged.

    Args:
        state_dict: Original checkpoint mapping.

    Returns:
        Converted state dictionary.

    Examples:
        >>> convert_vendor_state_dict({"module.fc1.weight": torch.zeros(1)})["fc1.weight"].shape
        torch.Size([1])
    """
    converted: dict[str, Shaped[torch.Tensor, "..."]] = {}
    for key, value in state_dict.items():
        name = key.removeprefix("module.")
        name = name.removeprefix("generator.")
        converted[name] = value
    return converted

random_initial_layout

random_initial_layout(
    batch_size: int,
    max_elem: int,
    *,
    generator: Generator | None = None,
    seed: int | None = None,
    device: device | str | None = None,
    dtype: dtype = torch.float32,
    weighted_classes: bool = True,
    use_numpy_classes: bool = False,
) -> Float[torch.Tensor, "batch elements 2 4"]

Sample the DS-GAN initial layout tensor.

Parameters:

Name Type Description Default
batch_size int

Batch size.

required
max_elem int

Number of layout slots.

required
generator Generator | None

Optional torch generator. Takes precedence over seed.

None
seed int | None

Convenience seed used only when generator is absent.

None
device device | str | None

Target torch device.

None
dtype dtype

Target floating dtype.

float32
weighted_classes bool

Whether to use the released inference class prior.

True
use_numpy_classes bool

Use NumPy's legacy RandomState class sampler to mirror the reference script's weighted class prior when seed is supplied. Torch box sampling still follows generator or seed.

False

Returns:

Type Description
Float[Tensor, 'batch elements 2 4']

Tensor shaped (batch, max_elem, 2, 4).

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
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
def random_initial_layout(
    batch_size: int,
    max_elem: int,
    *,
    generator: torch.Generator | None = None,
    seed: int | None = None,
    device: torch.device | str | None = None,
    dtype: torch.dtype = torch.float32,
    weighted_classes: bool = True,
    use_numpy_classes: bool = False,
) -> Float[torch.Tensor, "batch elements 2 4"]:
    """Sample the DS-GAN initial layout tensor.

    Args:
        batch_size: Batch size.
        max_elem: Number of layout slots.
        generator: Optional torch generator. Takes precedence over ``seed``.
        seed: Convenience seed used only when ``generator`` is absent.
        device: Target torch device.
        dtype: Target floating dtype.
        weighted_classes: Whether to use the released inference class prior.
        use_numpy_classes: Use NumPy's legacy ``RandomState`` class sampler to
            mirror the reference script's weighted class prior when ``seed`` is
            supplied. Torch box sampling still follows ``generator`` or ``seed``.

    Returns:
        Tensor shaped ``(batch, max_elem, 2, 4)``.
    """
    resolved_device = (
        torch.device(device) if device is not None else torch.device("cpu")
    )
    if generator is None and seed is not None:
        generator = torch.Generator(device=resolved_device).manual_seed(seed)
    if weighted_classes:
        probs = torch.tensor((0.1, 0.8, 1.0, 1.0), device=resolved_device)
        probs = probs / probs.sum()
    else:
        probs = torch.full((4,), 0.25, device=resolved_device)
    if use_numpy_classes:
        rng = np.random.RandomState(seed)
        np_probs = probs.detach().cpu().numpy()
        class_ids = torch.as_tensor(
            rng.choice(4, size=(batch_size, max_elem, 1), p=np_probs),
            dtype=torch.long,
            device=resolved_device,
        )
    else:
        class_ids = torch.multinomial(
            probs,
            num_samples=batch_size * max_elem,
            replacement=True,
            generator=generator,
        ).reshape(batch_size, max_elem, 1)
    class_one_hot = torch.zeros(
        batch_size,
        max_elem,
        4,
        dtype=dtype,
        device=resolved_device,
    )
    class_one_hot.scatter_(-1, class_ids, 1)
    box_xyxy = torch.normal(
        mean=0.5,
        std=0.15,
        size=(batch_size, max_elem, 1, 4),
        generator=generator,
        device=resolved_device,
        dtype=dtype,
    )
    bbox = xyxy_to_xywh(box_xyxy)
    return torch.concat([class_one_hot.unsqueeze(2), bbox], dim=2)

annotations_from_pku_example

annotations_from_pku_example(
    example: Mapping[str, DSGANExampleValue],
    *,
    max_elem: int = 32,
) -> dict[
    str, Shaped[torch.Tensor, "..."] | tuple[int, int]
]

Convert a PKU PosterLayout dataset row into public layout tensors.

The adapter filters INVALID annotations, converts pixel ltrb boxes to normalized center xywh, derives canvas size from the image columns, and applies the reference designSeq.reorder ordering policy.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
def annotations_from_pku_example(
    example: Mapping[str, DSGANExampleValue],
    *,
    max_elem: int = 32,
) -> dict[str, Shaped[torch.Tensor, "..."] | tuple[int, int]]:
    """Convert a PKU PosterLayout dataset row into public layout tensors.

    The adapter filters ``INVALID`` annotations, converts pixel ``ltrb`` boxes
    to normalized center ``xywh``, derives canvas size from the image columns,
    and applies the reference ``designSeq.reorder`` ordering policy.
    """
    annotations = cast(
        Mapping[
            str, Sequence[str | int | float] | Sequence[Sequence[str | int | float]]
        ],
        example.get("annotations", example),
    )
    raw_labels = cast(Sequence[str | int | float], annotations["cls_elem"])
    raw_boxes = cast(
        Sequence[str | Sequence[int | float | str]], annotations["box_elem"]
    )
    canvas_size = _canvas_size_from_example(example)
    model_labels: list[int] = []
    public_labels: list[int] = []
    boxes: list[list[float]] = []

    for raw_label, raw_box in zip(raw_labels, raw_boxes, strict=True):
        label = str(raw_label)
        if label == "INVALID":
            continue
        public_id = PKU_DATASET_LABEL2ID[label]
        model_labels.append(PKU_MODEL_LABEL2ID[label])
        public_labels.append(public_id)
        boxes.append(_parse_box(raw_box))

    if boxes:
        box_t = torch.tensor(boxes, dtype=torch.float32)
        order = _designseq_reorder(model_labels, box_t, max_elem=max_elem)
        box_t = box_t[order]
        labels_t = torch.tensor([public_labels[i] for i in order], dtype=torch.long)
        bbox_t = normalize_boxes(
            box_t.unsqueeze(0), canvas_size=canvas_size, box_format="ltrb"
        ).squeeze(0)
    else:
        bbox_t = torch.zeros(0, 4, dtype=torch.float32)
        labels_t = torch.zeros(0, dtype=torch.long)
    mask_t = torch.ones(labels_t.shape, dtype=torch.bool)
    return {
        "bbox": bbox_t.unsqueeze(0),
        "labels": labels_t.unsqueeze(0),
        "mask": mask_t.unsqueeze(0),
        "canvas_size": canvas_size,
    }

processor_for_dataset

processor_for_dataset(
    dataset_name: DatasetName | str,
) -> DSGANProcessor

Create a DS-GAN processor for a supported dataset.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
303
304
305
def processor_for_dataset(dataset_name: DatasetName | str) -> DSGANProcessor:
    """Create a DS-GAN processor for a supported dataset."""
    return DSGANProcessor(dataset_name=dataset_name)

configuration_ds_gan

Configuration for converted DS-GAN checkpoints.

DSGANConfig

Bases: PretrainedConfig

Store DS-GAN architecture and dataset metadata.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset key. DS-GAN currently supports PKU PosterLayout.

pku_posterlayout
backbone str

Timm ResNet backbone name used by the reference checkpoint.

'resnet50'
max_elem int

Maximum number of generated elements.

32
in_channels int

CNN-LSTM input channels after flattening class and box planes.

8
out_channels int

CNN-LSTM convolution output channels.

32
hidden_size int | None

Bidirectional LSTM hidden size.

None
num_layers int

Number of LSTM layers.

4
output_size int

Combined class and box output width in the internal model.

8
image_size tuple[int, int] | list[int]

Processor/model input size as (height, width).

(350, 240)
reference_canvas_size tuple[int, int] | list[int]

Reference normalization canvas as (width, height).

(513, 750)
backbone_feature_size int

Flattened ResNet-FPN spatial size. The reference default is 22 * 15 = 330 for image_size=(350, 240).

330
model_num_classes int

Internal class channels including 0 = no object.

4
id2label Id2LabelMapping | None

Public zero-based semantic label mapping.

None
model_subfolder str

Pipeline subfolder for the model component.

'model'
processor_subfolder str

Pipeline subfolder for the processor component.

'processor'

Examples:

>>> DSGANConfig().max_elem
32
Source code in models/ds-gan/src/ds_gan/configuration_ds_gan.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
class DSGANConfig(PretrainedConfig):
    """Store DS-GAN architecture and dataset metadata.

    Args:
        dataset_name: Dataset key. DS-GAN currently supports PKU PosterLayout.
        backbone: Timm ResNet backbone name used by the reference checkpoint.
        max_elem: Maximum number of generated elements.
        in_channels: CNN-LSTM input channels after flattening class and box planes.
        out_channels: CNN-LSTM convolution output channels.
        hidden_size: Bidirectional LSTM hidden size.
        num_layers: Number of LSTM layers.
        output_size: Combined class and box output width in the internal model.
        image_size: Processor/model input size as ``(height, width)``.
        reference_canvas_size: Reference normalization canvas as ``(width, height)``.
        backbone_feature_size: Flattened ResNet-FPN spatial size. The reference
            default is ``22 * 15 = 330`` for ``image_size=(350, 240)``.
        model_num_classes: Internal class channels including ``0 = no object``.
        id2label: Public zero-based semantic label mapping.
        model_subfolder: Pipeline subfolder for the model component.
        processor_subfolder: Pipeline subfolder for the processor component.

    Examples:
        >>> DSGANConfig().max_elem
        32
    """

    model_type = "ds_gan"

    def __init__(
        self,
        dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
        backbone: str = "resnet50",
        max_elem: int = 32,
        in_channels: int = 8,
        out_channels: int = 32,
        hidden_size: int | None = None,
        num_layers: int = 4,
        output_size: int = 8,
        image_size: tuple[int, int] | list[int] = (350, 240),
        reference_canvas_size: tuple[int, int] | list[int] = (513, 750),
        backbone_feature_size: int = 330,
        model_num_classes: int = 4,
        id2label: Id2LabelMapping | None = None,
        label2id: dict[str, int] | None = None,
        model_subfolder: str = "model",
        processor_subfolder: str = "processor",
        condition_types: list[str] | tuple[str, ...] | None = None,
        architectures: list[str] | None = None,
        model_type: str | None = None,
        transformers_version: str | None = None,
        torch_dtype: str | None = None,
        dtype: str | None = None,
        name_or_path: str = "",
        _commit_hash: str | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize DS-GAN configuration."""
        _ = (model_type, transformers_version)
        dataset = normalize_dataset_name(dataset_name)
        if dataset is not DatasetName.pku_posterlayout:
            raise ValueError(f"Unsupported DS-GAN dataset_name: {dataset_name}")

        public_id2label = {
            int(k): v for k, v in (id2label or _semantic_pku_id2label()).items()
        }
        public_label2id = label2id or {v: k for k, v in public_id2label.items()}
        super().__init__(
            id2label=public_id2label,
            label2id=public_label2id,
            architectures=architectures or ["DSGANModel"],
            torch_dtype=torch_dtype,  # ty: ignore[unknown-argument]
            dtype=dtype,
            name_or_path=name_or_path,  # ty: ignore[unknown-argument]
            _commit_hash=_commit_hash,  # ty: ignore[unknown-argument]
            **kwargs,  # ty: ignore[invalid-argument-type]
        )
        self.dataset_name = str(dataset)
        self.backbone = backbone
        self.max_elem = max_elem
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.hidden_size = hidden_size if hidden_size is not None else max_elem * 8
        self.num_layers = num_layers
        self.output_size = output_size
        self.image_size = tuple(int(v) for v in image_size)
        self.reference_canvas_size = tuple(int(v) for v in reference_canvas_size)
        self.backbone_feature_size = backbone_feature_size
        self.model_num_classes = model_num_classes
        self.model_subfolder = model_subfolder
        self.processor_subfolder = processor_subfolder
        self.condition_types = list(condition_types or ["content_image"])

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

public_num_labels property

public_num_labels: int

Return the number of public semantic labels.

__init__

__init__(
    dataset_name: DatasetName
    | str = DatasetName.pku_posterlayout,
    backbone: str = "resnet50",
    max_elem: int = 32,
    in_channels: int = 8,
    out_channels: int = 32,
    hidden_size: int | None = None,
    num_layers: int = 4,
    output_size: int = 8,
    image_size: tuple[int, int] | list[int] = (350, 240),
    reference_canvas_size: tuple[int, int] | list[int] = (
        513,
        750,
    ),
    backbone_feature_size: int = 330,
    model_num_classes: int = 4,
    id2label: Id2LabelMapping | None = None,
    label2id: dict[str, int] | None = None,
    model_subfolder: str = "model",
    processor_subfolder: str = "processor",
    condition_types: list[str]
    | tuple[str, ...]
    | None = None,
    architectures: list[str] | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    torch_dtype: str | None = None,
    dtype: str | None = None,
    name_or_path: str = "",
    _commit_hash: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize DS-GAN configuration.

Source code in models/ds-gan/src/ds_gan/configuration_ds_gan.py
 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
def __init__(
    self,
    dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
    backbone: str = "resnet50",
    max_elem: int = 32,
    in_channels: int = 8,
    out_channels: int = 32,
    hidden_size: int | None = None,
    num_layers: int = 4,
    output_size: int = 8,
    image_size: tuple[int, int] | list[int] = (350, 240),
    reference_canvas_size: tuple[int, int] | list[int] = (513, 750),
    backbone_feature_size: int = 330,
    model_num_classes: int = 4,
    id2label: Id2LabelMapping | None = None,
    label2id: dict[str, int] | None = None,
    model_subfolder: str = "model",
    processor_subfolder: str = "processor",
    condition_types: list[str] | tuple[str, ...] | None = None,
    architectures: list[str] | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    torch_dtype: str | None = None,
    dtype: str | None = None,
    name_or_path: str = "",
    _commit_hash: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize DS-GAN configuration."""
    _ = (model_type, transformers_version)
    dataset = normalize_dataset_name(dataset_name)
    if dataset is not DatasetName.pku_posterlayout:
        raise ValueError(f"Unsupported DS-GAN dataset_name: {dataset_name}")

    public_id2label = {
        int(k): v for k, v in (id2label or _semantic_pku_id2label()).items()
    }
    public_label2id = label2id or {v: k for k, v in public_id2label.items()}
    super().__init__(
        id2label=public_id2label,
        label2id=public_label2id,
        architectures=architectures or ["DSGANModel"],
        torch_dtype=torch_dtype,  # ty: ignore[unknown-argument]
        dtype=dtype,
        name_or_path=name_or_path,  # ty: ignore[unknown-argument]
        _commit_hash=_commit_hash,  # ty: ignore[unknown-argument]
        **kwargs,  # ty: ignore[invalid-argument-type]
    )
    self.dataset_name = str(dataset)
    self.backbone = backbone
    self.max_elem = max_elem
    self.in_channels = in_channels
    self.out_channels = out_channels
    self.hidden_size = hidden_size if hidden_size is not None else max_elem * 8
    self.num_layers = num_layers
    self.output_size = output_size
    self.image_size = tuple(int(v) for v in image_size)
    self.reference_canvas_size = tuple(int(v) for v in reference_canvas_size)
    self.backbone_feature_size = backbone_feature_size
    self.model_num_classes = model_num_classes
    self.model_subfolder = model_subfolder
    self.processor_subfolder = processor_subfolder
    self.condition_types = list(condition_types or ["content_image"])

default_ds_gan_config

default_ds_gan_config() -> DSGANConfig

Return the reference-compatible DS-GAN default configuration.

Examples:

>>> default_ds_gan_config().backbone
'resnet50'
Source code in models/ds-gan/src/ds_gan/configuration_ds_gan.py
125
126
127
128
129
130
131
132
def default_ds_gan_config() -> DSGANConfig:
    """Return the reference-compatible DS-GAN default configuration.

    Examples:
        >>> default_ds_gan_config().backbone
        'resnet50'
    """
    return DSGANConfig()

pku_model_label2id

pku_model_label2id() -> dict[str, int]

Return DS-GAN model labels including the no-object class.

Examples:

>>> pku_model_label2id()["no_object"]
0
Source code in models/ds-gan/src/ds_gan/configuration_ds_gan.py
135
136
137
138
139
140
141
142
def pku_model_label2id() -> dict[str, int]:
    """Return DS-GAN model labels including the no-object class.

    Examples:
        >>> pku_model_label2id()["no_object"]
        0
    """
    return {"no_object": 0, "text": 1, "logo": 2, "underlay": 3}

pku_dataset_label2id

pku_dataset_label2id() -> dict[str, int]

Return PKU dataset annotation labels including INVALID.

Source code in models/ds-gan/src/ds_gan/configuration_ds_gan.py
145
146
147
def pku_dataset_label2id() -> dict[str, int]:
    """Return PKU dataset annotation labels including ``INVALID``."""
    return {"text": 0, "logo": 1, "underlay": 2, "INVALID": 3}

conversion

Conversion helpers for original PosterLayout DS-GAN checkpoints.

convert_vendor_state_dict

convert_vendor_state_dict(
    state_dict: Mapping[str, Shaped[Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]

Convert vendor DS-GAN generator keys to DSGANModel keys.

The released checkpoint was commonly saved from torch.nn.DataParallel; this helper strips the leading module. prefix and keeps all generator module names otherwise unchanged.

Parameters:

Name Type Description Default
state_dict Mapping[str, Shaped[Tensor, '...']]

Original checkpoint mapping.

required

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Converted state dictionary.

Examples:

>>> convert_vendor_state_dict({"module.fc1.weight": torch.zeros(1)})["fc1.weight"].shape
torch.Size([1])
Source code in models/ds-gan/src/ds_gan/conversion.py
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
def convert_vendor_state_dict(
    state_dict: Mapping[str, Shaped[torch.Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Convert vendor DS-GAN generator keys to ``DSGANModel`` keys.

    The released checkpoint was commonly saved from ``torch.nn.DataParallel``;
    this helper strips the leading ``module.`` prefix and keeps all generator
    module names otherwise unchanged.

    Args:
        state_dict: Original checkpoint mapping.

    Returns:
        Converted state dictionary.

    Examples:
        >>> convert_vendor_state_dict({"module.fc1.weight": torch.zeros(1)})["fc1.weight"].shape
        torch.Size([1])
    """
    converted: dict[str, Shaped[torch.Tensor, "..."]] = {}
    for key, value in state_dict.items():
        name = key.removeprefix("module.")
        name = name.removeprefix("generator.")
        converted[name] = value
    return converted

config_from_vendor_args

config_from_vendor_args(
    args: Namespace
    | SimpleNamespace
    | Mapping[str, DSGANArgValue]
    | None = None,
) -> DSGANConfig

Build a DS-GAN config from vendor args or defaults.

Source code in models/ds-gan/src/ds_gan/conversion.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def config_from_vendor_args(
    args: argparse.Namespace
    | SimpleNamespace
    | Mapping[str, DSGANArgValue]
    | None = None,
) -> DSGANConfig:
    """Build a DS-GAN config from vendor args or defaults."""
    if args is None:
        return DSGANConfig()
    values = _values(args)
    max_elem = _int_value(values, "max_elem", 32)
    return DSGANConfig(
        backbone=str(values.get("backbone", "resnet50")),
        max_elem=max_elem,
        in_channels=_int_value(values, "in_channels", 8),
        out_channels=_int_value(values, "out_channels", 32),
        hidden_size=_int_value(values, "hidden_size", max_elem * 8),
        num_layers=_int_value(values, "num_layers", 4),
        output_size=_int_value(values, "output_size", 8),
    )

model_card

Hub model-card helper for converted DS-GAN checkpoints.

dsgan_model_card

dsgan_model_card() -> ModelCard

Build the DS-GAN PKU PosterLayout Hub model card.

Source code in models/ds-gan/src/ds_gan/model_card.py
12
13
14
15
16
17
18
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
def dsgan_model_card() -> ModelCard:
    """Build the DS-GAN PKU PosterLayout Hub model card."""
    return build_layout_model_card(
        model_id="creative-graphic-design/ds-gan-pku-posterlayout",
        model_name="DS-GAN PosterLayout",
        dataset_ids=["creative-graphic-design/PKU-PosterLayout"],
        license="other",
        library_name="transformers",
        pipeline_tag="image-to-image",
        tags=["ds-gan", "posterlayout", "layout-generation", "poster-generation"],
        model_details=(
            "DS-GAN predicts poster element boxes and semantic labels from an RGB "
            "poster/background image plus saliency. The converted package returns "
            "normalized center xywh boxes, zero-based labels, and mask-based padding."
        ),
        intended_uses=(
            "Research use for content-aware poster layout generation and checkpoint "
            "parity studies against the original CVPR 2023 implementation."
        ),
        limitations=(
            "The upstream repository does not include a redistribution license. "
            "Converted weights should not be published until license permission is "
            "resolved."
        ),
        how_to_use=(
            "from ds_gan import DSGANPipeline\n\n"
            "pipe = DSGANPipeline.from_pretrained(\n"
            '    "creative-graphic-design/ds-gan-pku-posterlayout"\n'
            ")\n"
            "out = pipe(images=image, saliency=saliency, seed=0)"
        ),
        training_data=(
            "PKU PosterLayout via creative-graphic-design/PKU-PosterLayout; "
            "annotations contain pixel ltrb boxes and an INVALID class that is "
            "excluded from public semantic labels."
        ),
        parity_metrics=[
            {
                "dataset": "PKU PosterLayout",
                "tokenizer_exact": "not applicable",
                "deterministic_exact": "905/905 exact",
                "logits_max_abs": 0.0,
                "logits_max_rel": 0.0,
            }
        ],
        citation_bibtex=(
            "@inproceedings{Hsu-2023-posterlayout,\n"
            "  title={PosterLayout: A New Benchmark and Approach for "
            "Content-Aware Visual-Textual Presentation Layout},\n"
            "  author={HsiaoYuan Hsu and Xiangteng He and Yuxin Peng and "
            "Hao Kong and Qing Zhang},\n"
            "  booktitle={CVPR},\n"
            "  year={2023}\n"
            "}"
        ),
        original_implementation_url=(
            "https://github.com/PKU-ICST-MIPL/PosterLayout-CVPR2023"
        ),
    )

write_dsgan_model_card

write_dsgan_model_card(output_dir: str | Path) -> Path

Write README.md for a converted DS-GAN checkpoint directory.

Source code in models/ds-gan/src/ds_gan/model_card.py
73
74
75
76
77
def write_dsgan_model_card(output_dir: str | Path) -> Path:
    """Write ``README.md`` for a converted DS-GAN checkpoint directory."""
    path = Path(output_dir) / "README.md"
    path.write_text(str(dsgan_model_card()), encoding="utf-8")
    return path

modeling_ds_gan

PyTorch/Transformers implementation of the PosterLayout DS-GAN generator.

DSGANModelOutput dataclass

Bases: ModelOutput

Raw DS-GAN generator output.

Attributes:

Name Type Description
class_probs Float[Tensor, 'batch elements 4']

Internal class probabilities with shape (batch, elements, 4) where id 0 is no object.

bbox Float[Tensor, 'batch elements 4'] | None

Normalized center xywh boxes with shape (batch, elements, 4).

initial_layout Float[Tensor, 'batch elements 2 4'] | None

Initial class/box layout passed to the generator.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@dataclass
class DSGANModelOutput(ModelOutput):
    """Raw DS-GAN generator output.

    Attributes:
        class_probs: Internal class probabilities with shape
            ``(batch, elements, 4)`` where id 0 is ``no object``.
        bbox: Normalized center ``xywh`` boxes with shape
            ``(batch, elements, 4)``.
        initial_layout: Initial class/box layout passed to the generator.
    """

    class_probs: Float[torch.Tensor, "batch elements 4"]
    bbox: Float[torch.Tensor, "batch elements 4"] | None = None
    initial_layout: Float[torch.Tensor, "batch elements 2 4"] | None = None

ResnetBackbone

Bases: Module

DS-GAN ResNet-FPN encoder used to initialize the DS-GAN LSTM state.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
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 ResnetBackbone(nn.Module):
    """DS-GAN ResNet-FPN encoder used to initialize the DS-GAN LSTM state."""

    def __init__(self, config: DSGANConfig) -> None:
        """Initialize the ResNet-FPN encoder."""
        super().__init__()
        try:
            import timm
        except ImportError as exc:
            raise ImportError(
                "DSGANModel requires the optional timm dependency"
            ) from exc

        if config.backbone == "resnet50":
            channels = (1024, 2048)
        elif config.backbone == "resnet18":
            channels = (256, 512)
        else:
            raise ValueError(f"Unsupported DS-GAN backbone: {config.backbone}")

        resnet = timm.create_model(config.backbone, pretrained=False)
        resnet.conv1 = nn.Conv2d(
            4,
            64,
            kernel_size=(7, 7),
            stride=(2, 2),
            padding=(3, 3),
            bias=False,
        )
        children = list(resnet.children())
        self.resnet_tilconv4 = nn.Sequential(*children[:7])
        self.resnet_conv5 = children[7]
        self.fpn_conv11_4 = nn.Conv2d(channels[0], 256, 1, 1, 0)
        self.fpn_conv11_5 = nn.Conv2d(channels[1], 256, 1, 1, 0)
        self.fpn_conv33 = nn.Conv2d(256, 256, 3, 1, 1)
        self.proj = nn.Conv2d(512, 8 * config.max_elem, 1, 1, 0)
        self.fc_h0 = nn.Linear(config.backbone_feature_size, config.num_layers * 2)

    def forward(
        self, pixel_values: Float[torch.Tensor, "batch 4 height width"]
    ) -> Float[torch.Tensor, "layers2 batch hidden"]:
        """Encode image/saliency tensors into an LSTM initial hidden state."""
        resnet_f4 = self.resnet_tilconv4(pixel_values)
        resnet_f5 = self.resnet_conv5(resnet_f4)
        resnet_f4p = self.fpn_conv11_4(resnet_f4)
        resnet_f5p = self.fpn_conv11_5(resnet_f5)
        resnet_f5up = F.interpolate(
            resnet_f5p, size=resnet_f4p.shape[2:], mode="nearest"
        )
        fused = torch.concat(
            [resnet_f5up, self.fpn_conv33(resnet_f5up + resnet_f4p)], dim=1
        )
        projected = self.proj(fused)
        flattened = projected.flatten(start_dim=-2)
        return self.fc_h0(flattened).permute(2, 0, 1)

__init__

__init__(config: DSGANConfig) -> None

Initialize the ResNet-FPN encoder.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
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
def __init__(self, config: DSGANConfig) -> None:
    """Initialize the ResNet-FPN encoder."""
    super().__init__()
    try:
        import timm
    except ImportError as exc:
        raise ImportError(
            "DSGANModel requires the optional timm dependency"
        ) from exc

    if config.backbone == "resnet50":
        channels = (1024, 2048)
    elif config.backbone == "resnet18":
        channels = (256, 512)
    else:
        raise ValueError(f"Unsupported DS-GAN backbone: {config.backbone}")

    resnet = timm.create_model(config.backbone, pretrained=False)
    resnet.conv1 = nn.Conv2d(
        4,
        64,
        kernel_size=(7, 7),
        stride=(2, 2),
        padding=(3, 3),
        bias=False,
    )
    children = list(resnet.children())
    self.resnet_tilconv4 = nn.Sequential(*children[:7])
    self.resnet_conv5 = children[7]
    self.fpn_conv11_4 = nn.Conv2d(channels[0], 256, 1, 1, 0)
    self.fpn_conv11_5 = nn.Conv2d(channels[1], 256, 1, 1, 0)
    self.fpn_conv33 = nn.Conv2d(256, 256, 3, 1, 1)
    self.proj = nn.Conv2d(512, 8 * config.max_elem, 1, 1, 0)
    self.fc_h0 = nn.Linear(config.backbone_feature_size, config.num_layers * 2)

forward

forward(
    pixel_values: Float[Tensor, "batch 4 height width"],
) -> Float[torch.Tensor, "layers2 batch hidden"]

Encode image/saliency tensors into an LSTM initial hidden state.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def forward(
    self, pixel_values: Float[torch.Tensor, "batch 4 height width"]
) -> Float[torch.Tensor, "layers2 batch hidden"]:
    """Encode image/saliency tensors into an LSTM initial hidden state."""
    resnet_f4 = self.resnet_tilconv4(pixel_values)
    resnet_f5 = self.resnet_conv5(resnet_f4)
    resnet_f4p = self.fpn_conv11_4(resnet_f4)
    resnet_f5p = self.fpn_conv11_5(resnet_f5)
    resnet_f5up = F.interpolate(
        resnet_f5p, size=resnet_f4p.shape[2:], mode="nearest"
    )
    fused = torch.concat(
        [resnet_f5up, self.fpn_conv33(resnet_f5up + resnet_f4p)], dim=1
    )
    projected = self.proj(fused)
    flattened = projected.flatten(start_dim=-2)
    return self.fc_h0(flattened).permute(2, 0, 1)

CNNLSTM

Bases: Module

DS-GAN CNN-LSTM sequence model.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
 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
class CNNLSTM(nn.Module):
    """DS-GAN CNN-LSTM sequence model."""

    def __init__(self, config: DSGANConfig) -> None:
        """Initialize the CNN-LSTM block."""
        super().__init__()
        self.conv = nn.Sequential(
            nn.Conv1d(
                in_channels=config.in_channels,
                out_channels=config.out_channels,
                kernel_size=3,
                padding="same",
            ),
            nn.ReLU(),
            nn.MaxPool1d(3, stride=1, padding=1),
        )
        self.lstm = nn.LSTM(
            input_size=config.out_channels,
            hidden_size=config.hidden_size,
            num_layers=config.num_layers,
            batch_first=True,
            bidirectional=True,
        )

    def forward(
        self,
        layout: Float[torch.Tensor, "batch elements 2 4"],
        h0: Float[torch.Tensor, "layers2 batch hidden"],
    ) -> Float[torch.Tensor, "batch elements hidden2"]:
        """Run the DS-GAN CNN-LSTM over initial layout tensors."""
        self.lstm.flatten_parameters()
        x = layout.flatten(start_dim=2).permute(0, 2, 1).contiguous()
        x = self.conv(x).permute(0, 2, 1).contiguous()
        output, _ = self.lstm(x, (torch.zeros_like(h0).contiguous(), h0.contiguous()))
        return output

__init__

__init__(config: DSGANConfig) -> None

Initialize the CNN-LSTM block.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def __init__(self, config: DSGANConfig) -> None:
    """Initialize the CNN-LSTM block."""
    super().__init__()
    self.conv = nn.Sequential(
        nn.Conv1d(
            in_channels=config.in_channels,
            out_channels=config.out_channels,
            kernel_size=3,
            padding="same",
        ),
        nn.ReLU(),
        nn.MaxPool1d(3, stride=1, padding=1),
    )
    self.lstm = nn.LSTM(
        input_size=config.out_channels,
        hidden_size=config.hidden_size,
        num_layers=config.num_layers,
        batch_first=True,
        bidirectional=True,
    )

forward

forward(
    layout: Float[Tensor, "batch elements 2 4"],
    h0: Float[Tensor, "layers2 batch hidden"],
) -> Float[torch.Tensor, "batch elements hidden2"]

Run the DS-GAN CNN-LSTM over initial layout tensors.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
120
121
122
123
124
125
126
127
128
129
130
def forward(
    self,
    layout: Float[torch.Tensor, "batch elements 2 4"],
    h0: Float[torch.Tensor, "layers2 batch hidden"],
) -> Float[torch.Tensor, "batch elements hidden2"]:
    """Run the DS-GAN CNN-LSTM over initial layout tensors."""
    self.lstm.flatten_parameters()
    x = layout.flatten(start_dim=2).permute(0, 2, 1).contiguous()
    x = self.conv(x).permute(0, 2, 1).contiguous()
    output, _ = self.lstm(x, (torch.zeros_like(h0).contiguous(), h0.contiguous()))
    return output

DSGANModel

Bases: PreTrainedModel

Transformers-compatible DS-GAN generator.

Parameters:

Name Type Description Default
config DSGANConfig

DS-GAN model configuration.

required

Examples:

>>> config = DSGANConfig(backbone="resnet18", max_elem=4, hidden_size=32, num_layers=2, image_size=(64, 64), backbone_feature_size=16)
>>> model = DSGANModel(config)
>>> model.config.model_type
'ds_gan'
Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
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
class DSGANModel(PreTrainedModel):
    """Transformers-compatible DS-GAN generator.

    Args:
        config: DS-GAN model configuration.

    Examples:
        >>> config = DSGANConfig(backbone="resnet18", max_elem=4, hidden_size=32, num_layers=2, image_size=(64, 64), backbone_feature_size=16)
        >>> model = DSGANModel(config)
        >>> model.config.model_type
        'ds_gan'
    """

    config_class = DSGANConfig
    base_model_prefix = "ds_gan"
    supports_gradient_checkpointing = False

    def __init__(self, config: DSGANConfig) -> None:
        """Initialize DS-GAN generator layers."""
        super().__init__(config)
        self.resnet_fpn = ResnetBackbone(config)
        self.cnnlstm = CNNLSTM(config)
        self.fc1 = nn.Linear(2 * config.hidden_size, config.output_size // 2)
        self.fc2 = nn.Linear(2 * config.hidden_size, config.output_size // 2)
        self.post_init()

    def forward(
        self,
        pixel_values: Float[torch.Tensor, "batch 4 height width"],
        layout: Float[torch.Tensor, "batch elements 2 4"],
        return_dict: bool = True,
    ) -> (
        DSGANModelOutput
        | tuple[
            Float[torch.Tensor, "batch elements 4"],
            Float[torch.Tensor, "batch elements 4"],
        ]
    ):
        """Run a DS-GAN generator forward pass.

        Args:
            pixel_values: RGB plus saliency tensor shaped ``(B, 4, H, W)``.
            layout: Initial internal layout shaped ``(B, max_elem, 2, 4)``.
            return_dict: Whether to return a dataclass output.

        Returns:
            Raw class probabilities and normalized center ``xywh`` boxes.

        Raises:
            ValueError: If tensor shapes do not match the config.
        """
        if pixel_values.ndim != 4 or pixel_values.shape[1] != 4:
            raise ValueError("pixel_values must have shape (batch, 4, height, width)")

        expected_layout = (pixel_values.shape[0], self.config.max_elem, 2, 4)
        if tuple(layout.shape) != expected_layout:
            raise ValueError(f"layout must have shape {expected_layout}")

        pixel_values = pixel_values.to(dtype=self.dtype)
        layout = layout.to(device=pixel_values.device, dtype=self.dtype)
        h0 = self.resnet_fpn(pixel_values)
        lstm_output = self.cnnlstm(layout, h0)
        class_probs = torch.softmax(self.fc1(lstm_output), dim=-1)
        bbox = torch.sigmoid(self.fc2(lstm_output))
        if not return_dict:
            return class_probs, bbox
        return DSGANModelOutput(
            class_probs=class_probs,
            bbox=bbox,
            initial_layout=layout,
        )

__init__

__init__(config: DSGANConfig) -> None

Initialize DS-GAN generator layers.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
150
151
152
153
154
155
156
157
def __init__(self, config: DSGANConfig) -> None:
    """Initialize DS-GAN generator layers."""
    super().__init__(config)
    self.resnet_fpn = ResnetBackbone(config)
    self.cnnlstm = CNNLSTM(config)
    self.fc1 = nn.Linear(2 * config.hidden_size, config.output_size // 2)
    self.fc2 = nn.Linear(2 * config.hidden_size, config.output_size // 2)
    self.post_init()

forward

forward(
    pixel_values: Float[Tensor, "batch 4 height width"],
    layout: Float[Tensor, "batch elements 2 4"],
    return_dict: bool = True,
) -> (
    DSGANModelOutput
    | tuple[
        Float[torch.Tensor, "batch elements 4"],
        Float[torch.Tensor, "batch elements 4"],
    ]
)

Run a DS-GAN generator forward pass.

Parameters:

Name Type Description Default
pixel_values Float[Tensor, 'batch 4 height width']

RGB plus saliency tensor shaped (B, 4, H, W).

required
layout Float[Tensor, 'batch elements 2 4']

Initial internal layout shaped (B, max_elem, 2, 4).

required
return_dict bool

Whether to return a dataclass output.

True

Returns:

Type Description
DSGANModelOutput | tuple[Float[Tensor, 'batch elements 4'], Float[Tensor, 'batch elements 4']]

Raw class probabilities and normalized center xywh boxes.

Raises:

Type Description
ValueError

If tensor shapes do not match the config.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
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
def forward(
    self,
    pixel_values: Float[torch.Tensor, "batch 4 height width"],
    layout: Float[torch.Tensor, "batch elements 2 4"],
    return_dict: bool = True,
) -> (
    DSGANModelOutput
    | tuple[
        Float[torch.Tensor, "batch elements 4"],
        Float[torch.Tensor, "batch elements 4"],
    ]
):
    """Run a DS-GAN generator forward pass.

    Args:
        pixel_values: RGB plus saliency tensor shaped ``(B, 4, H, W)``.
        layout: Initial internal layout shaped ``(B, max_elem, 2, 4)``.
        return_dict: Whether to return a dataclass output.

    Returns:
        Raw class probabilities and normalized center ``xywh`` boxes.

    Raises:
        ValueError: If tensor shapes do not match the config.
    """
    if pixel_values.ndim != 4 or pixel_values.shape[1] != 4:
        raise ValueError("pixel_values must have shape (batch, 4, height, width)")

    expected_layout = (pixel_values.shape[0], self.config.max_elem, 2, 4)
    if tuple(layout.shape) != expected_layout:
        raise ValueError(f"layout must have shape {expected_layout}")

    pixel_values = pixel_values.to(dtype=self.dtype)
    layout = layout.to(device=pixel_values.device, dtype=self.dtype)
    h0 = self.resnet_fpn(pixel_values)
    lstm_output = self.cnnlstm(layout, h0)
    class_probs = torch.softmax(self.fc1(lstm_output), dim=-1)
    bbox = torch.sigmoid(self.fc2(lstm_output))
    if not return_dict:
        return class_probs, bbox
    return DSGANModelOutput(
        class_probs=class_probs,
        bbox=bbox,
        initial_layout=layout,
    )

xyxy_to_xywh

xyxy_to_xywh(
    bbox: Float[Tensor, "... 4"],
) -> Float[torch.Tensor, "... 4"]

Convert left/top/right/bottom boxes to center xywh boxes.

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
206
207
208
209
210
211
212
213
214
def xyxy_to_xywh(
    bbox: Float[torch.Tensor, "... 4"],
) -> Float[torch.Tensor, "... 4"]:
    """Convert left/top/right/bottom boxes to center ``xywh`` boxes."""
    left, top, right, bottom = bbox.unbind(-1)
    return torch.stack(
        ((left + right) / 2, (top + bottom) / 2, right - left, bottom - top),
        dim=-1,
    )

random_initial_layout

random_initial_layout(
    batch_size: int,
    max_elem: int,
    *,
    generator: Generator | None = None,
    seed: int | None = None,
    device: device | str | None = None,
    dtype: dtype = torch.float32,
    weighted_classes: bool = True,
    use_numpy_classes: bool = False,
) -> Float[torch.Tensor, "batch elements 2 4"]

Sample the DS-GAN initial layout tensor.

Parameters:

Name Type Description Default
batch_size int

Batch size.

required
max_elem int

Number of layout slots.

required
generator Generator | None

Optional torch generator. Takes precedence over seed.

None
seed int | None

Convenience seed used only when generator is absent.

None
device device | str | None

Target torch device.

None
dtype dtype

Target floating dtype.

float32
weighted_classes bool

Whether to use the released inference class prior.

True
use_numpy_classes bool

Use NumPy's legacy RandomState class sampler to mirror the reference script's weighted class prior when seed is supplied. Torch box sampling still follows generator or seed.

False

Returns:

Type Description
Float[Tensor, 'batch elements 2 4']

Tensor shaped (batch, max_elem, 2, 4).

Source code in models/ds-gan/src/ds_gan/modeling_ds_gan.py
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
def random_initial_layout(
    batch_size: int,
    max_elem: int,
    *,
    generator: torch.Generator | None = None,
    seed: int | None = None,
    device: torch.device | str | None = None,
    dtype: torch.dtype = torch.float32,
    weighted_classes: bool = True,
    use_numpy_classes: bool = False,
) -> Float[torch.Tensor, "batch elements 2 4"]:
    """Sample the DS-GAN initial layout tensor.

    Args:
        batch_size: Batch size.
        max_elem: Number of layout slots.
        generator: Optional torch generator. Takes precedence over ``seed``.
        seed: Convenience seed used only when ``generator`` is absent.
        device: Target torch device.
        dtype: Target floating dtype.
        weighted_classes: Whether to use the released inference class prior.
        use_numpy_classes: Use NumPy's legacy ``RandomState`` class sampler to
            mirror the reference script's weighted class prior when ``seed`` is
            supplied. Torch box sampling still follows ``generator`` or ``seed``.

    Returns:
        Tensor shaped ``(batch, max_elem, 2, 4)``.
    """
    resolved_device = (
        torch.device(device) if device is not None else torch.device("cpu")
    )
    if generator is None and seed is not None:
        generator = torch.Generator(device=resolved_device).manual_seed(seed)
    if weighted_classes:
        probs = torch.tensor((0.1, 0.8, 1.0, 1.0), device=resolved_device)
        probs = probs / probs.sum()
    else:
        probs = torch.full((4,), 0.25, device=resolved_device)
    if use_numpy_classes:
        rng = np.random.RandomState(seed)
        np_probs = probs.detach().cpu().numpy()
        class_ids = torch.as_tensor(
            rng.choice(4, size=(batch_size, max_elem, 1), p=np_probs),
            dtype=torch.long,
            device=resolved_device,
        )
    else:
        class_ids = torch.multinomial(
            probs,
            num_samples=batch_size * max_elem,
            replacement=True,
            generator=generator,
        ).reshape(batch_size, max_elem, 1)
    class_one_hot = torch.zeros(
        batch_size,
        max_elem,
        4,
        dtype=dtype,
        device=resolved_device,
    )
    class_one_hot.scatter_(-1, class_ids, 1)
    box_xyxy = torch.normal(
        mean=0.5,
        std=0.15,
        size=(batch_size, max_elem, 1, 4),
        generator=generator,
        device=resolved_device,
        dtype=dtype,
    )
    bbox = xyxy_to_xywh(box_xyxy)
    return torch.concat([class_one_hot.unsqueeze(2), bbox], dim=2)

pipeline_ds_gan

Pipeline interface for PosterLayout DS-GAN generation.

DSGANPipelineComponent

Bases: Protocol

Runtime-checkable loaded pipeline component marker.

Source code in models/ds-gan/src/ds_gan/pipeline_ds_gan.py
31
32
33
@runtime_checkable
class DSGANPipelineComponent(Protocol):
    """Runtime-checkable loaded pipeline component marker."""

OutputType

Bases: StrEnum

Supported DS-GAN pipeline output containers.

Source code in models/ds-gan/src/ds_gan/pipeline_ds_gan.py
36
37
38
39
40
class OutputType(StrEnum):
    """Supported DS-GAN pipeline output containers."""

    dataclass = auto()
    dict = auto()

DSGANPipeline

Bases: LayoutGenerationPipeline

Transformers-side pipeline for content-aware PosterLayout generation.

Parameters:

Name Type Description Default
model DSGANModel

DS-GAN generator.

required
processor DSGANProcessor | None

Optional processor for images and output decoding.

None
config DSGANConfig | None

Optional root pipeline config.

None
device str | device | None

Optional runtime device.

None

Examples:

>>> config = DSGANConfig(backbone="resnet18", max_elem=4, hidden_size=32, num_layers=2, image_size=(64, 64), backbone_feature_size=16)
>>> pipe = DSGANPipeline(DSGANModel(config))
>>> pipe.config.model_type
'ds_gan'
Source code in models/ds-gan/src/ds_gan/pipeline_ds_gan.py
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
class DSGANPipeline(LayoutGenerationPipeline):
    """Transformers-side pipeline for content-aware PosterLayout generation.

    Args:
        model: DS-GAN generator.
        processor: Optional processor for images and output decoding.
        config: Optional root pipeline config.
        device: Optional runtime device.

    Examples:
        >>> config = DSGANConfig(backbone="resnet18", max_elem=4, hidden_size=32, num_layers=2, image_size=(64, 64), backbone_feature_size=16)
        >>> pipe = DSGANPipeline(DSGANModel(config))
        >>> pipe.config.model_type
        'ds_gan'
    """

    config_class: ClassVar[type[PretrainedConfig]] = DSGANConfig
    component_specs: ClassVar[dict[str, PipelineComponentSpec]] = {
        "model": PipelineComponentSpec(
            attribute_name="model",
            loader=_load_model_component,
            marker_file="config.json",
            config_subfolder_attribute="model_subfolder",
        ),
        "processor": PipelineComponentSpec(
            attribute_name="processor",
            loader=_load_processor_component,
            marker_file="processor_config.json",
            save_with_is_main_process=False,
            config_subfolder_attribute="processor_subfolder",
        ),
    }

    config: DSGANConfig
    model: DSGANModel
    processor: DSGANProcessor

    def __init__(
        self,
        model: DSGANModel,
        processor: DSGANProcessor | None = None,
        config: DSGANConfig | None = None,
        device: str | torch.device | None = None,
    ) -> None:
        """Initialize DS-GAN pipeline."""
        super().__init__(config or model.config)
        self.config = config or model.config
        self.model = model
        self.processor = processor or DSGANProcessor(
            dataset_name=self.config.dataset_name,
            id2label=cast(dict[int | str, str], self.config.id2label),
            image_size=cast(tuple[int, int], self.config.image_size),
        )
        self.model.eval()
        if device is not None:
            self.to(device)

    @classmethod
    def _from_pretrained_components(
        cls,
        *,
        config: PretrainedConfig,
        components: Mapping[str, DSGANPipelineComponent | None],
    ) -> "DSGANPipeline":
        """Build a pipeline from loaded root components."""
        return cls(
            config=cast(DSGANConfig, config),
            model=cast(DSGANModel, components["model"]),
            processor=cast(DSGANProcessor, components["processor"]),
        )

    @torch.no_grad()
    def __call__(  # ty: ignore[invalid-method-override]
        self,
        images: ImageInput
        | list[ImageInput]
        | Float[torch.Tensor, "..."]
        | 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[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[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: OutputType | str = OutputType.dataclass,
        return_intermediates: bool = False,
        saliency: ImageInput
        | list[ImageInput]
        | Float[torch.Tensor, "..."]
        | None = None,
        saliency_pfpnet: ImageInput
        | list[ImageInput]
        | Float[torch.Tensor, "..."]
        | None = None,
        saliency_basnet: ImageInput
        | list[ImageInput]
        | Float[torch.Tensor, "..."]
        | None = None,
        pixel_values: Float[torch.Tensor, "batch 4 height width"] | None = None,
        initial_layout: Float[torch.Tensor, "batch elements 2 4"] | None = None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | dict[int, str]
            | Mapping[str, Shaped[torch.Tensor, "..."] | ConditionType | str | bool]
            | None,
        ]
    ):
        """Generate layouts from content images and saliency maps.

        Args:
            images: RGB image or batch. Required unless ``pixel_values`` is given.
            batch_size: Batch size used when ``pixel_values`` is supplied.
            seed: Convenience seed. Ignored when ``generator`` is supplied.
            generator: Explicit torch generator.
            condition_type: Must normalize to ``content_image``.
            labels: Optional public labels used only with ``bbox`` to provide a
                fixed initial layout.
            bbox: Optional public boxes used with ``labels`` for a fixed layout.
            mask: Optional valid-element mask for fixed initial layouts.
            num_elements: Reserved compatibility argument.
            box_format: Format of optional ``bbox``.
            normalized: Whether optional ``bbox`` is normalized.
            canvas_size: Pixel canvas size for optional unnormalized ``bbox``.
            num_inference_steps: Reserved compatibility argument.
            output_type: ``dataclass`` or ``dict``.
            return_intermediates: Whether to include raw model tensors.
            saliency: Optional single merged saliency map.
            saliency_pfpnet: Optional PFPNet saliency map.
            saliency_basnet: Optional BASNet saliency map.
            pixel_values: Preprocessed ``(B, 4, H, W)`` tensor.
            initial_layout: Optional internal layout ``(B, max_elem, 2, 4)``.

        Returns:
            Shared layout-generation output.

        Raises:
            ValueError: If the condition, image inputs, or fixed layout are invalid.
        """
        del num_elements, num_inference_steps
        canonical = normalize_condition_type(condition_type)
        resolved_output_type = normalize_output_type(output_type)
        device = self.device or next(self.model.parameters()).device
        if pixel_values is None:
            if images is None:
                raise ValueError("images or pixel_values are required for DS-GAN")

            encoded = self.processor(
                images,
                saliency=saliency,
                saliency_pfpnet=saliency_pfpnet,
                saliency_basnet=saliency_basnet,
            )
            pixel_values = cast(torch.Tensor, encoded["pixel_values"])
        pixel_values = pixel_values.to(device=device, dtype=self.model.dtype)
        batch_size = pixel_values.shape[0] if pixel_values is not None else batch_size
        if initial_layout is None and bbox is not None and labels is not None:
            encoded_layout = self.processor.encode_layout(
                bbox=bbox,
                labels=labels,
                mask=mask,
                box_format=box_format,
                normalized=normalized,
                canvas_size=canvas_size,
                max_elem=self.config.max_elem,
            )
            initial_layout = encoded_layout["layout"]
        if initial_layout is None:
            prepared = self.prepare_generator(
                generator=generator, seed=seed, device=device
            )
            initial_layout = random_initial_layout(
                batch_size,
                self.config.max_elem,
                generator=prepared,
                device=device,
                dtype=self.model.dtype,
            )
        else:
            initial_layout = initial_layout.to(device=device, dtype=self.model.dtype)
        model_output = self.model(
            pixel_values=pixel_values,
            layout=initial_layout,
            return_dict=True,
        )
        assert isinstance(model_output, DSGANModelOutput)
        intermediates = None
        if return_intermediates:
            intermediates = {
                "condition_type": canonical,
                "initial_layout": initial_layout.detach().cpu(),
                "class_probs": model_output.class_probs.detach().cpu(),
            }
        return self.processor.decode(
            class_probs=model_output.class_probs,
            bbox=model_output.bbox,
            output_type=resolved_output_type.value,
            intermediates=intermediates,
        )

__init__

__init__(
    model: DSGANModel,
    processor: DSGANProcessor | None = None,
    config: DSGANConfig | None = None,
    device: str | device | None = None,
) -> None

Initialize DS-GAN pipeline.

Source code in models/ds-gan/src/ds_gan/pipeline_ds_gan.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def __init__(
    self,
    model: DSGANModel,
    processor: DSGANProcessor | None = None,
    config: DSGANConfig | None = None,
    device: str | torch.device | None = None,
) -> None:
    """Initialize DS-GAN pipeline."""
    super().__init__(config or model.config)
    self.config = config or model.config
    self.model = model
    self.processor = processor or DSGANProcessor(
        dataset_name=self.config.dataset_name,
        id2label=cast(dict[int | str, str], self.config.id2label),
        image_size=cast(tuple[int, int], self.config.image_size),
    )
    self.model.eval()
    if device is not None:
        self.to(device)

__call__

__call__(
    images: ImageInput
    | list[ImageInput]
    | Float[Tensor, "..."]
    | 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[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[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: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
    saliency: ImageInput
    | list[ImageInput]
    | Float[Tensor, "..."]
    | None = None,
    saliency_pfpnet: ImageInput
    | list[ImageInput]
    | Float[Tensor, "..."]
    | None = None,
    saliency_basnet: ImageInput
    | list[ImageInput]
    | Float[Tensor, "..."]
    | None = None,
    pixel_values: Float[Tensor, "batch 4 height width"]
    | None = None,
    initial_layout: Float[Tensor, "batch elements 2 4"]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | Mapping[
            str,
            Shaped[torch.Tensor, "..."]
            | ConditionType
            | str
            | bool,
        ]
        | None,
    ]
)

Generate layouts from content images and saliency maps.

Parameters:

Name Type Description Default
images ImageInput | list[ImageInput] | Float[Tensor, '...'] | None

RGB image or batch. Required unless pixel_values is given.

None
batch_size int

Batch size used when pixel_values is supplied.

1
seed int | None

Convenience seed. Ignored when generator is supplied.

None
generator Generator | None

Explicit torch generator.

None
condition_type ConditionType | str

Must normalize to content_image.

content_image
labels Int[Tensor, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional public labels used only with bbox to provide a fixed initial layout.

None
bbox Float[Tensor, 'batch elements 4'] | Sequence[ArrayLikeInput] | None

Optional public boxes used with labels for a fixed layout.

None
mask Bool[Tensor, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional valid-element mask for fixed initial layouts.

None
num_elements int | list[int] | Int[Tensor, 'batch'] | None

Reserved compatibility argument.

None
box_format BoxFormat | str

Format of optional bbox.

xywh
normalized bool

Whether optional bbox is normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size for optional unnormalized bbox.

None
num_inference_steps int | None

Reserved compatibility argument.

None
output_type OutputType | str

dataclass or dict.

dataclass
return_intermediates bool

Whether to include raw model tensors.

False
saliency ImageInput | list[ImageInput] | Float[Tensor, '...'] | None

Optional single merged saliency map.

None
saliency_pfpnet ImageInput | list[ImageInput] | Float[Tensor, '...'] | None

Optional PFPNet saliency map.

None
saliency_basnet ImageInput | list[ImageInput] | Float[Tensor, '...'] | None

Optional BASNet saliency map.

None
pixel_values Float[Tensor, 'batch 4 height width'] | None

Preprocessed (B, 4, H, W) tensor.

None
initial_layout Float[Tensor, 'batch elements 2 4'] | None

Optional internal layout (B, max_elem, 2, 4).

None

Returns:

Type Description
LayoutGenerationOutput | dict[str, Shaped[Tensor, '...'] | dict[int, str] | Mapping[str, Shaped[Tensor, '...'] | ConditionType | str | bool] | None]

Shared layout-generation output.

Raises:

Type Description
ValueError

If the condition, image inputs, or fixed layout are invalid.

Source code in models/ds-gan/src/ds_gan/pipeline_ds_gan.py
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
@torch.no_grad()
def __call__(  # ty: ignore[invalid-method-override]
    self,
    images: ImageInput
    | list[ImageInput]
    | Float[torch.Tensor, "..."]
    | 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[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[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: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
    saliency: ImageInput
    | list[ImageInput]
    | Float[torch.Tensor, "..."]
    | None = None,
    saliency_pfpnet: ImageInput
    | list[ImageInput]
    | Float[torch.Tensor, "..."]
    | None = None,
    saliency_basnet: ImageInput
    | list[ImageInput]
    | Float[torch.Tensor, "..."]
    | None = None,
    pixel_values: Float[torch.Tensor, "batch 4 height width"] | None = None,
    initial_layout: Float[torch.Tensor, "batch elements 2 4"] | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | Mapping[str, Shaped[torch.Tensor, "..."] | ConditionType | str | bool]
        | None,
    ]
):
    """Generate layouts from content images and saliency maps.

    Args:
        images: RGB image or batch. Required unless ``pixel_values`` is given.
        batch_size: Batch size used when ``pixel_values`` is supplied.
        seed: Convenience seed. Ignored when ``generator`` is supplied.
        generator: Explicit torch generator.
        condition_type: Must normalize to ``content_image``.
        labels: Optional public labels used only with ``bbox`` to provide a
            fixed initial layout.
        bbox: Optional public boxes used with ``labels`` for a fixed layout.
        mask: Optional valid-element mask for fixed initial layouts.
        num_elements: Reserved compatibility argument.
        box_format: Format of optional ``bbox``.
        normalized: Whether optional ``bbox`` is normalized.
        canvas_size: Pixel canvas size for optional unnormalized ``bbox``.
        num_inference_steps: Reserved compatibility argument.
        output_type: ``dataclass`` or ``dict``.
        return_intermediates: Whether to include raw model tensors.
        saliency: Optional single merged saliency map.
        saliency_pfpnet: Optional PFPNet saliency map.
        saliency_basnet: Optional BASNet saliency map.
        pixel_values: Preprocessed ``(B, 4, H, W)`` tensor.
        initial_layout: Optional internal layout ``(B, max_elem, 2, 4)``.

    Returns:
        Shared layout-generation output.

    Raises:
        ValueError: If the condition, image inputs, or fixed layout are invalid.
    """
    del num_elements, num_inference_steps
    canonical = normalize_condition_type(condition_type)
    resolved_output_type = normalize_output_type(output_type)
    device = self.device or next(self.model.parameters()).device
    if pixel_values is None:
        if images is None:
            raise ValueError("images or pixel_values are required for DS-GAN")

        encoded = self.processor(
            images,
            saliency=saliency,
            saliency_pfpnet=saliency_pfpnet,
            saliency_basnet=saliency_basnet,
        )
        pixel_values = cast(torch.Tensor, encoded["pixel_values"])
    pixel_values = pixel_values.to(device=device, dtype=self.model.dtype)
    batch_size = pixel_values.shape[0] if pixel_values is not None else batch_size
    if initial_layout is None and bbox is not None and labels is not None:
        encoded_layout = self.processor.encode_layout(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
            max_elem=self.config.max_elem,
        )
        initial_layout = encoded_layout["layout"]
    if initial_layout is None:
        prepared = self.prepare_generator(
            generator=generator, seed=seed, device=device
        )
        initial_layout = random_initial_layout(
            batch_size,
            self.config.max_elem,
            generator=prepared,
            device=device,
            dtype=self.model.dtype,
        )
    else:
        initial_layout = initial_layout.to(device=device, dtype=self.model.dtype)
    model_output = self.model(
        pixel_values=pixel_values,
        layout=initial_layout,
        return_dict=True,
    )
    assert isinstance(model_output, DSGANModelOutput)
    intermediates = None
    if return_intermediates:
        intermediates = {
            "condition_type": canonical,
            "initial_layout": initial_layout.detach().cpu(),
            "class_probs": model_output.class_probs.detach().cpu(),
        }
    return self.processor.decode(
        class_probs=model_output.class_probs,
        bbox=model_output.bbox,
        output_type=resolved_output_type.value,
        intermediates=intermediates,
    )

normalize_condition_type

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

Normalize DS-GAN condition aliases.

Parameters:

Name Type Description Default
condition_type ConditionType | str | None

Canonical condition enum, alias, or None.

required

Returns:

Type Description
ConditionType

Canonical content_image condition.

Raises:

Type Description
ValueError

If DS-GAN does not support the requested mode.

Examples:

>>> str(normalize_condition_type("content"))
'content_image'
Source code in models/ds-gan/src/ds_gan/pipeline_ds_gan.py
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
def normalize_condition_type(
    condition_type: ConditionType | str | None,
) -> ConditionType:
    """Normalize DS-GAN condition aliases.

    Args:
        condition_type: Canonical condition enum, alias, or ``None``.

    Returns:
        Canonical ``content_image`` condition.

    Raises:
        ValueError: If DS-GAN does not support the requested mode.

    Examples:
        >>> str(normalize_condition_type("content"))
        'content_image'
    """
    if condition_type is None:
        canonical = ConditionType.content_image
    elif isinstance(condition_type, ConditionType):
        canonical = condition_type
    else:
        key = condition_type.lower().replace("-", "_")
        canonical = _DSGAN_CONDITION_ALIASES.get(key)
        if canonical is None:
            canonical = normalize_shared_condition_type(condition_type)
    if canonical not in _SUPPORTED_CONDITION_TYPES:
        raise ValueError(f"Unsupported DS-GAN condition_type: {condition_type}")

    return canonical

normalize_output_type

normalize_output_type(
    output_type: OutputType | str,
) -> OutputType

Normalize public output type aliases.

Source code in models/ds-gan/src/ds_gan/pipeline_ds_gan.py
85
86
87
88
89
90
91
92
def normalize_output_type(output_type: OutputType | str) -> OutputType:
    """Normalize public output type aliases."""
    if isinstance(output_type, OutputType):
        return output_type
    try:
        return OutputType(output_type)
    except ValueError as exc:
        raise ValueError(f"Unsupported output_type: {output_type}") from exc

processing_ds_gan

Processor for DS-GAN content-image inputs and layout decoding.

DSGANProcessor

Bases: ProcessorMixin

Prepare PosterLayout RGB/saliency inputs and decode DS-GAN outputs.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset key. Only PKU PosterLayout is supported.

pku_posterlayout
id2label dict[int | str, str] | None

Public semantic labels excluding model no object.

None
image_size tuple[int, int] | list[int]

Resize target as (height, width).

(350, 240)

Examples:

>>> processor = DSGANProcessor()
>>> processor.id2label[0]
'text'
Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
 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
class DSGANProcessor(ProcessorMixin):
    """Prepare PosterLayout RGB/saliency inputs and decode DS-GAN outputs.

    Args:
        dataset_name: Dataset key. Only PKU PosterLayout is supported.
        id2label: Public semantic labels excluding model ``no object``.
        image_size: Resize target as ``(height, width)``.

    Examples:
        >>> processor = DSGANProcessor()
        >>> processor.id2label[0]
        'text'
    """

    config_name = "processor_config.json"

    def __init__(
        self,
        dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
        id2label: dict[int | str, str] | None = None,
        image_size: tuple[int, int] | list[int] = (350, 240),
    ) -> None:
        """Initialize processor metadata."""
        self.chat_template = None
        dataset = normalize_dataset_name(dataset_name)
        if dataset is not DatasetName.pku_posterlayout:
            raise ValueError(f"Unsupported DS-GAN dataset_name: {dataset_name}")

        self.dataset_name = str(dataset)
        default_id2label = {0: "text", 1: "logo", 2: "underlay"}
        raw_id2label = id2label or default_id2label
        self.id2label = {int(k): v for k, v in raw_id2label.items()}
        self.label2id = {v: k for k, v in self.id2label.items()}
        height, width = image_size
        self.image_size: tuple[int, int] = (int(height), int(width))

    def __call__(
        self,
        images: DSGANImageInput | Sequence[DSGANImageInput],
        *,
        saliency: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
        saliency_pfpnet: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
        saliency_basnet: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
        return_tensors: Literal["pt"] = "pt",
    ) -> BatchEncoding:
        """Encode content images into ``pixel_values``.

        Args:
            images: RGB image or batch of RGB images.
            saliency: Optional saliency image or batch. If omitted and both
                saliency maps are given, the maps are merged by pixelwise max.
            saliency_pfpnet: Optional PFPNet saliency map.
            saliency_basnet: Optional BASNet saliency map.
            return_tensors: Tensor framework. Only ``pt`` is supported.

        Returns:
            Batch encoding containing ``pixel_values`` shaped ``(B, 4, H, W)``.
        """
        if return_tensors != "pt":
            raise ValueError("DSGANProcessor only supports return_tensors='pt'")

        image_rows = _ensure_batch(images)
        saliency_rows = self._resolve_saliency(
            len(image_rows),
            saliency=saliency,
            saliency_pfpnet=saliency_pfpnet,
            saliency_basnet=saliency_basnet,
        )
        tensors = []
        for image, sal in zip(image_rows, saliency_rows, strict=True):
            rgb = _to_rgb_tensor(image, self.image_size)
            sal_tensor = (
                torch.zeros(1, *self.image_size, dtype=torch.float32)
                if sal is None
                else _to_l_tensor(sal, self.image_size)
            )
            tensors.append(torch.cat((rgb, sal_tensor), dim=0))
        return BatchEncoding({"pixel_values": torch.stack(tensors)})

    def decode(
        self,
        *,
        class_probs: Float[torch.Tensor, "batch elements 4"],
        bbox: Float[torch.Tensor, "batch elements 4"],
        output_type: Literal["dataclass", "dict"] = "dataclass",
        scores: Float[torch.Tensor, "batch elements"] | None = None,
        intermediates: Mapping[str, Shaped[torch.Tensor, "..."] | str | bool]
        | None = None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | dict[int, str]
            | Mapping[str, Shaped[torch.Tensor, "..."] | str | bool]
            | None,
        ]
    ):
        """Decode raw DS-GAN class probabilities and boxes.

        Args:
            class_probs: Internal class probabilities shaped ``(B, E, 4)``.
            bbox: Normalized center ``xywh`` boxes shaped ``(B, E, 4)``.
            output_type: Return format.
            scores: Optional per-element class scores.
            intermediates: Optional model-specific intermediate tensors.

        Returns:
            Shared layout output with public labels and mask semantics.
        """
        class_ids = torch.argmax(class_probs, dim=-1)
        mask = class_ids != 0
        public_labels = (class_ids - 1).clamp_min(0).long()
        resolved_scores = scores
        if resolved_scores is None:
            resolved_scores = class_probs.max(dim=-1).values
        output = LayoutGenerationOutput(
            bbox=bbox.detach().cpu().clamp(0.0, 1.0),
            labels=public_labels.detach().cpu(),
            mask=mask.detach().cpu(),
            id2label=dict(self.id2label),
            scores=resolved_scores.detach().cpu(),
            intermediates=intermediates,
        )
        if output_type == "dict":
            return dict(output)
        if output_type == "dataclass":
            return output
        raise ValueError(f"Unsupported output_type: {output_type}")

    def encode_layout(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput],
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput],
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        max_elem: int = 32,
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Encode public boxes/labels into the internal layout tensor.

        Args:
            bbox: Public boxes.
            labels: Public zero-based semantic labels.
            mask: Optional valid-element mask.
            box_format: Input box format.
            normalized: Whether the boxes are normalized.
            canvas_size: Pixel canvas size used when ``normalized=False``.
            max_elem: Output slot count.

        Returns:
            Dictionary with internal ``layout``, normalized ``bbox``, labels, and mask.
        """
        bbox_t, labels_t, mask_t = prepare_layout_tensors(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
            clamp_converted_normalized=True,
        )
        bbox_t, labels_t, mask_t = self.pad(bbox_t, labels_t, mask_t, max_elem=max_elem)
        model_labels = torch.zeros_like(labels_t)
        model_labels[mask_t] = labels_t[mask_t] + 1
        class_one_hot = torch.nn.functional.one_hot(model_labels, num_classes=4).to(
            dtype=bbox_t.dtype
        )
        layout = torch.stack((class_one_hot, bbox_t), dim=2)
        return {"layout": layout, "bbox": bbox_t, "labels": labels_t, "mask": mask_t}

    def pad(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"],
        *,
        max_elem: int,
    ) -> tuple[
        Float[torch.Tensor, "batch padded_elements 4"],
        Int[torch.Tensor, "batch padded_elements"],
        Bool[torch.Tensor, "batch padded_elements"],
    ]:
        """Pad layout tensors to DS-GAN ``max_elem`` slots."""
        if bbox.shape[1] > max_elem:
            raise ValueError(f"DS-GAN supports at most {max_elem} elements")

        pad_count = max_elem - bbox.shape[1]
        if pad_count:
            bbox = torch.cat((bbox, torch.zeros(bbox.shape[0], pad_count, 4)), dim=1)
            labels = torch.cat(
                (labels, torch.zeros(labels.shape[0], pad_count, dtype=torch.long)),
                dim=1,
            )
            mask = torch.cat(
                (mask, torch.zeros(mask.shape[0], pad_count, dtype=torch.bool)),
                dim=1,
            )
        labels = labels.clone()
        labels[~mask] = 0
        return bbox, labels, mask

    def _resolve_saliency(
        self,
        batch_size: int,
        *,
        saliency: DSGANImageInput | Sequence[DSGANImageInput] | None,
        saliency_pfpnet: DSGANImageInput | Sequence[DSGANImageInput] | None,
        saliency_basnet: DSGANImageInput | Sequence[DSGANImageInput] | None,
    ) -> list[DSGANImageInput | Float[torch.Tensor, "1 height width"] | None]:
        if saliency is not None:
            rows = _ensure_batch(saliency)
            if len(rows) != batch_size:
                raise ValueError("saliency batch size must match images")

            return cast(
                list[DSGANImageInput | Float[torch.Tensor, "1 height width"] | None],
                rows,
            )
        if saliency_pfpnet is None and saliency_basnet is None:
            return [None] * batch_size
        first = (
            _ensure_batch(saliency_pfpnet)
            if saliency_pfpnet is not None
            else [None] * batch_size
        )
        second = (
            _ensure_batch(saliency_basnet)
            if saliency_basnet is not None
            else [None] * batch_size
        )
        if len(first) != batch_size or len(second) != batch_size:
            raise ValueError("saliency batch size must match images")

        merged: list[Float[torch.Tensor, "1 height width"]] = []
        for left, right in zip(first, second, strict=True):
            if left is None:
                merged.append(_to_l_tensor(right, self.image_size))
            elif right is None:
                merged.append(_to_l_tensor(left, self.image_size))
            else:
                merged.append(_merge_saliency_native(left, right, self.image_size))
        return cast(
            list[DSGANImageInput | Float[torch.Tensor, "1 height width"] | None], merged
        )

__init__

__init__(
    dataset_name: DatasetName
    | str = DatasetName.pku_posterlayout,
    id2label: dict[int | str, str] | None = None,
    image_size: tuple[int, int] | list[int] = (350, 240),
) -> None

Initialize processor metadata.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __init__(
    self,
    dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
    id2label: dict[int | str, str] | None = None,
    image_size: tuple[int, int] | list[int] = (350, 240),
) -> None:
    """Initialize processor metadata."""
    self.chat_template = None
    dataset = normalize_dataset_name(dataset_name)
    if dataset is not DatasetName.pku_posterlayout:
        raise ValueError(f"Unsupported DS-GAN dataset_name: {dataset_name}")

    self.dataset_name = str(dataset)
    default_id2label = {0: "text", 1: "logo", 2: "underlay"}
    raw_id2label = id2label or default_id2label
    self.id2label = {int(k): v for k, v in raw_id2label.items()}
    self.label2id = {v: k for k, v in self.id2label.items()}
    height, width = image_size
    self.image_size: tuple[int, int] = (int(height), int(width))

__call__

__call__(
    images: DSGANImageInput | Sequence[DSGANImageInput],
    *,
    saliency: DSGANImageInput
    | Sequence[DSGANImageInput]
    | None = None,
    saliency_pfpnet: DSGANImageInput
    | Sequence[DSGANImageInput]
    | None = None,
    saliency_basnet: DSGANImageInput
    | Sequence[DSGANImageInput]
    | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding

Encode content images into pixel_values.

Parameters:

Name Type Description Default
images DSGANImageInput | Sequence[DSGANImageInput]

RGB image or batch of RGB images.

required
saliency DSGANImageInput | Sequence[DSGANImageInput] | None

Optional saliency image or batch. If omitted and both saliency maps are given, the maps are merged by pixelwise max.

None
saliency_pfpnet DSGANImageInput | Sequence[DSGANImageInput] | None

Optional PFPNet saliency map.

None
saliency_basnet DSGANImageInput | Sequence[DSGANImageInput] | None

Optional BASNet saliency map.

None
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

'pt'

Returns:

Type Description
BatchEncoding

Batch encoding containing pixel_values shaped (B, 4, H, W).

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
 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
def __call__(
    self,
    images: DSGANImageInput | Sequence[DSGANImageInput],
    *,
    saliency: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
    saliency_pfpnet: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
    saliency_basnet: DSGANImageInput | Sequence[DSGANImageInput] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding:
    """Encode content images into ``pixel_values``.

    Args:
        images: RGB image or batch of RGB images.
        saliency: Optional saliency image or batch. If omitted and both
            saliency maps are given, the maps are merged by pixelwise max.
        saliency_pfpnet: Optional PFPNet saliency map.
        saliency_basnet: Optional BASNet saliency map.
        return_tensors: Tensor framework. Only ``pt`` is supported.

    Returns:
        Batch encoding containing ``pixel_values`` shaped ``(B, 4, H, W)``.
    """
    if return_tensors != "pt":
        raise ValueError("DSGANProcessor only supports return_tensors='pt'")

    image_rows = _ensure_batch(images)
    saliency_rows = self._resolve_saliency(
        len(image_rows),
        saliency=saliency,
        saliency_pfpnet=saliency_pfpnet,
        saliency_basnet=saliency_basnet,
    )
    tensors = []
    for image, sal in zip(image_rows, saliency_rows, strict=True):
        rgb = _to_rgb_tensor(image, self.image_size)
        sal_tensor = (
            torch.zeros(1, *self.image_size, dtype=torch.float32)
            if sal is None
            else _to_l_tensor(sal, self.image_size)
        )
        tensors.append(torch.cat((rgb, sal_tensor), dim=0))
    return BatchEncoding({"pixel_values": torch.stack(tensors)})

decode

decode(
    *,
    class_probs: Float[Tensor, "batch elements 4"],
    bbox: Float[Tensor, "batch elements 4"],
    output_type: Literal["dataclass", "dict"] = "dataclass",
    scores: Float[Tensor, "batch elements"] | None = None,
    intermediates: Mapping[
        str, Shaped[Tensor, "..."] | str | bool
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | Mapping[
            str, Shaped[torch.Tensor, "..."] | str | bool
        ]
        | None,
    ]
)

Decode raw DS-GAN class probabilities and boxes.

Parameters:

Name Type Description Default
class_probs Float[Tensor, 'batch elements 4']

Internal class probabilities shaped (B, E, 4).

required
bbox Float[Tensor, 'batch elements 4']

Normalized center xywh boxes shaped (B, E, 4).

required
output_type Literal['dataclass', 'dict']

Return format.

'dataclass'
scores Float[Tensor, 'batch elements'] | None

Optional per-element class scores.

None
intermediates Mapping[str, Shaped[Tensor, '...'] | str | bool] | None

Optional model-specific intermediate tensors.

None

Returns:

Type Description
LayoutGenerationOutput | dict[str, Shaped[Tensor, '...'] | dict[int, str] | Mapping[str, Shaped[Tensor, '...'] | str | bool] | None]

Shared layout output with public labels and mask semantics.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
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
def decode(
    self,
    *,
    class_probs: Float[torch.Tensor, "batch elements 4"],
    bbox: Float[torch.Tensor, "batch elements 4"],
    output_type: Literal["dataclass", "dict"] = "dataclass",
    scores: Float[torch.Tensor, "batch elements"] | None = None,
    intermediates: Mapping[str, Shaped[torch.Tensor, "..."] | str | bool]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | Mapping[str, Shaped[torch.Tensor, "..."] | str | bool]
        | None,
    ]
):
    """Decode raw DS-GAN class probabilities and boxes.

    Args:
        class_probs: Internal class probabilities shaped ``(B, E, 4)``.
        bbox: Normalized center ``xywh`` boxes shaped ``(B, E, 4)``.
        output_type: Return format.
        scores: Optional per-element class scores.
        intermediates: Optional model-specific intermediate tensors.

    Returns:
        Shared layout output with public labels and mask semantics.
    """
    class_ids = torch.argmax(class_probs, dim=-1)
    mask = class_ids != 0
    public_labels = (class_ids - 1).clamp_min(0).long()
    resolved_scores = scores
    if resolved_scores is None:
        resolved_scores = class_probs.max(dim=-1).values
    output = LayoutGenerationOutput(
        bbox=bbox.detach().cpu().clamp(0.0, 1.0),
        labels=public_labels.detach().cpu(),
        mask=mask.detach().cpu(),
        id2label=dict(self.id2label),
        scores=resolved_scores.detach().cpu(),
        intermediates=intermediates,
    )
    if output_type == "dict":
        return dict(output)
    if output_type == "dataclass":
        return output
    raise ValueError(f"Unsupported output_type: {output_type}")

encode_layout

encode_layout(
    *,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    max_elem: int = 32,
) -> dict[str, Shaped[torch.Tensor, "..."]]

Encode public boxes/labels into the internal layout tensor.

Parameters:

Name Type Description Default
bbox Float[Tensor, 'batch elements 4'] | Float[ndarray, 'batch elements 4'] | Sequence[ArrayLikeInput]

Public boxes.

required
labels Int[Tensor, 'batch elements'] | Int[ndarray, 'batch elements'] | Sequence[ArrayLikeInput]

Public zero-based semantic labels.

required
mask Bool[Tensor, 'batch elements'] | Bool[ndarray, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional valid-element mask.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether the boxes are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size used when normalized=False.

None
max_elem int

Output slot count.

32

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Dictionary with internal layout, normalized bbox, labels, and mask.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
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
def encode_layout(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    max_elem: int = 32,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Encode public boxes/labels into the internal layout tensor.

    Args:
        bbox: Public boxes.
        labels: Public zero-based semantic labels.
        mask: Optional valid-element mask.
        box_format: Input box format.
        normalized: Whether the boxes are normalized.
        canvas_size: Pixel canvas size used when ``normalized=False``.
        max_elem: Output slot count.

    Returns:
        Dictionary with internal ``layout``, normalized ``bbox``, labels, and mask.
    """
    bbox_t, labels_t, mask_t = prepare_layout_tensors(
        bbox=bbox,
        labels=labels,
        mask=mask,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
        clamp_converted_normalized=True,
    )
    bbox_t, labels_t, mask_t = self.pad(bbox_t, labels_t, mask_t, max_elem=max_elem)
    model_labels = torch.zeros_like(labels_t)
    model_labels[mask_t] = labels_t[mask_t] + 1
    class_one_hot = torch.nn.functional.one_hot(model_labels, num_classes=4).to(
        dtype=bbox_t.dtype
    )
    layout = torch.stack((class_one_hot, bbox_t), dim=2)
    return {"layout": layout, "bbox": bbox_t, "labels": labels_t, "mask": mask_t}

pad

pad(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"],
    *,
    max_elem: int,
) -> tuple[
    Float[torch.Tensor, "batch padded_elements 4"],
    Int[torch.Tensor, "batch padded_elements"],
    Bool[torch.Tensor, "batch padded_elements"],
]

Pad layout tensors to DS-GAN max_elem slots.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
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
def pad(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"],
    *,
    max_elem: int,
) -> tuple[
    Float[torch.Tensor, "batch padded_elements 4"],
    Int[torch.Tensor, "batch padded_elements"],
    Bool[torch.Tensor, "batch padded_elements"],
]:
    """Pad layout tensors to DS-GAN ``max_elem`` slots."""
    if bbox.shape[1] > max_elem:
        raise ValueError(f"DS-GAN supports at most {max_elem} elements")

    pad_count = max_elem - bbox.shape[1]
    if pad_count:
        bbox = torch.cat((bbox, torch.zeros(bbox.shape[0], pad_count, 4)), dim=1)
        labels = torch.cat(
            (labels, torch.zeros(labels.shape[0], pad_count, dtype=torch.long)),
            dim=1,
        )
        mask = torch.cat(
            (mask, torch.zeros(mask.shape[0], pad_count, dtype=torch.bool)),
            dim=1,
        )
    labels = labels.clone()
    labels[~mask] = 0
    return bbox, labels, mask

processor_for_dataset

processor_for_dataset(
    dataset_name: DatasetName | str,
) -> DSGANProcessor

Create a DS-GAN processor for a supported dataset.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
303
304
305
def processor_for_dataset(dataset_name: DatasetName | str) -> DSGANProcessor:
    """Create a DS-GAN processor for a supported dataset."""
    return DSGANProcessor(dataset_name=dataset_name)

annotations_from_pku_example

annotations_from_pku_example(
    example: Mapping[str, DSGANExampleValue],
    *,
    max_elem: int = 32,
) -> dict[
    str, Shaped[torch.Tensor, "..."] | tuple[int, int]
]

Convert a PKU PosterLayout dataset row into public layout tensors.

The adapter filters INVALID annotations, converts pixel ltrb boxes to normalized center xywh, derives canvas size from the image columns, and applies the reference designSeq.reorder ordering policy.

Source code in models/ds-gan/src/ds_gan/processing_ds_gan.py
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
def annotations_from_pku_example(
    example: Mapping[str, DSGANExampleValue],
    *,
    max_elem: int = 32,
) -> dict[str, Shaped[torch.Tensor, "..."] | tuple[int, int]]:
    """Convert a PKU PosterLayout dataset row into public layout tensors.

    The adapter filters ``INVALID`` annotations, converts pixel ``ltrb`` boxes
    to normalized center ``xywh``, derives canvas size from the image columns,
    and applies the reference ``designSeq.reorder`` ordering policy.
    """
    annotations = cast(
        Mapping[
            str, Sequence[str | int | float] | Sequence[Sequence[str | int | float]]
        ],
        example.get("annotations", example),
    )
    raw_labels = cast(Sequence[str | int | float], annotations["cls_elem"])
    raw_boxes = cast(
        Sequence[str | Sequence[int | float | str]], annotations["box_elem"]
    )
    canvas_size = _canvas_size_from_example(example)
    model_labels: list[int] = []
    public_labels: list[int] = []
    boxes: list[list[float]] = []

    for raw_label, raw_box in zip(raw_labels, raw_boxes, strict=True):
        label = str(raw_label)
        if label == "INVALID":
            continue
        public_id = PKU_DATASET_LABEL2ID[label]
        model_labels.append(PKU_MODEL_LABEL2ID[label])
        public_labels.append(public_id)
        boxes.append(_parse_box(raw_box))

    if boxes:
        box_t = torch.tensor(boxes, dtype=torch.float32)
        order = _designseq_reorder(model_labels, box_t, max_elem=max_elem)
        box_t = box_t[order]
        labels_t = torch.tensor([public_labels[i] for i in order], dtype=torch.long)
        bbox_t = normalize_boxes(
            box_t.unsqueeze(0), canvas_size=canvas_size, box_format="ltrb"
        ).squeeze(0)
    else:
        bbox_t = torch.zeros(0, 4, dtype=torch.float32)
        labels_t = torch.zeros(0, dtype=torch.long)
    mask_t = torch.ones(labels_t.shape, dtype=torch.bool)
    return {
        "bbox": bbox_t.unsqueeze(0),
        "labels": labels_t.unsqueeze(0),
        "mask": mask_t.unsqueeze(0),
        "canvas_size": canvas_size,
    }