Skip to content

Cgb dm

CGB-DM content-aware poster layout generation package.

CGBDMConfig

Bases: ConfigMixin

Store CGB-DM architecture, schedule, and dataset metadata.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Poster/content dataset key.

pku_posterlayout
num_labels int | None

Internal class-channel count, including invalid/pad.

None
max_seq_length int

Maximum number of layout elements.

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

Model image size as (height, width).

(384, 256)
canvas_size tuple[int, int] | list[int]

Dataset canvas size as (width, height).

(513, 750)
num_train_timesteps int

DDPM training timesteps.

1000
ddim_num_steps int

Default DDIM inference steps.

100
dim_model int

Transformer hidden dimension.

512
n_head int

Attention head count.

8
num_layers int

Number of layout decoder layers.

4
feature_dim int

Feed-forward hidden dimension.

1024
id2label Id2LabelMapping | None

Public id-to-label mapping, excluding invalid/pad.

None

Examples:

>>> CGBDMConfig().dataset_name
'pku_posterlayout'
Source code in models/cgb-dm/src/cgb_dm/configuration_cgb_dm.py
 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
class CGBDMConfig(ConfigMixin):
    """Store CGB-DM architecture, schedule, and dataset metadata.

    Args:
        dataset_name: Poster/content dataset key.
        num_labels: Internal class-channel count, including invalid/pad.
        max_seq_length: Maximum number of layout elements.
        image_size: Model image size as ``(height, width)``.
        canvas_size: Dataset canvas size as ``(width, height)``.
        num_train_timesteps: DDPM training timesteps.
        ddim_num_steps: Default DDIM inference steps.
        dim_model: Transformer hidden dimension.
        n_head: Attention head count.
        num_layers: Number of layout decoder layers.
        feature_dim: Feed-forward hidden dimension.
        id2label: Public id-to-label mapping, excluding invalid/pad.

    Examples:
        >>> CGBDMConfig().dataset_name
        'pku_posterlayout'
    """

    config_name = "cgb_dm_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
        num_labels: int | None = None,
        max_seq_length: int = 16,
        image_size: tuple[int, int] | list[int] = (384, 256),
        canvas_size: tuple[int, int] | list[int] = (513, 750),
        num_train_timesteps: int = 1000,
        ddim_num_steps: int = 100,
        dim_model: int = 512,
        n_head: int = 8,
        num_layers: int = 4,
        feature_dim: int = 1024,
        id2label: Id2LabelMapping | None = None,
        condition_types: list[str] | tuple[str, ...] | None = None,
        train_beta_schedule: str = "cosine",
        sampling_beta_schedule: str = "linear",
        model_subfolder: str = "model",
        scheduler_subfolder: str = "scheduler",
        processor_subfolder: str = "processor",
    ) -> None:
        """Initialize CGB-DM configuration."""
        dataset = normalize_dataset_name(dataset_name)
        spec = DATASET_SPECS.get(dataset)
        if spec is None:
            raise ValueError(f"Unsupported CGB-DM dataset_name: {dataset_name}")

        self.dataset_name = str(dataset)
        self.num_labels = int(num_labels or spec.num_labels)
        self.max_seq_length = int(max_seq_length)
        self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
        self.canvas_size: tuple[int, int] = (int(canvas_size[0]), int(canvas_size[1]))
        self.num_train_timesteps = int(num_train_timesteps)
        self.ddim_num_steps = int(ddim_num_steps)
        self.dim_model = int(dim_model)
        self.n_head = int(n_head)
        self.num_layers = int(num_layers)
        self.feature_dim = int(feature_dim)
        self.id2label = {int(k): v for k, v in (id2label or spec.id2label).items()}
        self.condition_types = list(
            condition_types
            or ["content_image", "label", "label_size", "completion", "refinement"]
        )
        self.train_beta_schedule = train_beta_schedule
        self.sampling_beta_schedule = sampling_beta_schedule
        self.model_subfolder = model_subfolder
        self.scheduler_subfolder = scheduler_subfolder
        self.processor_subfolder = processor_subfolder

    @property
    def seq_dim(self) -> int:
        """Return the internal layout channel count."""
        return self.num_labels + 4

    @property
    def public_num_labels(self) -> int:
        """Return the public semantic label count."""
        return len(self.id2label)

seq_dim property

seq_dim: int

Return the internal layout channel count.

public_num_labels property

public_num_labels: int

Return the public semantic label count.

__init__

__init__(
    *,
    dataset_name: DatasetName
    | str = DatasetName.pku_posterlayout,
    num_labels: int | None = None,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
    canvas_size: tuple[int, int] | list[int] = (513, 750),
    num_train_timesteps: int = 1000,
    ddim_num_steps: int = 100,
    dim_model: int = 512,
    n_head: int = 8,
    num_layers: int = 4,
    feature_dim: int = 1024,
    id2label: Id2LabelMapping | None = None,
    condition_types: list[str]
    | tuple[str, ...]
    | None = None,
    train_beta_schedule: str = "cosine",
    sampling_beta_schedule: str = "linear",
    model_subfolder: str = "model",
    scheduler_subfolder: str = "scheduler",
    processor_subfolder: str = "processor",
) -> None

Initialize CGB-DM configuration.

Source code in models/cgb-dm/src/cgb_dm/configuration_cgb_dm.py
 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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
    num_labels: int | None = None,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
    canvas_size: tuple[int, int] | list[int] = (513, 750),
    num_train_timesteps: int = 1000,
    ddim_num_steps: int = 100,
    dim_model: int = 512,
    n_head: int = 8,
    num_layers: int = 4,
    feature_dim: int = 1024,
    id2label: Id2LabelMapping | None = None,
    condition_types: list[str] | tuple[str, ...] | None = None,
    train_beta_schedule: str = "cosine",
    sampling_beta_schedule: str = "linear",
    model_subfolder: str = "model",
    scheduler_subfolder: str = "scheduler",
    processor_subfolder: str = "processor",
) -> None:
    """Initialize CGB-DM configuration."""
    dataset = normalize_dataset_name(dataset_name)
    spec = DATASET_SPECS.get(dataset)
    if spec is None:
        raise ValueError(f"Unsupported CGB-DM dataset_name: {dataset_name}")

    self.dataset_name = str(dataset)
    self.num_labels = int(num_labels or spec.num_labels)
    self.max_seq_length = int(max_seq_length)
    self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
    self.canvas_size: tuple[int, int] = (int(canvas_size[0]), int(canvas_size[1]))
    self.num_train_timesteps = int(num_train_timesteps)
    self.ddim_num_steps = int(ddim_num_steps)
    self.dim_model = int(dim_model)
    self.n_head = int(n_head)
    self.num_layers = int(num_layers)
    self.feature_dim = int(feature_dim)
    self.id2label = {int(k): v for k, v in (id2label or spec.id2label).items()}
    self.condition_types = list(
        condition_types
        or ["content_image", "label", "label_size", "completion", "refinement"]
    )
    self.train_beta_schedule = train_beta_schedule
    self.sampling_beta_schedule = sampling_beta_schedule
    self.model_subfolder = model_subfolder
    self.scheduler_subfolder = scheduler_subfolder
    self.processor_subfolder = processor_subfolder

CGBDMModelOutput dataclass

Bases: BaseOutput

Output returned by the CGB-DM denoiser.

Attributes:

Name Type Description
sample Float[Tensor, 'batch elements channels']

Predicted epsilon tensor with the same shape as the input layout.

cgb_weight Float[Tensor, 'batch 1 1'] | None

Content-graphic balance weight estimated from image tokens.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
22
23
24
25
26
27
28
29
30
31
32
@dataclass
class CGBDMModelOutput(BaseOutput):
    """Output returned by the CGB-DM denoiser.

    Attributes:
        sample: Predicted epsilon tensor with the same shape as the input layout.
        cgb_weight: Content-graphic balance weight estimated from image tokens.
    """

    sample: Float[torch.Tensor, "batch elements channels"]
    cgb_weight: Float[torch.Tensor, "batch 1 1"] | None = None

CGBDMTransformerModel

Bases: ModelMixin, ConfigMixin

CGB-DM transformer denoiser with image and saliency conditioning.

Parameters:

Name Type Description Default
num_labels int

Internal class-channel count including invalid/pad.

4
max_seq_length int

Maximum number of layout elements.

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

Image tensor size as (height, width).

(384, 256)
patch_size int

Image patch size.

32
dim_model int

Hidden dimension.

512
n_head int

Attention head count.

8
feature_dim int

Feed-forward hidden dimension.

1024
num_layers int

Number of decoder layers.

4
num_train_timesteps int

Number of training diffusion steps.

1000

Examples:

>>> model = CGBDMTransformerModel(num_labels=4, max_seq_length=2, image_size=(32, 32), dim_model=16, n_head=2, feature_dim=32, num_layers=1)
>>> model.seq_dim
8
Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
class CGBDMTransformerModel(ModelMixin, ConfigMixin):
    """CGB-DM transformer denoiser with image and saliency conditioning.

    Args:
        num_labels: Internal class-channel count including invalid/pad.
        max_seq_length: Maximum number of layout elements.
        image_size: Image tensor size as ``(height, width)``.
        patch_size: Image patch size.
        dim_model: Hidden dimension.
        n_head: Attention head count.
        feature_dim: Feed-forward hidden dimension.
        num_layers: Number of decoder layers.
        num_train_timesteps: Number of training diffusion steps.

    Examples:
        >>> model = CGBDMTransformerModel(num_labels=4, max_seq_length=2, image_size=(32, 32), dim_model=16, n_head=2, feature_dim=32, num_layers=1)
        >>> model.seq_dim
        8
    """

    config_name = "model_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        num_labels: int = 4,
        max_seq_length: int = 16,
        image_size: tuple[int, int] | list[int] = (384, 256),
        patch_size: int = 32,
        dim_model: int = 512,
        n_head: int = 8,
        feature_dim: int = 1024,
        num_layers: int = 4,
        num_train_timesteps: int = 1000,
    ) -> None:
        """Initialize the CGB-DM denoising network."""
        super().__init__()
        self.num_labels = int(num_labels)
        self.max_seq_length = int(max_seq_length)
        self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
        self.seq_dim = self.num_labels + 4
        self.img_encoder = CGBDMImageEncoder(
            image_size=self.image_size,
            patch_size=patch_size,
            in_channels=4,
            dim_model=dim_model,
            depth=6,
            heads=8,
            mlp_dim=2048,
            dropout=0.1,
            emb_dropout=0.1,
        )
        self.layout_encoder = CGBDMLayoutModule(
            seq_dim=self.seq_dim,
            dim_model=dim_model,
            n_head=n_head,
            feature_dim=feature_dim,
            num_layers=num_layers // 2,
            num_train_timesteps=num_train_timesteps,
            max_seq_length=self.max_seq_length,
            if_encoder=True,
        )
        self.layout_decoder = CGBDMLayoutModule(
            seq_dim=self.seq_dim,
            dim_model=dim_model,
            n_head=n_head,
            feature_dim=feature_dim,
            num_layers=num_layers,
            num_train_timesteps=num_train_timesteps,
            max_seq_length=self.max_seq_length,
            if_encoder=False,
        )
        self.cgbwp = CGBDMQFormer(in_dim=dim_model, num_tokens=1)
        self.salbox_encoder = CGBDMMLP(4, dim_model, dim_model)

    def forward(
        self,
        sample: Float[torch.Tensor, "batch elements channels"],
        image: Float[torch.Tensor, "batch 4 height width"],
        saliency_box: Float[torch.Tensor, "batch 1 4"],
        timestep: Int[torch.Tensor, "batch"],
        return_dict: bool = True,
    ) -> CGBDMModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
        """Predict epsilon for a noisy layout tensor.

        Args:
            sample: Noisy class-plus-box layout tensor.
            image: Four-channel RGB/saliency tensor in ``[-1, 1]``.
            saliency_box: Saliency box tensor in internal ``[-1, 1]`` center xywh.
            timestep: Per-example diffusion timestep ids.
            return_dict: Whether to return ``CGBDMModelOutput``.

        Returns:
            Output dataclass or one-item tuple containing predicted epsilon.
        """
        image_tokens = self.img_encoder(image)
        saliency_tokens = self.salbox_encoder(saliency_box)
        encoded = self.layout_encoder(sample, None, None, None, timestep)
        cgb_weight = self.cgbwp(image_tokens)
        pred = self.layout_decoder(
            encoded,
            image_tokens,
            cgb_weight,
            saliency_tokens,
            timestep,
        )
        if not return_dict:
            return (pred,)
        return CGBDMModelOutput(sample=pred, cgb_weight=cgb_weight)

__init__

__init__(
    *,
    num_labels: int = 4,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
    patch_size: int = 32,
    dim_model: int = 512,
    n_head: int = 8,
    feature_dim: int = 1024,
    num_layers: int = 4,
    num_train_timesteps: int = 1000,
) -> None

Initialize the CGB-DM denoising network.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
@register_to_config
def __init__(
    self,
    *,
    num_labels: int = 4,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
    patch_size: int = 32,
    dim_model: int = 512,
    n_head: int = 8,
    feature_dim: int = 1024,
    num_layers: int = 4,
    num_train_timesteps: int = 1000,
) -> None:
    """Initialize the CGB-DM denoising network."""
    super().__init__()
    self.num_labels = int(num_labels)
    self.max_seq_length = int(max_seq_length)
    self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
    self.seq_dim = self.num_labels + 4
    self.img_encoder = CGBDMImageEncoder(
        image_size=self.image_size,
        patch_size=patch_size,
        in_channels=4,
        dim_model=dim_model,
        depth=6,
        heads=8,
        mlp_dim=2048,
        dropout=0.1,
        emb_dropout=0.1,
    )
    self.layout_encoder = CGBDMLayoutModule(
        seq_dim=self.seq_dim,
        dim_model=dim_model,
        n_head=n_head,
        feature_dim=feature_dim,
        num_layers=num_layers // 2,
        num_train_timesteps=num_train_timesteps,
        max_seq_length=self.max_seq_length,
        if_encoder=True,
    )
    self.layout_decoder = CGBDMLayoutModule(
        seq_dim=self.seq_dim,
        dim_model=dim_model,
        n_head=n_head,
        feature_dim=feature_dim,
        num_layers=num_layers,
        num_train_timesteps=num_train_timesteps,
        max_seq_length=self.max_seq_length,
        if_encoder=False,
    )
    self.cgbwp = CGBDMQFormer(in_dim=dim_model, num_tokens=1)
    self.salbox_encoder = CGBDMMLP(4, dim_model, dim_model)

forward

forward(
    sample: Float[Tensor, "batch elements channels"],
    image: Float[Tensor, "batch 4 height width"],
    saliency_box: Float[Tensor, "batch 1 4"],
    timestep: Int[Tensor, "batch"],
    return_dict: bool = True,
) -> (
    CGBDMModelOutput
    | tuple[Float[torch.Tensor, "batch elements channels"]]
)

Predict epsilon for a noisy layout tensor.

Parameters:

Name Type Description Default
sample Float[Tensor, 'batch elements channels']

Noisy class-plus-box layout tensor.

required
image Float[Tensor, 'batch 4 height width']

Four-channel RGB/saliency tensor in [-1, 1].

required
saliency_box Float[Tensor, 'batch 1 4']

Saliency box tensor in internal [-1, 1] center xywh.

required
timestep Int[Tensor, 'batch']

Per-example diffusion timestep ids.

required
return_dict bool

Whether to return CGBDMModelOutput.

True

Returns:

Type Description
CGBDMModelOutput | tuple[Float[Tensor, 'batch elements channels']]

Output dataclass or one-item tuple containing predicted epsilon.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
def forward(
    self,
    sample: Float[torch.Tensor, "batch elements channels"],
    image: Float[torch.Tensor, "batch 4 height width"],
    saliency_box: Float[torch.Tensor, "batch 1 4"],
    timestep: Int[torch.Tensor, "batch"],
    return_dict: bool = True,
) -> CGBDMModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
    """Predict epsilon for a noisy layout tensor.

    Args:
        sample: Noisy class-plus-box layout tensor.
        image: Four-channel RGB/saliency tensor in ``[-1, 1]``.
        saliency_box: Saliency box tensor in internal ``[-1, 1]`` center xywh.
        timestep: Per-example diffusion timestep ids.
        return_dict: Whether to return ``CGBDMModelOutput``.

    Returns:
        Output dataclass or one-item tuple containing predicted epsilon.
    """
    image_tokens = self.img_encoder(image)
    saliency_tokens = self.salbox_encoder(saliency_box)
    encoded = self.layout_encoder(sample, None, None, None, timestep)
    cgb_weight = self.cgbwp(image_tokens)
    pred = self.layout_decoder(
        encoded,
        image_tokens,
        cgb_weight,
        saliency_tokens,
        timestep,
    )
    if not return_dict:
        return (pred,)
    return CGBDMModelOutput(sample=pred, cgb_weight=cgb_weight)

CGBDMPipeline

Bases: DiffusionPipeline

Generate content-aware poster layouts with CGB-DM.

Parameters:

Name Type Description Default
model CGBDMTransformerModel

CGB-DM denoiser.

required
scheduler CGBDMScheduler

CGB-DM scheduler.

required
processor CGBDMProcessor

Processor for images and layouts.

required

Examples:

>>> model = CGBDMTransformerModel(num_labels=4, max_seq_length=2, image_size=(32, 32), dim_model=16, n_head=2, feature_dim=32, num_layers=1)
>>> pipe = CGBDMPipeline(model=model, scheduler=CGBDMScheduler(num_train_timesteps=10, ddim_num_steps=1), processor=CGBDMProcessor(max_seq_length=2, image_size=(32, 32)))
>>> pipe.processor.seq_dim
8
Source code in models/cgb-dm/src/cgb_dm/pipeline_cgb_dm.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
class CGBDMPipeline(DiffusionPipeline):
    """Generate content-aware poster layouts with CGB-DM.

    Args:
        model: CGB-DM denoiser.
        scheduler: CGB-DM scheduler.
        processor: Processor for images and layouts.

    Examples:
        >>> model = CGBDMTransformerModel(num_labels=4, max_seq_length=2, image_size=(32, 32), dim_model=16, n_head=2, feature_dim=32, num_layers=1)
        >>> pipe = CGBDMPipeline(model=model, scheduler=CGBDMScheduler(num_train_timesteps=10, ddim_num_steps=1), processor=CGBDMProcessor(max_seq_length=2, image_size=(32, 32)))
        >>> pipe.processor.seq_dim
        8
    """

    model_cpu_offload_seq = "model"

    def __init__(
        self,
        model: CGBDMTransformerModel,
        scheduler: CGBDMScheduler,
        processor: CGBDMProcessor,
    ) -> None:
        """Initialize pipeline components."""
        super().__init__()
        self.register_modules(model=model, scheduler=scheduler, processor=processor)
        self.model = model
        self.scheduler = scheduler
        self.processor = processor
        self.model.eval()

    @property
    def components(
        self,
    ) -> dict[str, CGBDMTransformerModel | CGBDMScheduler | CGBDMProcessor]:
        """Return serializable pipeline components."""
        return {
            "model": self.model,
            "scheduler": self.scheduler,
            "processor": self.processor,
        }

    @torch.no_grad()
    def __call__(
        self,
        *,
        image: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | None = None,
        content: dict[
            str,
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes],
        ]
        | None = None,
        saliency: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | None = None,
        saliency_isnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | None = None,
        saliency_basnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | None = None,
        saliency_box: Float[torch.Tensor, "..."] | None = None,
        pixel_values: Float[torch.Tensor, "batch channels height width"] | 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, "..."]
        | Int[np.ndarray, "..."]
        | list[list[int]]
        | list[int]
        | list[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "..."]
        | Float[np.ndarray, "..."]
        | list[list[list[float]]]
        | list[list[float]]
        | list[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "..."]
        | Bool[np.ndarray, "..."]
        | list[list[bool]]
        | list[bool]
        | list[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "..."] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        completion_ratio: float = 0.2,
        output_type: OutputType | str = OutputType.dataclass,
        return_intermediates: bool = False,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | dict[int, str]
            | list[Float[torch.Tensor, "..."]]
            | dict[str, str | Float[torch.Tensor, "..."] | None]
            | None,
        ]
    ):
        """Run DDIM sampling and return generated layouts.

        Args:
            image: RGB image or batch of images.
            content: Optional content container with ``image`` and saliency keys.
            saliency: Optional merged saliency map.
            saliency_isnet: Optional first saliency map.
            saliency_basnet: Optional second saliency map.
            saliency_box: Optional normalized center ``xywh`` saliency box.
            pixel_values: Preprocessed four-channel image tensor.
            batch_size: Number of layouts when ``pixel_values`` is synthetic.
            seed: Convenience seed used when ``generator`` is absent.
            generator: Optional torch generator. Takes precedence over ``seed``.
            condition_type: Canonical condition mode or alias.
            labels: Conditioning labels for constrained modes.
            bbox: Conditioning boxes for constrained modes.
            mask: Optional valid-element mask.
            num_elements: Accepted for interface compatibility.
            box_format: Input box format.
            normalized: Whether input boxes are normalized.
            canvas_size: Canvas size required for pixel boxes.
            num_inference_steps: DDIM step count.
            completion_ratio: Completion conditioning keep ratio.
            output_type: ``"dataclass"`` or ``"dict"``.
            return_intermediates: Whether to include trajectory/debug tensors.

        Returns:
            Layout output dataclass or dictionary.

        Raises:
            ValueError: If required content or conditioning inputs are absent.
        """
        del num_elements
        canonical = normalize_condition_type(condition_type)
        out_type = normalize_output_type(output_type)
        if generator is None and seed is not None:
            generator = torch.Generator(device=self.device).manual_seed(seed)
        if pixel_values is None:
            if content is not None:
                image = content.get("image", image)
                saliency = content.get("saliency", saliency)
            encoded_content = self.processor(
                image,
                saliency=saliency,
                saliency_isnet=saliency_isnet,
                saliency_basnet=saliency_basnet,
                saliency_box=saliency_box,
            )
            pixel_values = encoded_content["pixel_values"]
            resolved_saliency_box = encoded_content["saliency_box"]
        else:
            if saliency_box is None:
                resolved_saliency_box = torch.zeros(pixel_values.shape[0], 1, 4)
            else:
                resolved_saliency_box = 2 * (
                    torch.as_tensor(saliency_box, dtype=torch.float32).clamp(0.0, 1.0)
                    - 0.5
                )
                if resolved_saliency_box.ndim == 2:
                    resolved_saliency_box = resolved_saliency_box.unsqueeze(1)
        batch_size = int(
            pixel_values.shape[0] if pixel_values is not None else batch_size
        )
        encoded_layout = None
        if canonical is not ConditionType.content_image:
            if bbox is None or labels is None:
                raise ValueError(
                    f"bbox and labels are required for condition_type={condition_type}"
                )

            encoded_layout = self.processor.encode_layout(
                bbox=bbox,
                labels=labels,
                mask=mask,
                box_format=box_format,
                normalized=normalized,
                canvas_size=canvas_size,
            )
            batch_size = encoded_layout["layout"].shape[0]
        self.scheduler.set_timesteps(num_inference_steps, device=self.device)
        sample = self.scheduler.initial_sample(
            batch_size,
            self.processor.max_seq_length,
            self.processor.seq_dim,
            device=self.device,
            generator=generator,
        )
        real_layout = (
            None if encoded_layout is None else encoded_layout["layout"].to(self.device)
        )
        fix_mask = None
        if real_layout is not None:
            fix_mask = self.scheduler.condition_mask(
                real_layout,
                canonical,
                completion_ratio=completion_ratio,
                generator=generator,
            )
            sample = torch.where(fix_mask, real_layout, sample)
        pixel_values = pixel_values.to(self.device)
        resolved_saliency_box = resolved_saliency_box.to(self.device)
        trajectory: list[Float[torch.Tensor, "..."]] = []
        cgb_weights: list[Float[torch.Tensor, "..."]] = []
        for index, timestep in enumerate(self.scheduler.timesteps):
            timestep_batch = torch.full(
                (batch_size,),
                int(timestep.item()),
                device=self.device,
                dtype=torch.long,
            )
            model_out = self.model(
                sample, pixel_values, resolved_saliency_box, timestep_batch
            )
            step = self.scheduler.step(
                model_out.sample,
                timestep_batch,
                sample,
                len(self.scheduler.timesteps) - index - 1,
                generator=generator,
            )
            sample = step.prev_sample
            if real_layout is not None and fix_mask is not None:
                sample = torch.where(fix_mask, real_layout, sample)
            if return_intermediates:
                trajectory.append(sample.detach().cpu())
                if model_out.cgb_weight is not None:
                    cgb_weights.append(model_out.cgb_weight.detach().cpu())
        intermediates = None
        if return_intermediates:
            intermediates = {
                "condition_type": str(canonical),
                "saliency_box": resolved_saliency_box.detach().cpu(),
                "cgb_weight": cgb_weights[-1] if cgb_weights else None,
            }
        return self._decode(sample, out_type, trajectory, intermediates)

    def _decode(
        self,
        sample: Float[torch.Tensor, "..."],
        output_type: OutputType,
        trajectory: list[Float[torch.Tensor, "..."]],
        intermediates: dict[str, str | Float[torch.Tensor, "..."] | None] | None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | dict[int, str]
            | list[Float[torch.Tensor, "..."]]
            | dict[str, str | Float[torch.Tensor, "..."] | None]
            | None,
        ]
    ):
        decoded = self.processor.decode(
            sample.detach().cpu(),
            output_type="dataclass",
            intermediates=intermediates,
        )
        output = LayoutGenerationOutput(
            bbox=decoded.bbox,
            labels=decoded.labels,
            mask=decoded.mask,
            id2label=decoded.id2label,
            sequences=decoded.sequences,
            scores=decoded.scores,
            trajectory=trajectory or None,
            intermediates=decoded.intermediates,
        )
        if output_type is OutputType.dict:
            return dict(output)
        return output

components property

components: dict[
    str,
    CGBDMTransformerModel | CGBDMScheduler | CGBDMProcessor,
]

Return serializable pipeline components.

__init__

__init__(
    model: CGBDMTransformerModel,
    scheduler: CGBDMScheduler,
    processor: CGBDMProcessor,
) -> None

Initialize pipeline components.

Source code in models/cgb-dm/src/cgb_dm/pipeline_cgb_dm.py
110
111
112
113
114
115
116
117
118
119
120
121
122
def __init__(
    self,
    model: CGBDMTransformerModel,
    scheduler: CGBDMScheduler,
    processor: CGBDMProcessor,
) -> None:
    """Initialize pipeline components."""
    super().__init__()
    self.register_modules(model=model, scheduler=scheduler, processor=processor)
    self.model = model
    self.scheduler = scheduler
    self.processor = processor
    self.model.eval()

__call__

__call__(
    *,
    image: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    content: dict[
        str,
        Float[Tensor, "..."]
        | Image
        | str
        | bytes
        | Path
        | IO[bytes],
    ]
    | None = None,
    saliency: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_isnet: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_basnet: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_box: Float[Tensor, "..."] | None = None,
    pixel_values: Float[
        Tensor, "batch channels height width"
    ]
    | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.content_image,
    labels: Int[Tensor, "..."]
    | Int[ndarray, "..."]
    | list[list[int]]
    | list[int]
    | list[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "..."]
    | Float[ndarray, "..."]
    | list[list[list[float]]]
    | list[list[float]]
    | list[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "..."]
    | Bool[ndarray, "..."]
    | list[list[bool]]
    | list[bool]
    | list[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "..."]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    completion_ratio: float = 0.2,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | dict[int, str]
        | list[Float[torch.Tensor, "..."]]
        | dict[str, str | Float[torch.Tensor, "..."] | None]
        | None,
    ]
)

Run DDIM sampling and return generated layouts.

Parameters:

Name Type Description Default
image Float[Tensor, '...'] | Image | str | bytes | Path | IO[bytes] | None

RGB image or batch of images.

None
content dict[str, Float[Tensor, '...'] | Image | str | bytes | Path | IO[bytes]] | None

Optional content container with image and saliency keys.

None
saliency Float[Tensor, '...'] | Image | str | bytes | Path | IO[bytes] | None

Optional merged saliency map.

None
saliency_isnet Float[Tensor, '...'] | Image | str | bytes | Path | IO[bytes] | None

Optional first saliency map.

None
saliency_basnet Float[Tensor, '...'] | Image | str | bytes | Path | IO[bytes] | None

Optional second saliency map.

None
saliency_box Float[Tensor, '...'] | None

Optional normalized center xywh saliency box.

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

Preprocessed four-channel image tensor.

None
batch_size int

Number of layouts when pixel_values is synthetic.

1
seed int | None

Convenience seed used when generator is absent.

None
generator Generator | None

Optional torch generator. Takes precedence over seed.

None
condition_type ConditionType | str

Canonical condition mode or alias.

content_image
labels Int[Tensor, '...'] | Int[ndarray, '...'] | list[list[int]] | list[int] | list[ArrayLikeInput] | None

Conditioning labels for constrained modes.

None
bbox Float[Tensor, '...'] | Float[ndarray, '...'] | list[list[list[float]]] | list[list[float]] | list[ArrayLikeInput] | None

Conditioning boxes for constrained modes.

None
mask Bool[Tensor, '...'] | Bool[ndarray, '...'] | list[list[bool]] | list[bool] | list[ArrayLikeInput] | None

Optional valid-element mask.

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

Accepted for interface compatibility.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether input boxes are normalized.

True
canvas_size tuple[int, int] | None

Canvas size required for pixel boxes.

None
num_inference_steps int | None

DDIM step count.

None
completion_ratio float

Completion conditioning keep ratio.

0.2
output_type OutputType | str

"dataclass" or "dict".

dataclass
return_intermediates bool

Whether to include trajectory/debug tensors.

False

Returns:

Type Description
LayoutGenerationOutput | dict[str, Float[Tensor, '...'] | Int[Tensor, '...'] | Bool[Tensor, '...'] | dict[int, str] | list[Float[Tensor, '...']] | dict[str, str | Float[Tensor, '...'] | None] | None]

Layout output dataclass or dictionary.

Raises:

Type Description
ValueError

If required content or conditioning inputs are absent.

Source code in models/cgb-dm/src/cgb_dm/pipeline_cgb_dm.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
@torch.no_grad()
def __call__(
    self,
    *,
    image: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    content: dict[
        str,
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes],
    ]
    | None = None,
    saliency: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_isnet: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_basnet: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_box: Float[torch.Tensor, "..."] | None = None,
    pixel_values: Float[torch.Tensor, "batch channels height width"] | 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, "..."]
    | Int[np.ndarray, "..."]
    | list[list[int]]
    | list[int]
    | list[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "..."]
    | Float[np.ndarray, "..."]
    | list[list[list[float]]]
    | list[list[float]]
    | list[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "..."]
    | Bool[np.ndarray, "..."]
    | list[list[bool]]
    | list[bool]
    | list[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "..."] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    completion_ratio: float = 0.2,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | dict[int, str]
        | list[Float[torch.Tensor, "..."]]
        | dict[str, str | Float[torch.Tensor, "..."] | None]
        | None,
    ]
):
    """Run DDIM sampling and return generated layouts.

    Args:
        image: RGB image or batch of images.
        content: Optional content container with ``image`` and saliency keys.
        saliency: Optional merged saliency map.
        saliency_isnet: Optional first saliency map.
        saliency_basnet: Optional second saliency map.
        saliency_box: Optional normalized center ``xywh`` saliency box.
        pixel_values: Preprocessed four-channel image tensor.
        batch_size: Number of layouts when ``pixel_values`` is synthetic.
        seed: Convenience seed used when ``generator`` is absent.
        generator: Optional torch generator. Takes precedence over ``seed``.
        condition_type: Canonical condition mode or alias.
        labels: Conditioning labels for constrained modes.
        bbox: Conditioning boxes for constrained modes.
        mask: Optional valid-element mask.
        num_elements: Accepted for interface compatibility.
        box_format: Input box format.
        normalized: Whether input boxes are normalized.
        canvas_size: Canvas size required for pixel boxes.
        num_inference_steps: DDIM step count.
        completion_ratio: Completion conditioning keep ratio.
        output_type: ``"dataclass"`` or ``"dict"``.
        return_intermediates: Whether to include trajectory/debug tensors.

    Returns:
        Layout output dataclass or dictionary.

    Raises:
        ValueError: If required content or conditioning inputs are absent.
    """
    del num_elements
    canonical = normalize_condition_type(condition_type)
    out_type = normalize_output_type(output_type)
    if generator is None and seed is not None:
        generator = torch.Generator(device=self.device).manual_seed(seed)
    if pixel_values is None:
        if content is not None:
            image = content.get("image", image)
            saliency = content.get("saliency", saliency)
        encoded_content = self.processor(
            image,
            saliency=saliency,
            saliency_isnet=saliency_isnet,
            saliency_basnet=saliency_basnet,
            saliency_box=saliency_box,
        )
        pixel_values = encoded_content["pixel_values"]
        resolved_saliency_box = encoded_content["saliency_box"]
    else:
        if saliency_box is None:
            resolved_saliency_box = torch.zeros(pixel_values.shape[0], 1, 4)
        else:
            resolved_saliency_box = 2 * (
                torch.as_tensor(saliency_box, dtype=torch.float32).clamp(0.0, 1.0)
                - 0.5
            )
            if resolved_saliency_box.ndim == 2:
                resolved_saliency_box = resolved_saliency_box.unsqueeze(1)
    batch_size = int(
        pixel_values.shape[0] if pixel_values is not None else batch_size
    )
    encoded_layout = None
    if canonical is not ConditionType.content_image:
        if bbox is None or labels is None:
            raise ValueError(
                f"bbox and labels are required for condition_type={condition_type}"
            )

        encoded_layout = self.processor.encode_layout(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        batch_size = encoded_layout["layout"].shape[0]
    self.scheduler.set_timesteps(num_inference_steps, device=self.device)
    sample = self.scheduler.initial_sample(
        batch_size,
        self.processor.max_seq_length,
        self.processor.seq_dim,
        device=self.device,
        generator=generator,
    )
    real_layout = (
        None if encoded_layout is None else encoded_layout["layout"].to(self.device)
    )
    fix_mask = None
    if real_layout is not None:
        fix_mask = self.scheduler.condition_mask(
            real_layout,
            canonical,
            completion_ratio=completion_ratio,
            generator=generator,
        )
        sample = torch.where(fix_mask, real_layout, sample)
    pixel_values = pixel_values.to(self.device)
    resolved_saliency_box = resolved_saliency_box.to(self.device)
    trajectory: list[Float[torch.Tensor, "..."]] = []
    cgb_weights: list[Float[torch.Tensor, "..."]] = []
    for index, timestep in enumerate(self.scheduler.timesteps):
        timestep_batch = torch.full(
            (batch_size,),
            int(timestep.item()),
            device=self.device,
            dtype=torch.long,
        )
        model_out = self.model(
            sample, pixel_values, resolved_saliency_box, timestep_batch
        )
        step = self.scheduler.step(
            model_out.sample,
            timestep_batch,
            sample,
            len(self.scheduler.timesteps) - index - 1,
            generator=generator,
        )
        sample = step.prev_sample
        if real_layout is not None and fix_mask is not None:
            sample = torch.where(fix_mask, real_layout, sample)
        if return_intermediates:
            trajectory.append(sample.detach().cpu())
            if model_out.cgb_weight is not None:
                cgb_weights.append(model_out.cgb_weight.detach().cpu())
    intermediates = None
    if return_intermediates:
        intermediates = {
            "condition_type": str(canonical),
            "saliency_box": resolved_saliency_box.detach().cpu(),
            "cgb_weight": cgb_weights[-1] if cgb_weights else None,
        }
    return self._decode(sample, out_type, trajectory, intermediates)

OutputType

Bases: StrEnum

Supported CGB-DM pipeline output containers.

Source code in models/cgb-dm/src/cgb_dm/pipeline_cgb_dm.py
25
26
27
28
29
class OutputType(StrEnum):
    """Supported CGB-DM pipeline output containers."""

    dataclass = auto()
    dict = auto()

CGBDMProcessor

Bases: ProcessorMixin

Prepare RGB/saliency inputs and decode CGB-DM layout tensors.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Poster/content dataset key.

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

Public id-to-label mapping excluding invalid/pad.

None
num_labels int

Internal class-channel count.

4
max_seq_length int

Maximum number of elements.

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

Resize target as (height, width).

(384, 256)

Examples:

>>> CGBDMProcessor().seq_dim
8
Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.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
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
class CGBDMProcessor(ProcessorMixin):
    """Prepare RGB/saliency inputs and decode CGB-DM layout tensors.

    Args:
        dataset_name: Poster/content dataset key.
        id2label: Public id-to-label mapping excluding invalid/pad.
        num_labels: Internal class-channel count.
        max_seq_length: Maximum number of elements.
        image_size: Resize target as ``(height, width)``.

    Examples:
        >>> CGBDMProcessor().seq_dim
        8
    """

    config_name = "processor_config.json"

    def __init__(
        self,
        dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
        id2label: Mapping[int, str] | Mapping[str, str] | None = None,
        num_labels: int = 4,
        max_seq_length: int = 16,
        image_size: tuple[int, int] | list[int] = (384, 256),
    ) -> None:
        """Initialize processor metadata."""
        super().__init__()
        dataset = normalize_dataset_name(dataset_name)
        labels = id2label_for_dataset(dataset)
        public_labels = {
            key: value for key, value in labels.items() if value != "INVALID"
        }
        self.dataset_name = str(dataset)
        self.id2label = {int(k): v for k, v in (id2label or public_labels).items()}
        self.label2id = {v: k for k, v in self.id2label.items()}
        self.num_labels = int(num_labels)
        self.max_seq_length = int(max_seq_length)
        self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
        self.chat_template = None

    @property
    def seq_dim(self) -> int:
        """Return the internal class-plus-box channel count."""
        return self.num_labels + 4

    def __call__(
        self,
        images: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None = None,
        *,
        saliency: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None = None,
        saliency_isnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None = None,
        saliency_basnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None = None,
        saliency_box: Float[torch.Tensor, "..."] | None = None,
        return_tensors: Literal["pt"] = "pt",
    ) -> BatchEncoding:
        """Encode image and saliency inputs into model tensors."""
        if return_tensors != "pt":
            raise ValueError("CGBDMProcessor only supports return_tensors='pt'")

        image_rows = _ensure_batch(images)
        saliency_rows = self._resolve_saliency(
            len(image_rows),
            saliency=saliency,
            saliency_isnet=saliency_isnet,
            saliency_basnet=saliency_basnet,
        )
        pixel_values = []
        boxes = []
        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)
                if sal is None
                else _to_l_tensor(sal, self.image_size)
            )
            pixel_values.append(torch.cat((rgb, sal_tensor), dim=0))
            boxes.append(_saliency_box_from_tensor(sal_tensor))
        resolved_box = (
            torch.stack(boxes)
            if saliency_box is None
            else torch.as_tensor(saliency_box, dtype=torch.float32)
        )
        if resolved_box.ndim == 1:
            resolved_box = resolved_box.reshape(1, 1, 4)
        elif resolved_box.ndim == 2:
            resolved_box = resolved_box.unsqueeze(1)
        return BatchEncoding(
            {
                "pixel_values": torch.stack(pixel_values),
                "saliency_box": 2 * (resolved_box.clamp(0.0, 1.0) - 0.5),
            }
        )

    def encode_layout(
        self,
        *,
        bbox: Float[torch.Tensor, "..."]
        | Float[np.ndarray, "..."]
        | Sequence[Sequence[Sequence[float]]]
        | Sequence[Sequence[float]]
        | Sequence[ArrayLikeInput],
        labels: Int[torch.Tensor, "..."]
        | Int[np.ndarray, "..."]
        | Sequence[Sequence[int]]
        | Sequence[int]
        | Sequence[ArrayLikeInput],
        mask: Bool[torch.Tensor, "..."]
        | Bool[np.ndarray, "..."]
        | Sequence[Sequence[bool]]
        | Sequence[bool]
        | Sequence[ArrayLikeInput]
        | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
    ) -> CGBDMEncodedLayout:
        """Encode public layout tensors into CGB-DM latent layout format."""
        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)
        layout = self.encode(bbox_t, labels_t, mask_t)
        return {
            CGBDM_LAYOUT_KEY: layout,
            CGBDM_BBOX_KEY: bbox_t,
            CGBDM_LABELS_KEY: labels_t,
            CGBDM_MASK_KEY: mask_t,
        }

    def pad(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
    ) -> tuple[
        Float[torch.Tensor, "batch elements 4"],
        Int[torch.Tensor, "batch elements"],
        Bool[torch.Tensor, "batch elements"],
    ]:
        """Pad public layout tensors to ``max_seq_length``."""
        if mask is None:
            mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
        if bbox.shape[1] > self.max_seq_length:
            raise ValueError(f"CGB-DM supports at most {self.max_seq_length} elements")

        pad_count = self.max_seq_length - bbox.shape[1]
        if pad_count:
            bbox = torch.nn.functional.pad(bbox, (0, 0, 0, pad_count))
            labels = torch.nn.functional.pad(labels, (0, pad_count))
            mask = torch.nn.functional.pad(mask, (0, pad_count))
        labels = labels.clone()
        labels[~mask] = 0
        return bbox, labels, mask

    def encode(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Encode normalized boxes and public labels into internal tensors."""
        bbox, labels, mask = self.pad(bbox, labels, mask)
        if self.dataset_name == str(DatasetName.pku_posterlayout):
            internal_labels = labels.clone().clamp_min(0) + 1
            internal_labels[~mask] = 0
        else:
            internal_labels = labels.clone().clamp_min(0)
            internal_labels[~mask] = 0
        one_hot = torch.nn.functional.one_hot(
            internal_labels.clamp(0, self.num_labels - 1),
            num_classes=self.num_labels,
        ).to(dtype=bbox.dtype, device=bbox.device)
        bbox_in = 2 * (bbox.clamp(0.0, 1.0) - 0.5)
        return torch.cat((one_hot, bbox_in), dim=-1)

    def decode(
        self,
        layout: Float[torch.Tensor, "batch elements channels"],
        *,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        scores: Float[torch.Tensor, "..."] | None = None,
        intermediates: dict[str, str | Float[torch.Tensor, "..."] | None] | None = None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | dict[int, str]
            | dict[str, str | Float[torch.Tensor, "..."] | None]
            | None,
        ]
    ):
        """Decode internal layout tensors into the public schema."""
        bbox = (layout[:, :, self.num_labels :].clamp(-1.0, 1.0) / 2 + 0.5).cpu()
        class_logits = layout[:, :, : self.num_labels]
        class_ids = class_logits.argmax(dim=-1).long().cpu()
        mask = class_ids != 0
        if self.dataset_name == str(DatasetName.pku_posterlayout):
            labels = (class_ids - 1).clamp(0, max(self.id2label)).cpu()
        else:
            labels = class_ids.clamp(0, max(self.id2label)).cpu()
        resolved_scores = (
            scores
            if scores is not None
            else class_logits.softmax(dim=-1).max(dim=-1).values
        )
        output = LayoutGenerationOutput(
            bbox=bbox,
            labels=labels,
            mask=mask,
            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 _resolve_saliency(
        self,
        batch_size: int,
        *,
        saliency: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None,
        saliency_isnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None,
        saliency_basnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None,
    ) -> list[
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes] | 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[
                    Float[torch.Tensor, "..."]
                    | Image.Image
                    | str
                    | bytes
                    | Path
                    | IO[bytes]
                    | None
                ],
                rows,
            )
        if saliency_isnet is None and saliency_basnet is None:
            return [None] * batch_size
        return cast(
            list[
                Float[torch.Tensor, "..."]
                | Image.Image
                | str
                | bytes
                | Path
                | IO[bytes]
                | None
            ],
            [
                _merge_saliency_pair(left, right, self.image_size)
                for left, right in zip(
                    _optional_batch(saliency_isnet, batch_size),
                    _optional_batch(saliency_basnet, batch_size),
                    strict=True,
                )
            ],
        )

seq_dim property

seq_dim: int

Return the internal class-plus-box channel count.

__init__

__init__(
    dataset_name: DatasetName
    | str = DatasetName.pku_posterlayout,
    id2label: Mapping[int, str]
    | Mapping[str, str]
    | None = None,
    num_labels: int = 4,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
) -> None

Initialize processor metadata.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def __init__(
    self,
    dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
    id2label: Mapping[int, str] | Mapping[str, str] | None = None,
    num_labels: int = 4,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
) -> None:
    """Initialize processor metadata."""
    super().__init__()
    dataset = normalize_dataset_name(dataset_name)
    labels = id2label_for_dataset(dataset)
    public_labels = {
        key: value for key, value in labels.items() if value != "INVALID"
    }
    self.dataset_name = str(dataset)
    self.id2label = {int(k): v for k, v in (id2label or public_labels).items()}
    self.label2id = {v: k for k, v in self.id2label.items()}
    self.num_labels = int(num_labels)
    self.max_seq_length = int(max_seq_length)
    self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
    self.chat_template = None

__call__

__call__(
    images: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[Tensor, "..."]
        | Image
        | str
        | bytes
        | Path
        | IO[bytes]
    ]
    | None = None,
    *,
    saliency: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[Tensor, "..."]
        | Image
        | str
        | bytes
        | Path
        | IO[bytes]
    ]
    | None = None,
    saliency_isnet: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[Tensor, "..."]
        | Image
        | str
        | bytes
        | Path
        | IO[bytes]
    ]
    | None = None,
    saliency_basnet: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[Tensor, "..."]
        | Image
        | str
        | bytes
        | Path
        | IO[bytes]
    ]
    | None = None,
    saliency_box: Float[Tensor, "..."] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding

Encode image and saliency inputs into model tensors.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
 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
def __call__(
    self,
    images: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
    ]
    | None = None,
    *,
    saliency: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
    ]
    | None = None,
    saliency_isnet: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
    ]
    | None = None,
    saliency_basnet: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
    ]
    | None = None,
    saliency_box: Float[torch.Tensor, "..."] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding:
    """Encode image and saliency inputs into model tensors."""
    if return_tensors != "pt":
        raise ValueError("CGBDMProcessor only supports return_tensors='pt'")

    image_rows = _ensure_batch(images)
    saliency_rows = self._resolve_saliency(
        len(image_rows),
        saliency=saliency,
        saliency_isnet=saliency_isnet,
        saliency_basnet=saliency_basnet,
    )
    pixel_values = []
    boxes = []
    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)
            if sal is None
            else _to_l_tensor(sal, self.image_size)
        )
        pixel_values.append(torch.cat((rgb, sal_tensor), dim=0))
        boxes.append(_saliency_box_from_tensor(sal_tensor))
    resolved_box = (
        torch.stack(boxes)
        if saliency_box is None
        else torch.as_tensor(saliency_box, dtype=torch.float32)
    )
    if resolved_box.ndim == 1:
        resolved_box = resolved_box.reshape(1, 1, 4)
    elif resolved_box.ndim == 2:
        resolved_box = resolved_box.unsqueeze(1)
    return BatchEncoding(
        {
            "pixel_values": torch.stack(pixel_values),
            "saliency_box": 2 * (resolved_box.clamp(0.0, 1.0) - 0.5),
        }
    )

encode_layout

encode_layout(
    *,
    bbox: Float[Tensor, "..."]
    | Float[ndarray, "..."]
    | Sequence[Sequence[Sequence[float]]]
    | Sequence[Sequence[float]]
    | Sequence[ArrayLikeInput],
    labels: Int[Tensor, "..."]
    | Int[ndarray, "..."]
    | Sequence[Sequence[int]]
    | Sequence[int]
    | Sequence[ArrayLikeInput],
    mask: Bool[Tensor, "..."]
    | Bool[ndarray, "..."]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> CGBDMEncodedLayout

Encode public layout tensors into CGB-DM latent layout format.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
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
def encode_layout(
    self,
    *,
    bbox: Float[torch.Tensor, "..."]
    | Float[np.ndarray, "..."]
    | Sequence[Sequence[Sequence[float]]]
    | Sequence[Sequence[float]]
    | Sequence[ArrayLikeInput],
    labels: Int[torch.Tensor, "..."]
    | Int[np.ndarray, "..."]
    | Sequence[Sequence[int]]
    | Sequence[int]
    | Sequence[ArrayLikeInput],
    mask: Bool[torch.Tensor, "..."]
    | Bool[np.ndarray, "..."]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> CGBDMEncodedLayout:
    """Encode public layout tensors into CGB-DM latent layout format."""
    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)
    layout = self.encode(bbox_t, labels_t, mask_t)
    return {
        CGBDM_LAYOUT_KEY: layout,
        CGBDM_BBOX_KEY: bbox_t,
        CGBDM_LABELS_KEY: labels_t,
        CGBDM_MASK_KEY: mask_t,
    }

pad

pad(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"] | None = None,
) -> tuple[
    Float[torch.Tensor, "batch elements 4"],
    Int[torch.Tensor, "batch elements"],
    Bool[torch.Tensor, "batch elements"],
]

Pad public layout tensors to max_seq_length.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def pad(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
) -> tuple[
    Float[torch.Tensor, "batch elements 4"],
    Int[torch.Tensor, "batch elements"],
    Bool[torch.Tensor, "batch elements"],
]:
    """Pad public layout tensors to ``max_seq_length``."""
    if mask is None:
        mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
    if bbox.shape[1] > self.max_seq_length:
        raise ValueError(f"CGB-DM supports at most {self.max_seq_length} elements")

    pad_count = self.max_seq_length - bbox.shape[1]
    if pad_count:
        bbox = torch.nn.functional.pad(bbox, (0, 0, 0, pad_count))
        labels = torch.nn.functional.pad(labels, (0, pad_count))
        mask = torch.nn.functional.pad(mask, (0, pad_count))
    labels = labels.clone()
    labels[~mask] = 0
    return bbox, labels, mask

encode

encode(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"] | None = None,
) -> Float[torch.Tensor, "batch elements channels"]

Encode normalized boxes and public labels into internal tensors.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
def encode(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Encode normalized boxes and public labels into internal tensors."""
    bbox, labels, mask = self.pad(bbox, labels, mask)
    if self.dataset_name == str(DatasetName.pku_posterlayout):
        internal_labels = labels.clone().clamp_min(0) + 1
        internal_labels[~mask] = 0
    else:
        internal_labels = labels.clone().clamp_min(0)
        internal_labels[~mask] = 0
    one_hot = torch.nn.functional.one_hot(
        internal_labels.clamp(0, self.num_labels - 1),
        num_classes=self.num_labels,
    ).to(dtype=bbox.dtype, device=bbox.device)
    bbox_in = 2 * (bbox.clamp(0.0, 1.0) - 0.5)
    return torch.cat((one_hot, bbox_in), dim=-1)

decode

decode(
    layout: Float[Tensor, "batch elements channels"],
    *,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    scores: Float[Tensor, "..."] | None = None,
    intermediates: dict[
        str, str | Float[Tensor, "..."] | None
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | dict[int, str]
        | dict[str, str | Float[torch.Tensor, "..."] | None]
        | None,
    ]
)

Decode internal layout tensors into the public schema.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
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
def decode(
    self,
    layout: Float[torch.Tensor, "batch elements channels"],
    *,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    scores: Float[torch.Tensor, "..."] | None = None,
    intermediates: dict[str, str | Float[torch.Tensor, "..."] | None] | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | dict[int, str]
        | dict[str, str | Float[torch.Tensor, "..."] | None]
        | None,
    ]
):
    """Decode internal layout tensors into the public schema."""
    bbox = (layout[:, :, self.num_labels :].clamp(-1.0, 1.0) / 2 + 0.5).cpu()
    class_logits = layout[:, :, : self.num_labels]
    class_ids = class_logits.argmax(dim=-1).long().cpu()
    mask = class_ids != 0
    if self.dataset_name == str(DatasetName.pku_posterlayout):
        labels = (class_ids - 1).clamp(0, max(self.id2label)).cpu()
    else:
        labels = class_ids.clamp(0, max(self.id2label)).cpu()
    resolved_scores = (
        scores
        if scores is not None
        else class_logits.softmax(dim=-1).max(dim=-1).values
    )
    output = LayoutGenerationOutput(
        bbox=bbox,
        labels=labels,
        mask=mask,
        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}")

CGBDMScheduler

Bases: SchedulerMixin, ConfigMixin

CGB-DM scheduler preserving separate training and sampling schedules.

Parameters:

Name Type Description Default
num_train_timesteps int

Number of DDPM training steps.

1000
ddim_num_steps int

Default DDIM inference step count.

100
train_beta_schedule CGBDMBetaSchedule | str

Schedule for training noising buffers.

cosine
sampling_beta_schedule CGBDMBetaSchedule | str

Schedule for DDIM sampling buffers.

linear
eta float

DDIM stochasticity.

0.0

Examples:

>>> scheduler = CGBDMScheduler(num_train_timesteps=10, ddim_num_steps=2)
>>> scheduler.ddim_timesteps.tolist()
[0, 5]
Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
 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
class CGBDMScheduler(SchedulerMixin, ConfigMixin):
    """CGB-DM scheduler preserving separate training and sampling schedules.

    Args:
        num_train_timesteps: Number of DDPM training steps.
        ddim_num_steps: Default DDIM inference step count.
        train_beta_schedule: Schedule for training noising buffers.
        sampling_beta_schedule: Schedule for DDIM sampling buffers.
        eta: DDIM stochasticity.

    Examples:
        >>> scheduler = CGBDMScheduler(num_train_timesteps=10, ddim_num_steps=2)
        >>> scheduler.ddim_timesteps.tolist()
        [0, 5]
    """

    config_name = "scheduler_config.json"
    order = 1

    @register_to_config
    def __init__(
        self,
        *,
        num_train_timesteps: int = 1000,
        ddim_num_steps: int = 100,
        train_beta_schedule: CGBDMBetaSchedule | str = CGBDMBetaSchedule.cosine,
        sampling_beta_schedule: CGBDMBetaSchedule | str = CGBDMBetaSchedule.linear,
        eta: float = 0.0,
    ) -> None:
        """Initialize noising and sampling buffers."""
        self.num_train_timesteps = int(num_train_timesteps)
        self.ddim_num_steps = int(ddim_num_steps)
        self.train_beta_schedule = str(CGBDMBetaSchedule(train_beta_schedule))
        self.sampling_beta_schedule = str(CGBDMBetaSchedule(sampling_beta_schedule))
        self.eta = float(eta)
        train_betas = make_beta_schedule(
            self.train_beta_schedule, self.num_train_timesteps
        ).float()
        train_alphas = 1.0 - train_betas
        self.train_alphas_cumprod = train_alphas.cumprod(dim=0)
        self.alphas_bar_sqrt = torch.sqrt(self.train_alphas_cumprod)
        self.one_minus_alphas_bar_sqrt = torch.sqrt(1.0 - self.train_alphas_cumprod)
        sampling_betas = make_beta_schedule(
            self.sampling_beta_schedule, self.num_train_timesteps
        ).float()
        sampling_alphas = 1.0 - sampling_betas
        self.sampling_alphas_cumprod = sampling_alphas.cumprod(dim=0)
        self.timesteps = torch.empty(0, dtype=torch.long)
        self.set_timesteps(self.ddim_num_steps)

    def set_timesteps(
        self, num_inference_steps: int | None = None, device: torch.device | None = None
    ) -> None:
        """Set DDIM timesteps and derived sampling parameters."""
        steps = int(num_inference_steps or self.ddim_num_steps)
        ddim = make_ddim_timesteps(
            num_ddim_timesteps=steps,
            num_ddpm_timesteps=self.num_train_timesteps,
        )
        self.ddim_timesteps = torch.as_tensor(ddim, dtype=torch.long, device=device)
        self.timesteps = torch.flip(self.ddim_timesteps, dims=(0,))
        alphas = self.sampling_alphas_cumprod.to(device)
        self.ddim_alphas = alphas[self.ddim_timesteps]
        self.ddim_alphas_prev = torch.as_tensor(
            [alphas[0].item()] + alphas[self.ddim_timesteps[:-1]].tolist(),
            dtype=torch.float32,
            device=device,
        )
        self.ddim_sigmas = self.eta * torch.sqrt(
            (1 - self.ddim_alphas_prev)
            / (1 - self.ddim_alphas)
            * (1 - self.ddim_alphas / self.ddim_alphas_prev)
        )
        self.ddim_sqrt_one_minus_alphas = torch.sqrt(1.0 - self.ddim_alphas)

    def sample_timesteps(
        self,
        batch_size: int,
        *,
        device: torch.device,
        generator: torch.Generator | None = None,
        t_max: int | None = None,
    ) -> Int[torch.Tensor, "batch"]:
        """Sample training timesteps."""
        high = int(t_max or self.num_train_timesteps - 1)
        return torch.randint(0, high, (batch_size,), device=device, generator=generator)

    def add_noise(
        self,
        original_samples: Float[torch.Tensor, "batch elements channels"],
        noise: Float[torch.Tensor, "batch elements channels"],
        timesteps: Int[torch.Tensor, "batch"],
        *,
        fix_mask: Bool[torch.Tensor, "batch elements channels"] | None = None,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Add training noise, preserving fixed channels when requested."""
        alphas = self.alphas_bar_sqrt.to(original_samples.device)
        one_minus = self.one_minus_alphas_bar_sqrt.to(original_samples.device)
        sqrt_alpha = torch.gather(alphas, 0, timesteps).reshape(-1, 1, 1)
        sqrt_one_minus = torch.gather(one_minus, 0, timesteps).reshape(-1, 1, 1)
        noised = sqrt_alpha * original_samples + sqrt_one_minus * noise
        if fix_mask is None:
            return noised
        return torch.where(fix_mask, original_samples, noised)

    def initial_sample(
        self,
        batch_size: int,
        seq_len: int,
        seq_dim: int,
        *,
        device: torch.device,
        generator: torch.Generator | None = None,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Create the initial DDIM sample."""
        return torch.randn(
            batch_size, seq_len, seq_dim, device=device, generator=generator
        )

    def condition_mask(
        self,
        layout: Float[torch.Tensor, "batch elements channels"],
        condition_type: ConditionType,
        *,
        completion_ratio: float = 0.2,
        generator: torch.Generator | None = None,
    ) -> Bool[torch.Tensor, "batch elements channels"]:
        """Build a channel-level mask for fixed conditioning values."""
        mask = torch.zeros_like(layout, dtype=torch.bool)
        num_labels = layout.shape[-1] - 4
        if condition_type is ConditionType.content_image:
            return mask

        if condition_type is ConditionType.label:
            mask[:, :, :num_labels] = True
            return mask

        if condition_type is ConditionType.label_size:
            mask[:, :, :num_labels] = True
            mask[:, :, num_labels + 2 : num_labels + 4] = True
            return mask

        if condition_type is ConditionType.completion:
            label_ids = layout[:, :, :num_labels].argmax(dim=-1)
            valid = label_ids != 0
            rand = torch.rand(valid.shape, device=layout.device, generator=generator)
            elem_mask = (rand <= completion_ratio) & valid
            return elem_mask.unsqueeze(-1).expand_as(layout)

        if condition_type is ConditionType.refinement:
            return torch.ones_like(mask)
        raise ValueError(f"Unsupported CGB-DM condition_type: {condition_type}")

    def step(
        self,
        model_output: Float[torch.Tensor, "batch elements channels"],
        timestep: Int[torch.Tensor, "batch"],
        sample: Float[torch.Tensor, "batch elements channels"],
        index: int,
        generator: torch.Generator | None = None,
    ) -> CGBDMSchedulerOutput:
        """Take one DDIM reverse step."""
        del timestep
        alpha_t = self.ddim_alphas[index].to(sample.device)
        alpha_prev = self.ddim_alphas_prev[index].to(sample.device)
        sigma_t = self.ddim_sigmas[index].to(sample.device)
        sqrt_one_minus = self.ddim_sqrt_one_minus_alphas[index].to(sample.device)
        pred_original = (sample - sqrt_one_minus * model_output) / alpha_t.sqrt()
        direction = (1.0 - alpha_prev - sigma_t**2).sqrt() * model_output
        noise = sigma_t * torch.randn(
            sample.shape,
            dtype=sample.dtype,
            device=sample.device,
            generator=generator,
        )
        prev_sample = alpha_prev.sqrt() * pred_original + direction + noise
        return CGBDMSchedulerOutput(
            prev_sample=prev_sample,
            pred_original_sample=pred_original,
        )

__init__

__init__(
    *,
    num_train_timesteps: int = 1000,
    ddim_num_steps: int = 100,
    train_beta_schedule: CGBDMBetaSchedule
    | str = CGBDMBetaSchedule.cosine,
    sampling_beta_schedule: CGBDMBetaSchedule
    | str = CGBDMBetaSchedule.linear,
    eta: float = 0.0,
) -> None

Initialize noising and sampling buffers.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
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
@register_to_config
def __init__(
    self,
    *,
    num_train_timesteps: int = 1000,
    ddim_num_steps: int = 100,
    train_beta_schedule: CGBDMBetaSchedule | str = CGBDMBetaSchedule.cosine,
    sampling_beta_schedule: CGBDMBetaSchedule | str = CGBDMBetaSchedule.linear,
    eta: float = 0.0,
) -> None:
    """Initialize noising and sampling buffers."""
    self.num_train_timesteps = int(num_train_timesteps)
    self.ddim_num_steps = int(ddim_num_steps)
    self.train_beta_schedule = str(CGBDMBetaSchedule(train_beta_schedule))
    self.sampling_beta_schedule = str(CGBDMBetaSchedule(sampling_beta_schedule))
    self.eta = float(eta)
    train_betas = make_beta_schedule(
        self.train_beta_schedule, self.num_train_timesteps
    ).float()
    train_alphas = 1.0 - train_betas
    self.train_alphas_cumprod = train_alphas.cumprod(dim=0)
    self.alphas_bar_sqrt = torch.sqrt(self.train_alphas_cumprod)
    self.one_minus_alphas_bar_sqrt = torch.sqrt(1.0 - self.train_alphas_cumprod)
    sampling_betas = make_beta_schedule(
        self.sampling_beta_schedule, self.num_train_timesteps
    ).float()
    sampling_alphas = 1.0 - sampling_betas
    self.sampling_alphas_cumprod = sampling_alphas.cumprod(dim=0)
    self.timesteps = torch.empty(0, dtype=torch.long)
    self.set_timesteps(self.ddim_num_steps)

set_timesteps

set_timesteps(
    num_inference_steps: int | None = None,
    device: device | None = None,
) -> None

Set DDIM timesteps and derived sampling parameters.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def set_timesteps(
    self, num_inference_steps: int | None = None, device: torch.device | None = None
) -> None:
    """Set DDIM timesteps and derived sampling parameters."""
    steps = int(num_inference_steps or self.ddim_num_steps)
    ddim = make_ddim_timesteps(
        num_ddim_timesteps=steps,
        num_ddpm_timesteps=self.num_train_timesteps,
    )
    self.ddim_timesteps = torch.as_tensor(ddim, dtype=torch.long, device=device)
    self.timesteps = torch.flip(self.ddim_timesteps, dims=(0,))
    alphas = self.sampling_alphas_cumprod.to(device)
    self.ddim_alphas = alphas[self.ddim_timesteps]
    self.ddim_alphas_prev = torch.as_tensor(
        [alphas[0].item()] + alphas[self.ddim_timesteps[:-1]].tolist(),
        dtype=torch.float32,
        device=device,
    )
    self.ddim_sigmas = self.eta * torch.sqrt(
        (1 - self.ddim_alphas_prev)
        / (1 - self.ddim_alphas)
        * (1 - self.ddim_alphas / self.ddim_alphas_prev)
    )
    self.ddim_sqrt_one_minus_alphas = torch.sqrt(1.0 - self.ddim_alphas)

sample_timesteps

sample_timesteps(
    batch_size: int,
    *,
    device: device,
    generator: Generator | None = None,
    t_max: int | None = None,
) -> Int[torch.Tensor, "batch"]

Sample training timesteps.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
168
169
170
171
172
173
174
175
176
177
178
def sample_timesteps(
    self,
    batch_size: int,
    *,
    device: torch.device,
    generator: torch.Generator | None = None,
    t_max: int | None = None,
) -> Int[torch.Tensor, "batch"]:
    """Sample training timesteps."""
    high = int(t_max or self.num_train_timesteps - 1)
    return torch.randint(0, high, (batch_size,), device=device, generator=generator)

add_noise

add_noise(
    original_samples: Float[
        Tensor, "batch elements channels"
    ],
    noise: Float[Tensor, "batch elements channels"],
    timesteps: Int[Tensor, "batch"],
    *,
    fix_mask: Bool[Tensor, "batch elements channels"]
    | None = None,
) -> Float[torch.Tensor, "batch elements channels"]

Add training noise, preserving fixed channels when requested.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
def add_noise(
    self,
    original_samples: Float[torch.Tensor, "batch elements channels"],
    noise: Float[torch.Tensor, "batch elements channels"],
    timesteps: Int[torch.Tensor, "batch"],
    *,
    fix_mask: Bool[torch.Tensor, "batch elements channels"] | None = None,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Add training noise, preserving fixed channels when requested."""
    alphas = self.alphas_bar_sqrt.to(original_samples.device)
    one_minus = self.one_minus_alphas_bar_sqrt.to(original_samples.device)
    sqrt_alpha = torch.gather(alphas, 0, timesteps).reshape(-1, 1, 1)
    sqrt_one_minus = torch.gather(one_minus, 0, timesteps).reshape(-1, 1, 1)
    noised = sqrt_alpha * original_samples + sqrt_one_minus * noise
    if fix_mask is None:
        return noised
    return torch.where(fix_mask, original_samples, noised)

initial_sample

initial_sample(
    batch_size: int,
    seq_len: int,
    seq_dim: int,
    *,
    device: device,
    generator: Generator | None = None,
) -> Float[torch.Tensor, "batch elements channels"]

Create the initial DDIM sample.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
198
199
200
201
202
203
204
205
206
207
208
209
210
def initial_sample(
    self,
    batch_size: int,
    seq_len: int,
    seq_dim: int,
    *,
    device: torch.device,
    generator: torch.Generator | None = None,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Create the initial DDIM sample."""
    return torch.randn(
        batch_size, seq_len, seq_dim, device=device, generator=generator
    )

condition_mask

condition_mask(
    layout: Float[Tensor, "batch elements channels"],
    condition_type: ConditionType,
    *,
    completion_ratio: float = 0.2,
    generator: Generator | None = None,
) -> Bool[torch.Tensor, "batch elements channels"]

Build a channel-level mask for fixed conditioning values.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
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
def condition_mask(
    self,
    layout: Float[torch.Tensor, "batch elements channels"],
    condition_type: ConditionType,
    *,
    completion_ratio: float = 0.2,
    generator: torch.Generator | None = None,
) -> Bool[torch.Tensor, "batch elements channels"]:
    """Build a channel-level mask for fixed conditioning values."""
    mask = torch.zeros_like(layout, dtype=torch.bool)
    num_labels = layout.shape[-1] - 4
    if condition_type is ConditionType.content_image:
        return mask

    if condition_type is ConditionType.label:
        mask[:, :, :num_labels] = True
        return mask

    if condition_type is ConditionType.label_size:
        mask[:, :, :num_labels] = True
        mask[:, :, num_labels + 2 : num_labels + 4] = True
        return mask

    if condition_type is ConditionType.completion:
        label_ids = layout[:, :, :num_labels].argmax(dim=-1)
        valid = label_ids != 0
        rand = torch.rand(valid.shape, device=layout.device, generator=generator)
        elem_mask = (rand <= completion_ratio) & valid
        return elem_mask.unsqueeze(-1).expand_as(layout)

    if condition_type is ConditionType.refinement:
        return torch.ones_like(mask)
    raise ValueError(f"Unsupported CGB-DM condition_type: {condition_type}")

step

step(
    model_output: Float[Tensor, "batch elements channels"],
    timestep: Int[Tensor, "batch"],
    sample: Float[Tensor, "batch elements channels"],
    index: int,
    generator: Generator | None = None,
) -> CGBDMSchedulerOutput

Take one DDIM reverse step.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
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
def step(
    self,
    model_output: Float[torch.Tensor, "batch elements channels"],
    timestep: Int[torch.Tensor, "batch"],
    sample: Float[torch.Tensor, "batch elements channels"],
    index: int,
    generator: torch.Generator | None = None,
) -> CGBDMSchedulerOutput:
    """Take one DDIM reverse step."""
    del timestep
    alpha_t = self.ddim_alphas[index].to(sample.device)
    alpha_prev = self.ddim_alphas_prev[index].to(sample.device)
    sigma_t = self.ddim_sigmas[index].to(sample.device)
    sqrt_one_minus = self.ddim_sqrt_one_minus_alphas[index].to(sample.device)
    pred_original = (sample - sqrt_one_minus * model_output) / alpha_t.sqrt()
    direction = (1.0 - alpha_prev - sigma_t**2).sqrt() * model_output
    noise = sigma_t * torch.randn(
        sample.shape,
        dtype=sample.dtype,
        device=sample.device,
        generator=generator,
    )
    prev_sample = alpha_prev.sqrt() * pred_original + direction + noise
    return CGBDMSchedulerOutput(
        prev_sample=prev_sample,
        pred_original_sample=pred_original,
    )

CGBDMSchedulerOutput dataclass

Bases: BaseOutput

Output returned by one CGB-DM DDIM step.

Attributes:

Name Type Description
prev_sample Float[Tensor, 'batch elements channels']

Layout sample for the next denoising step.

pred_original_sample Float[Tensor, 'batch elements channels']

Estimated clean layout sample.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
29
30
31
32
33
34
35
36
37
38
39
@dataclass
class CGBDMSchedulerOutput(BaseOutput):
    """Output returned by one CGB-DM DDIM step.

    Attributes:
        prev_sample: Layout sample for the next denoising step.
        pred_original_sample: Estimated clean layout sample.
    """

    prev_sample: Float[torch.Tensor, "batch elements channels"]
    pred_original_sample: Float[torch.Tensor, "batch elements channels"]

cgb_dm_config_for_dataset

cgb_dm_config_for_dataset(
    dataset_name: DatasetName | str,
) -> CGBDMConfig

Build a CGB-DM config for a supported dataset.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset key or enum.

required

Returns:

Type Description
CGBDMConfig

Dataset-specific CGB-DM config.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> cgb_dm_config_for_dataset("cgl").num_labels
5
Source code in models/cgb-dm/src/cgb_dm/configuration_cgb_dm.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
def cgb_dm_config_for_dataset(dataset_name: DatasetName | str) -> CGBDMConfig:
    """Build a CGB-DM config for a supported dataset.

    Args:
        dataset_name: Dataset key or enum.

    Returns:
        Dataset-specific CGB-DM config.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> cgb_dm_config_for_dataset("cgl").num_labels
        5
    """
    dataset = normalize_dataset_name(dataset_name)
    spec = DATASET_SPECS[dataset]
    return CGBDMConfig(
        dataset_name=dataset,
        num_labels=spec.num_labels,
        id2label=spec.id2label,
    )

convert_state_dict

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

Normalize CGB-DM checkpoint keys for CGBDMTransformerModel.

Parameters:

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

Original or Lightning checkpoint state dictionary.

required

Returns:

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

Converted state dictionary with common wrapper prefixes stripped.

Examples:

>>> convert_state_dict({"model.module.img_encoder.patch.weight": torch.zeros(1)})["img_encoder.patch.weight"].shape
torch.Size([1])
Source code in models/cgb-dm/src/cgb_dm/conversion.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def convert_state_dict(
    state_dict: Mapping[str, Float[torch.Tensor, "..."]],
) -> dict[str, Float[torch.Tensor, "..."]]:
    """Normalize CGB-DM checkpoint keys for ``CGBDMTransformerModel``.

    Args:
        state_dict: Original or Lightning checkpoint state dictionary.

    Returns:
        Converted state dictionary with common wrapper prefixes stripped.

    Examples:
        >>> convert_state_dict({"model.module.img_encoder.patch.weight": torch.zeros(1)})["img_encoder.patch.weight"].shape
        torch.Size([1])
    """
    converted: dict[str, Float[torch.Tensor, "..."]] = {}
    for key, value in state_dict.items():
        name = key.removeprefix("state_dict.")
        name = name.removeprefix("model.")
        name = name.removeprefix("module.")
        name = name.removeprefix("denoiser.")
        converted[name] = value
    return converted

normalize_condition_type

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

Normalize CGB-DM condition aliases.

Parameters:

Name Type Description Default
condition_type ConditionType | str | None

Canonical condition enum, alias, or None.

required

Returns:

Type Description
ConditionType

Canonical condition enum.

Raises:

Type Description
ValueError

If the condition is unsupported.

Examples:

>>> str(normalize_condition_type("uncond"))
'content_image'
Source code in models/cgb-dm/src/cgb_dm/pipeline_cgb_dm.py
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
def normalize_condition_type(
    condition_type: ConditionType | str | None,
) -> ConditionType:
    """Normalize CGB-DM condition aliases.

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

    Returns:
        Canonical condition enum.

    Raises:
        ValueError: If the condition is unsupported.

    Examples:
        >>> str(normalize_condition_type("uncond"))
        '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 = (
            ConditionType.content_image
            if key == "uncond"
            else normalize_shared_condition_type(condition_type)
        )
    if canonical is ConditionType.unconditional:
        raise ValueError(
            "CGB-DM requires image/content; use condition_type='content_image'"
        )

    if canonical not in _SUPPORTED_CONDITION_TYPES:
        raise ValueError(f"Unsupported CGB-DM condition_type: {condition_type}")

    return canonical

configuration_cgb_dm

Configuration metadata for CGB-DM checkpoints.

CGBDMDatasetSpec dataclass

Dataset defaults used by CGB-DM training and inference configs.

Attributes:

Name Type Description
dataset_name DatasetName

Canonical poster/content dataset enum.

num_labels int

Number of internal class channels, including invalid/pad.

train_batch_size int

Reference training batch size.

id2label dict[int, str]

Public label map persisted in checkpoints.

Source code in models/cgb-dm/src/cgb_dm/configuration_cgb_dm.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@dataclass(frozen=True)
class CGBDMDatasetSpec:
    """Dataset defaults used by CGB-DM training and inference configs.

    Attributes:
        dataset_name: Canonical poster/content dataset enum.
        num_labels: Number of internal class channels, including invalid/pad.
        train_batch_size: Reference training batch size.
        id2label: Public label map persisted in checkpoints.
    """

    dataset_name: DatasetName
    num_labels: int
    train_batch_size: int
    id2label: dict[int, str]

CGBDMConfig

Bases: ConfigMixin

Store CGB-DM architecture, schedule, and dataset metadata.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Poster/content dataset key.

pku_posterlayout
num_labels int | None

Internal class-channel count, including invalid/pad.

None
max_seq_length int

Maximum number of layout elements.

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

Model image size as (height, width).

(384, 256)
canvas_size tuple[int, int] | list[int]

Dataset canvas size as (width, height).

(513, 750)
num_train_timesteps int

DDPM training timesteps.

1000
ddim_num_steps int

Default DDIM inference steps.

100
dim_model int

Transformer hidden dimension.

512
n_head int

Attention head count.

8
num_layers int

Number of layout decoder layers.

4
feature_dim int

Feed-forward hidden dimension.

1024
id2label Id2LabelMapping | None

Public id-to-label mapping, excluding invalid/pad.

None

Examples:

>>> CGBDMConfig().dataset_name
'pku_posterlayout'
Source code in models/cgb-dm/src/cgb_dm/configuration_cgb_dm.py
 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
class CGBDMConfig(ConfigMixin):
    """Store CGB-DM architecture, schedule, and dataset metadata.

    Args:
        dataset_name: Poster/content dataset key.
        num_labels: Internal class-channel count, including invalid/pad.
        max_seq_length: Maximum number of layout elements.
        image_size: Model image size as ``(height, width)``.
        canvas_size: Dataset canvas size as ``(width, height)``.
        num_train_timesteps: DDPM training timesteps.
        ddim_num_steps: Default DDIM inference steps.
        dim_model: Transformer hidden dimension.
        n_head: Attention head count.
        num_layers: Number of layout decoder layers.
        feature_dim: Feed-forward hidden dimension.
        id2label: Public id-to-label mapping, excluding invalid/pad.

    Examples:
        >>> CGBDMConfig().dataset_name
        'pku_posterlayout'
    """

    config_name = "cgb_dm_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
        num_labels: int | None = None,
        max_seq_length: int = 16,
        image_size: tuple[int, int] | list[int] = (384, 256),
        canvas_size: tuple[int, int] | list[int] = (513, 750),
        num_train_timesteps: int = 1000,
        ddim_num_steps: int = 100,
        dim_model: int = 512,
        n_head: int = 8,
        num_layers: int = 4,
        feature_dim: int = 1024,
        id2label: Id2LabelMapping | None = None,
        condition_types: list[str] | tuple[str, ...] | None = None,
        train_beta_schedule: str = "cosine",
        sampling_beta_schedule: str = "linear",
        model_subfolder: str = "model",
        scheduler_subfolder: str = "scheduler",
        processor_subfolder: str = "processor",
    ) -> None:
        """Initialize CGB-DM configuration."""
        dataset = normalize_dataset_name(dataset_name)
        spec = DATASET_SPECS.get(dataset)
        if spec is None:
            raise ValueError(f"Unsupported CGB-DM dataset_name: {dataset_name}")

        self.dataset_name = str(dataset)
        self.num_labels = int(num_labels or spec.num_labels)
        self.max_seq_length = int(max_seq_length)
        self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
        self.canvas_size: tuple[int, int] = (int(canvas_size[0]), int(canvas_size[1]))
        self.num_train_timesteps = int(num_train_timesteps)
        self.ddim_num_steps = int(ddim_num_steps)
        self.dim_model = int(dim_model)
        self.n_head = int(n_head)
        self.num_layers = int(num_layers)
        self.feature_dim = int(feature_dim)
        self.id2label = {int(k): v for k, v in (id2label or spec.id2label).items()}
        self.condition_types = list(
            condition_types
            or ["content_image", "label", "label_size", "completion", "refinement"]
        )
        self.train_beta_schedule = train_beta_schedule
        self.sampling_beta_schedule = sampling_beta_schedule
        self.model_subfolder = model_subfolder
        self.scheduler_subfolder = scheduler_subfolder
        self.processor_subfolder = processor_subfolder

    @property
    def seq_dim(self) -> int:
        """Return the internal layout channel count."""
        return self.num_labels + 4

    @property
    def public_num_labels(self) -> int:
        """Return the public semantic label count."""
        return len(self.id2label)

seq_dim property

seq_dim: int

Return the internal layout channel count.

public_num_labels property

public_num_labels: int

Return the public semantic label count.

__init__

__init__(
    *,
    dataset_name: DatasetName
    | str = DatasetName.pku_posterlayout,
    num_labels: int | None = None,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
    canvas_size: tuple[int, int] | list[int] = (513, 750),
    num_train_timesteps: int = 1000,
    ddim_num_steps: int = 100,
    dim_model: int = 512,
    n_head: int = 8,
    num_layers: int = 4,
    feature_dim: int = 1024,
    id2label: Id2LabelMapping | None = None,
    condition_types: list[str]
    | tuple[str, ...]
    | None = None,
    train_beta_schedule: str = "cosine",
    sampling_beta_schedule: str = "linear",
    model_subfolder: str = "model",
    scheduler_subfolder: str = "scheduler",
    processor_subfolder: str = "processor",
) -> None

Initialize CGB-DM configuration.

Source code in models/cgb-dm/src/cgb_dm/configuration_cgb_dm.py
 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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
    num_labels: int | None = None,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
    canvas_size: tuple[int, int] | list[int] = (513, 750),
    num_train_timesteps: int = 1000,
    ddim_num_steps: int = 100,
    dim_model: int = 512,
    n_head: int = 8,
    num_layers: int = 4,
    feature_dim: int = 1024,
    id2label: Id2LabelMapping | None = None,
    condition_types: list[str] | tuple[str, ...] | None = None,
    train_beta_schedule: str = "cosine",
    sampling_beta_schedule: str = "linear",
    model_subfolder: str = "model",
    scheduler_subfolder: str = "scheduler",
    processor_subfolder: str = "processor",
) -> None:
    """Initialize CGB-DM configuration."""
    dataset = normalize_dataset_name(dataset_name)
    spec = DATASET_SPECS.get(dataset)
    if spec is None:
        raise ValueError(f"Unsupported CGB-DM dataset_name: {dataset_name}")

    self.dataset_name = str(dataset)
    self.num_labels = int(num_labels or spec.num_labels)
    self.max_seq_length = int(max_seq_length)
    self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
    self.canvas_size: tuple[int, int] = (int(canvas_size[0]), int(canvas_size[1]))
    self.num_train_timesteps = int(num_train_timesteps)
    self.ddim_num_steps = int(ddim_num_steps)
    self.dim_model = int(dim_model)
    self.n_head = int(n_head)
    self.num_layers = int(num_layers)
    self.feature_dim = int(feature_dim)
    self.id2label = {int(k): v for k, v in (id2label or spec.id2label).items()}
    self.condition_types = list(
        condition_types
        or ["content_image", "label", "label_size", "completion", "refinement"]
    )
    self.train_beta_schedule = train_beta_schedule
    self.sampling_beta_schedule = sampling_beta_schedule
    self.model_subfolder = model_subfolder
    self.scheduler_subfolder = scheduler_subfolder
    self.processor_subfolder = processor_subfolder

cgb_dm_config_for_dataset

cgb_dm_config_for_dataset(
    dataset_name: DatasetName | str,
) -> CGBDMConfig

Build a CGB-DM config for a supported dataset.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset key or enum.

required

Returns:

Type Description
CGBDMConfig

Dataset-specific CGB-DM config.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> cgb_dm_config_for_dataset("cgl").num_labels
5
Source code in models/cgb-dm/src/cgb_dm/configuration_cgb_dm.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
def cgb_dm_config_for_dataset(dataset_name: DatasetName | str) -> CGBDMConfig:
    """Build a CGB-DM config for a supported dataset.

    Args:
        dataset_name: Dataset key or enum.

    Returns:
        Dataset-specific CGB-DM config.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> cgb_dm_config_for_dataset("cgl").num_labels
        5
    """
    dataset = normalize_dataset_name(dataset_name)
    spec = DATASET_SPECS[dataset]
    return CGBDMConfig(
        dataset_name=dataset,
        num_labels=spec.num_labels,
        id2label=spec.id2label,
    )

conversion

Conversion helpers for CGB-DM checkpoints.

convert_state_dict

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

Normalize CGB-DM checkpoint keys for CGBDMTransformerModel.

Parameters:

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

Original or Lightning checkpoint state dictionary.

required

Returns:

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

Converted state dictionary with common wrapper prefixes stripped.

Examples:

>>> convert_state_dict({"model.module.img_encoder.patch.weight": torch.zeros(1)})["img_encoder.patch.weight"].shape
torch.Size([1])
Source code in models/cgb-dm/src/cgb_dm/conversion.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def convert_state_dict(
    state_dict: Mapping[str, Float[torch.Tensor, "..."]],
) -> dict[str, Float[torch.Tensor, "..."]]:
    """Normalize CGB-DM checkpoint keys for ``CGBDMTransformerModel``.

    Args:
        state_dict: Original or Lightning checkpoint state dictionary.

    Returns:
        Converted state dictionary with common wrapper prefixes stripped.

    Examples:
        >>> convert_state_dict({"model.module.img_encoder.patch.weight": torch.zeros(1)})["img_encoder.patch.weight"].shape
        torch.Size([1])
    """
    converted: dict[str, Float[torch.Tensor, "..."]] = {}
    for key, value in state_dict.items():
        name = key.removeprefix("state_dict.")
        name = name.removeprefix("model.")
        name = name.removeprefix("module.")
        name = name.removeprefix("denoiser.")
        converted[name] = value
    return converted

load_state_dict

load_state_dict(
    path: str | Path,
) -> dict[str, Float[torch.Tensor, "..."]]

Load a state-dict-like checkpoint from disk.

Source code in models/cgb-dm/src/cgb_dm/conversion.py
44
45
46
47
48
49
50
51
def load_state_dict(path: str | Path) -> dict[str, Float[torch.Tensor, "..."]]:
    """Load a state-dict-like checkpoint from disk."""
    checkpoint = torch.load(path, map_location="cpu")
    if isinstance(checkpoint, Mapping):
        raw = checkpoint.get("state_dict", checkpoint)
        if isinstance(raw, Mapping):
            return {str(k): cast(Float[torch.Tensor, "..."], v) for k, v in raw.items()}
    raise TypeError("Expected a state_dict-like checkpoint")

build_model_from_config

build_model_from_config(
    config: CGBDMConfig,
) -> CGBDMTransformerModel

Build the CGB-DM denoiser shape described by config.

Source code in models/cgb-dm/src/cgb_dm/conversion.py
54
55
56
57
58
59
60
61
62
63
64
65
def build_model_from_config(config: CGBDMConfig) -> CGBDMTransformerModel:
    """Build the CGB-DM denoiser shape described by ``config``."""
    return CGBDMTransformerModel(
        num_labels=config.num_labels,
        max_seq_length=config.max_seq_length,
        image_size=config.image_size,
        dim_model=config.dim_model,
        n_head=config.n_head,
        feature_dim=config.feature_dim,
        num_layers=config.num_layers,
        num_train_timesteps=config.num_train_timesteps,
    )

build_pipeline_from_checkpoint

build_pipeline_from_checkpoint(
    checkpoint_path: str | Path, *, config: CGBDMConfig
) -> CGBDMPipeline

Build a CGB-DM pipeline from a package-local training checkpoint.

Parameters:

Name Type Description Default
checkpoint_path str | Path

Path to a PyTorch checkpoint.

required
config CGBDMConfig

CGB-DM config that matches the training checkpoint.

required

Returns:

Type Description
CGBDMPipeline

Pipeline with converted model weights loaded.

Raises:

Type Description
ValueError

If the checkpoint keys do not match the model.

Source code in models/cgb-dm/src/cgb_dm/conversion.py
 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
def build_pipeline_from_checkpoint(
    checkpoint_path: str | Path,
    *,
    config: CGBDMConfig,
) -> CGBDMPipeline:
    """Build a CGB-DM pipeline from a package-local training checkpoint.

    Args:
        checkpoint_path: Path to a PyTorch checkpoint.
        config: CGB-DM config that matches the training checkpoint.

    Returns:
        Pipeline with converted model weights loaded.

    Raises:
        ValueError: If the checkpoint keys do not match the model.
    """
    model = build_model_from_config(config)
    converted = convert_state_dict(load_state_dict(checkpoint_path))
    missing, unexpected = model.load_state_dict(converted, strict=False)
    if missing or unexpected:
        raise ValueError(
            f"State dict mismatch: missing={missing}, unexpected={unexpected}"
        )

    return CGBDMPipeline(
        model=model,
        scheduler=CGBDMScheduler(
            num_train_timesteps=config.num_train_timesteps,
            ddim_num_steps=config.ddim_num_steps,
            train_beta_schedule=config.train_beta_schedule,
            sampling_beta_schedule=config.sampling_beta_schedule,
        ),
        processor=CGBDMProcessor(
            dataset_name=config.dataset_name,
            id2label=config.id2label,
            num_labels=config.num_labels,
            max_seq_length=config.max_seq_length,
            image_size=config.image_size,
        ),
    )

data

Dataset utilities for CGB-DM original zip extracts.

CGBDMDataPaths dataclass

Paths for one CGB-DM split in an extracted dataset tree.

Source code in models/cgb-dm/src/cgb_dm/data.py
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
@dataclass(frozen=True)
class CGBDMDataPaths:
    """Paths for one CGB-DM split in an extracted dataset tree."""

    root: Path
    split: str = "train"

    @property
    def inpaint_dir(self) -> Path:
        """Return the inpaint image directory."""
        return self.root / self.split / "inpaint"

    @property
    def saliency_dir(self) -> Path:
        """Return the first saliency directory."""
        return self.root / self.split / "saliency"

    @property
    def saliency_sub_dir(self) -> Path:
        """Return the second saliency directory."""
        return self.root / self.split / "saliency_sub"

    @property
    def annotation_csv(self) -> Path:
        """Return the element annotation CSV path."""
        return self.root / "csv" / f"{self.split}.csv"

    @property
    def saliency_csv(self) -> Path:
        """Return the saliency-box CSV path."""
        return self.root / "csv" / f"{self.split}_sal.csv"

inpaint_dir property

inpaint_dir: Path

Return the inpaint image directory.

saliency_dir property

saliency_dir: Path

Return the first saliency directory.

saliency_sub_dir property

saliency_sub_dir: Path

Return the second saliency directory.

annotation_csv property

annotation_csv: Path

Return the element annotation CSV path.

saliency_csv property

saliency_csv: Path

Return the saliency-box CSV path.

CGBDMOriginalDataset

Bases: Dataset[dict[str, Float[Tensor, '...']]]

Read an extracted CGB-DM dataset split without downloading assets.

Parameters:

Name Type Description Default
root str | Path

Extracted dataset root.

required
split Literal['train', 'val', 'test']

Dataset split name.

'train'
processor CGBDMProcessor | None

Processor used for image/layout normalization.

None

Examples:

>>> CGBDMDataPaths(Path("/tmp/data")).annotation_csv.name
'train.csv'
Source code in models/cgb-dm/src/cgb_dm/data.py
 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
class CGBDMOriginalDataset(Dataset[dict[str, Float[torch.Tensor, "..."]]]):
    """Read an extracted CGB-DM dataset split without downloading assets.

    Args:
        root: Extracted dataset root.
        split: Dataset split name.
        processor: Processor used for image/layout normalization.

    Examples:
        >>> CGBDMDataPaths(Path("/tmp/data")).annotation_csv.name
        'train.csv'
    """

    def __init__(
        self,
        root: str | Path,
        *,
        split: Literal["train", "val", "test"] = "train",
        processor: CGBDMProcessor | None = None,
        name_manifest: str | Path | list[str] | tuple[str, ...] | None = None,
        encoding: Literal["public", "reference"] = "public",
    ) -> None:
        """Initialize file lists and CSV indexes."""
        self.paths = CGBDMDataPaths(Path(root), split)
        self.processor = processor or CGBDMProcessor()
        self.names = _load_names(self.paths.inpaint_dir, name_manifest)
        self.encoding = encoding
        self.annotations = _read_grouped_boxes(self.paths.annotation_csv)
        self.saliency_boxes = _read_grouped_boxes(self.paths.saliency_csv)

    def __len__(self) -> int:
        """Return number of image rows."""
        return len(self.names)

    def __getitem__(self, index: int) -> dict[str, Float[torch.Tensor, "..."]]:
        """Return one normalized CGB-DM training row."""
        name = self.names[index]
        image_path = self.paths.inpaint_dir / name
        image = Image.open(image_path).convert("RGB")
        width, height = image.size
        saliency = Image.open(self.paths.saliency_dir / name).convert("L")
        saliency_sub = Image.open(self.paths.saliency_sub_dir / name).convert("L")
        if self.encoding == "reference":
            return _encode_reference_row(
                image=image,
                saliency=saliency,
                saliency_sub=saliency_sub,
                annotations=self.annotations[name],
                saliency_box=self.saliency_boxes[name][0],
                width=width,
                height=height,
                max_seq_length=self.processor.max_seq_length,
                num_labels=self.processor.num_labels,
                image_size=self.processor.image_size,
            )
        if self.encoding != "public":
            raise ValueError(f"Unsupported CGB-DM dataset encoding: {self.encoding}")

        content = self.processor(
            image,
            saliency_isnet=saliency,
            saliency_basnet=saliency_sub,
            saliency_box=_normalize_ltrb(self.saliency_boxes[name][0], width, height),
        )
        boxes, labels = zip(*self.annotations[name], strict=False)
        public_labels = (
            [label - 1 for label in labels]
            if self.processor.dataset_name == "pku_posterlayout"
            else list(labels)
        )
        layout = self.processor.encode_layout(
            bbox=[[_normalize_ltrb(box, width, height).tolist() for box in boxes]],
            labels=[public_labels],
        )["layout"][0]
        return {
            "pixel_values": content["pixel_values"][0],
            "layout": layout,
            "saliency_box": content["saliency_box"][0],
        }

__init__

__init__(
    root: str | Path,
    *,
    split: Literal["train", "val", "test"] = "train",
    processor: CGBDMProcessor | None = None,
    name_manifest: str
    | Path
    | list[str]
    | tuple[str, ...]
    | None = None,
    encoding: Literal["public", "reference"] = "public",
) -> None

Initialize file lists and CSV indexes.

Source code in models/cgb-dm/src/cgb_dm/data.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def __init__(
    self,
    root: str | Path,
    *,
    split: Literal["train", "val", "test"] = "train",
    processor: CGBDMProcessor | None = None,
    name_manifest: str | Path | list[str] | tuple[str, ...] | None = None,
    encoding: Literal["public", "reference"] = "public",
) -> None:
    """Initialize file lists and CSV indexes."""
    self.paths = CGBDMDataPaths(Path(root), split)
    self.processor = processor or CGBDMProcessor()
    self.names = _load_names(self.paths.inpaint_dir, name_manifest)
    self.encoding = encoding
    self.annotations = _read_grouped_boxes(self.paths.annotation_csv)
    self.saliency_boxes = _read_grouped_boxes(self.paths.saliency_csv)

__len__

__len__() -> int

Return number of image rows.

Source code in models/cgb-dm/src/cgb_dm/data.py
86
87
88
def __len__(self) -> int:
    """Return number of image rows."""
    return len(self.names)

__getitem__

__getitem__(
    index: int,
) -> dict[str, Float[torch.Tensor, "..."]]

Return one normalized CGB-DM training row.

Source code in models/cgb-dm/src/cgb_dm/data.py
 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
def __getitem__(self, index: int) -> dict[str, Float[torch.Tensor, "..."]]:
    """Return one normalized CGB-DM training row."""
    name = self.names[index]
    image_path = self.paths.inpaint_dir / name
    image = Image.open(image_path).convert("RGB")
    width, height = image.size
    saliency = Image.open(self.paths.saliency_dir / name).convert("L")
    saliency_sub = Image.open(self.paths.saliency_sub_dir / name).convert("L")
    if self.encoding == "reference":
        return _encode_reference_row(
            image=image,
            saliency=saliency,
            saliency_sub=saliency_sub,
            annotations=self.annotations[name],
            saliency_box=self.saliency_boxes[name][0],
            width=width,
            height=height,
            max_seq_length=self.processor.max_seq_length,
            num_labels=self.processor.num_labels,
            image_size=self.processor.image_size,
        )
    if self.encoding != "public":
        raise ValueError(f"Unsupported CGB-DM dataset encoding: {self.encoding}")

    content = self.processor(
        image,
        saliency_isnet=saliency,
        saliency_basnet=saliency_sub,
        saliency_box=_normalize_ltrb(self.saliency_boxes[name][0], width, height),
    )
    boxes, labels = zip(*self.annotations[name], strict=False)
    public_labels = (
        [label - 1 for label in labels]
        if self.processor.dataset_name == "pku_posterlayout"
        else list(labels)
    )
    layout = self.processor.encode_layout(
        bbox=[[_normalize_ltrb(box, width, height).tolist() for box in boxes]],
        labels=[public_labels],
    )["layout"][0]
    return {
        "pixel_values": content["pixel_values"][0],
        "layout": layout,
        "saliency_box": content["saliency_box"][0],
    }

modeling_cgb_dm

Transformer denoiser used by CGB-DM checkpoints.

CGBDMModelOutput dataclass

Bases: BaseOutput

Output returned by the CGB-DM denoiser.

Attributes:

Name Type Description
sample Float[Tensor, 'batch elements channels']

Predicted epsilon tensor with the same shape as the input layout.

cgb_weight Float[Tensor, 'batch 1 1'] | None

Content-graphic balance weight estimated from image tokens.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
22
23
24
25
26
27
28
29
30
31
32
@dataclass
class CGBDMModelOutput(BaseOutput):
    """Output returned by the CGB-DM denoiser.

    Attributes:
        sample: Predicted epsilon tensor with the same shape as the input layout.
        cgb_weight: Content-graphic balance weight estimated from image tokens.
    """

    sample: Float[torch.Tensor, "batch elements channels"]
    cgb_weight: Float[torch.Tensor, "batch 1 1"] | None = None

CGBDMImageEncoder

Bases: Module

Patch image encoder used for content-aware conditioning.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
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
class CGBDMImageEncoder(nn.Module):
    """Patch image encoder used for content-aware conditioning."""

    def __init__(
        self,
        *,
        image_size: tuple[int, int],
        patch_size: int,
        in_channels: int,
        dim_model: int,
        depth: int = 6,
        heads: int = 8,
        mlp_dim: int = 2048,
        dim_head: int = 64,
        dropout: float = 0.1,
        emb_dropout: float = 0.1,
    ) -> None:
        """Initialize patch embedding and image transformer blocks."""
        super().__init__()
        image_height, image_width = image_size
        patch_height, patch_width = _pair(patch_size)
        if image_height % patch_height or image_width % patch_width:
            raise ValueError("image_size must be divisible by patch_size")

        num_patches = (image_height // patch_height) * (image_width // patch_width)
        patch_dim = in_channels * patch_height * patch_width
        self.to_patch_embedding = nn.Sequential(
            Rearrange(
                "b c (h p1) (w p2) -> b (h w) (p1 p2 c)",
                p1=patch_height,
                p2=patch_width,
            ),
            nn.LayerNorm(patch_dim),
            nn.Linear(patch_dim, dim_model),
            nn.LayerNorm(dim_model),
        )
        self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim_model))
        self.cls_token = nn.Parameter(torch.randn(1, 1, dim_model))
        self.dropout = nn.Dropout(emb_dropout)
        self.transformer = _ImageTransformer(
            dim_model, depth, heads, dim_head, mlp_dim, dropout
        )

    def forward(
        self, image: Float[torch.Tensor, "batch channels height width"]
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Encode image tensors into patch tokens."""
        x = self.to_patch_embedding(image)
        batch, tokens, _ = x.shape
        cls_tokens = repeat(self.cls_token, "1 1 d -> b 1 d", b=batch)
        x = torch.cat((cls_tokens, x), dim=1)
        x = x + self.pos_embedding[:, : tokens + 1]
        return self.transformer(self.dropout(x))

__init__

__init__(
    *,
    image_size: tuple[int, int],
    patch_size: int,
    in_channels: int,
    dim_model: int,
    depth: int = 6,
    heads: int = 8,
    mlp_dim: int = 2048,
    dim_head: int = 64,
    dropout: float = 0.1,
    emb_dropout: float = 0.1,
) -> None

Initialize patch embedding and image transformer blocks.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
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
def __init__(
    self,
    *,
    image_size: tuple[int, int],
    patch_size: int,
    in_channels: int,
    dim_model: int,
    depth: int = 6,
    heads: int = 8,
    mlp_dim: int = 2048,
    dim_head: int = 64,
    dropout: float = 0.1,
    emb_dropout: float = 0.1,
) -> None:
    """Initialize patch embedding and image transformer blocks."""
    super().__init__()
    image_height, image_width = image_size
    patch_height, patch_width = _pair(patch_size)
    if image_height % patch_height or image_width % patch_width:
        raise ValueError("image_size must be divisible by patch_size")

    num_patches = (image_height // patch_height) * (image_width // patch_width)
    patch_dim = in_channels * patch_height * patch_width
    self.to_patch_embedding = nn.Sequential(
        Rearrange(
            "b c (h p1) (w p2) -> b (h w) (p1 p2 c)",
            p1=patch_height,
            p2=patch_width,
        ),
        nn.LayerNorm(patch_dim),
        nn.Linear(patch_dim, dim_model),
        nn.LayerNorm(dim_model),
    )
    self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim_model))
    self.cls_token = nn.Parameter(torch.randn(1, 1, dim_model))
    self.dropout = nn.Dropout(emb_dropout)
    self.transformer = _ImageTransformer(
        dim_model, depth, heads, dim_head, mlp_dim, dropout
    )

forward

forward(
    image: Float[Tensor, "batch channels height width"],
) -> Float[torch.Tensor, "batch tokens channels"]

Encode image tensors into patch tokens.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
164
165
166
167
168
169
170
171
172
173
def forward(
    self, image: Float[torch.Tensor, "batch channels height width"]
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Encode image tensors into patch tokens."""
    x = self.to_patch_embedding(image)
    batch, tokens, _ = x.shape
    cls_tokens = repeat(self.cls_token, "1 1 d -> b 1 d", b=batch)
    x = torch.cat((cls_tokens, x), dim=1)
    x = x + self.pos_embedding[:, : tokens + 1]
    return self.transformer(self.dropout(x))

CGBDMLayoutModule

Bases: Module

Timestep-conditioned layout encoder or decoder block stack.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
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
class CGBDMLayoutModule(nn.Module):
    """Timestep-conditioned layout encoder or decoder block stack."""

    def __init__(
        self,
        *,
        seq_dim: int,
        dim_model: int,
        n_head: int,
        feature_dim: int,
        num_layers: int,
        num_train_timesteps: int,
        max_seq_length: int,
        if_encoder: bool,
    ) -> None:
        """Initialize layout projections and timestep-aware blocks."""
        super().__init__()
        self.max_elem = max_seq_length
        self.if_encoder = if_encoder
        self.mlp = (
            _LayoutMLP(seq_dim, dim_model, dim_model, 3)
            if if_encoder
            else _LayoutMLP(dim_model, dim_model, seq_dim, 3)
        )
        self.pos_encoder = SinusoidalPosEmb(num_steps=max_seq_length, dim=dim_model)
        layer = _LayoutBlock(
            d_model=dim_model,
            nhead=n_head,
            dim_feedforward=feature_dim,
            diffusion_steps=num_train_timesteps,
            timestep_type="adalayernorm",
        )
        self.layers = nn.ModuleList(copy.deepcopy(layer) for _ in range(num_layers))
        self.num_layers = num_layers

    def forward(
        self,
        src: Float[Tensor, "batch elements channels"],
        img_encode: Float[Tensor, "..."] | None,
        cgb_w: Float[Tensor, "..."] | None,
        salbox_encode: Float[Tensor, "..."] | None,
        timestep: Int[Tensor, "batch"],
    ) -> Float[Tensor, "batch elements channels"]:
        """Run the layout encoder or decoder path."""
        if self.if_encoder:
            output = F.softplus(self.mlp(src))
            positions = torch.arange(self.max_elem, device=src.device)
            output = output + self.pos_encoder(positions)
        else:
            output = src
        for index, layer in enumerate(self.layers):
            output = layer(
                output,
                img_encode,
                cgb_w,
                salbox_encode,
                timestep=timestep,
            )
            if index < self.num_layers - 1:
                output = F.softplus(output)
        if not self.if_encoder:
            output = self.mlp(output)
        return output

__init__

__init__(
    *,
    seq_dim: int,
    dim_model: int,
    n_head: int,
    feature_dim: int,
    num_layers: int,
    num_train_timesteps: int,
    max_seq_length: int,
    if_encoder: bool,
) -> None

Initialize layout projections and timestep-aware blocks.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
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
def __init__(
    self,
    *,
    seq_dim: int,
    dim_model: int,
    n_head: int,
    feature_dim: int,
    num_layers: int,
    num_train_timesteps: int,
    max_seq_length: int,
    if_encoder: bool,
) -> None:
    """Initialize layout projections and timestep-aware blocks."""
    super().__init__()
    self.max_elem = max_seq_length
    self.if_encoder = if_encoder
    self.mlp = (
        _LayoutMLP(seq_dim, dim_model, dim_model, 3)
        if if_encoder
        else _LayoutMLP(dim_model, dim_model, seq_dim, 3)
    )
    self.pos_encoder = SinusoidalPosEmb(num_steps=max_seq_length, dim=dim_model)
    layer = _LayoutBlock(
        d_model=dim_model,
        nhead=n_head,
        dim_feedforward=feature_dim,
        diffusion_steps=num_train_timesteps,
        timestep_type="adalayernorm",
    )
    self.layers = nn.ModuleList(copy.deepcopy(layer) for _ in range(num_layers))
    self.num_layers = num_layers

forward

forward(
    src: Float[Tensor, "batch elements channels"],
    img_encode: Float[Tensor, "..."] | None,
    cgb_w: Float[Tensor, "..."] | None,
    salbox_encode: Float[Tensor, "..."] | None,
    timestep: Int[Tensor, "batch"],
) -> Float[Tensor, "batch elements channels"]

Run the layout encoder or decoder path.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
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
def forward(
    self,
    src: Float[Tensor, "batch elements channels"],
    img_encode: Float[Tensor, "..."] | None,
    cgb_w: Float[Tensor, "..."] | None,
    salbox_encode: Float[Tensor, "..."] | None,
    timestep: Int[Tensor, "batch"],
) -> Float[Tensor, "batch elements channels"]:
    """Run the layout encoder or decoder path."""
    if self.if_encoder:
        output = F.softplus(self.mlp(src))
        positions = torch.arange(self.max_elem, device=src.device)
        output = output + self.pos_encoder(positions)
    else:
        output = src
    for index, layer in enumerate(self.layers):
        output = layer(
            output,
            img_encode,
            cgb_w,
            salbox_encode,
            timestep=timestep,
        )
        if index < self.num_layers - 1:
            output = F.softplus(output)
    if not self.if_encoder:
        output = self.mlp(output)
    return output

CGBDMQFormer

Bases: Module

Estimate a scalar content-graphic balance weight from image tokens.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
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
class CGBDMQFormer(nn.Module):
    """Estimate a scalar content-graphic balance weight from image tokens."""

    def __init__(
        self,
        in_dim: int = 512,
        out_dim: int = 1,
        num_heads: int = 8,
        num_tokens: int = 1,
        n_layers: int = 2,
    ) -> None:
        """Initialize query-token transformer and scalar projection."""
        super().__init__()
        scale = in_dim**-0.5
        self.num_tokens = num_tokens
        self.scale_emb = nn.Parameter(torch.randn(1, num_tokens, in_dim) * scale)
        self.transformer_blocks = _TokenTransformer(
            width=in_dim, layers=n_layers, heads=num_heads
        )
        self.ln1 = nn.LayerNorm(in_dim)
        self.ln2 = nn.LayerNorm(in_dim)
        self.out = nn.Sequential(
            nn.Linear(in_dim, in_dim // 2),
            nn.GELU(),
            nn.Linear(in_dim // 2, out_dim),
            nn.Softplus(),
        )

    def forward(
        self, image_tokens: Float[torch.Tensor, "batch tokens channels"]
    ) -> Float[torch.Tensor, "batch tokens 1"]:
        """Pool image tokens into a content-graphic balance weight."""
        scale_emb = self.scale_emb.repeat(image_tokens.shape[0], 1, 1)
        x = torch.cat([scale_emb, image_tokens], dim=1)
        x = self.ln1(x).permute(1, 0, 2)
        x = self.transformer_blocks(x).permute(1, 0, 2)
        x = self.ln2(x[:, : self.num_tokens, :])
        return self.out(x)

__init__

__init__(
    in_dim: int = 512,
    out_dim: int = 1,
    num_heads: int = 8,
    num_tokens: int = 1,
    n_layers: int = 2,
) -> None

Initialize query-token transformer and scalar projection.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
def __init__(
    self,
    in_dim: int = 512,
    out_dim: int = 1,
    num_heads: int = 8,
    num_tokens: int = 1,
    n_layers: int = 2,
) -> None:
    """Initialize query-token transformer and scalar projection."""
    super().__init__()
    scale = in_dim**-0.5
    self.num_tokens = num_tokens
    self.scale_emb = nn.Parameter(torch.randn(1, num_tokens, in_dim) * scale)
    self.transformer_blocks = _TokenTransformer(
        width=in_dim, layers=n_layers, heads=num_heads
    )
    self.ln1 = nn.LayerNorm(in_dim)
    self.ln2 = nn.LayerNorm(in_dim)
    self.out = nn.Sequential(
        nn.Linear(in_dim, in_dim // 2),
        nn.GELU(),
        nn.Linear(in_dim // 2, out_dim),
        nn.Softplus(),
    )

forward

forward(
    image_tokens: Float[Tensor, "batch tokens channels"],
) -> Float[torch.Tensor, "batch tokens 1"]

Pool image tokens into a content-graphic balance weight.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
440
441
442
443
444
445
446
447
448
449
def forward(
    self, image_tokens: Float[torch.Tensor, "batch tokens channels"]
) -> Float[torch.Tensor, "batch tokens 1"]:
    """Pool image tokens into a content-graphic balance weight."""
    scale_emb = self.scale_emb.repeat(image_tokens.shape[0], 1, 1)
    x = torch.cat([scale_emb, image_tokens], dim=1)
    x = self.ln1(x).permute(1, 0, 2)
    x = self.transformer_blocks(x).permute(1, 0, 2)
    x = self.ln2(x[:, : self.num_tokens, :])
    return self.out(x)

CGBDMMLP

Bases: _LayoutMLP

Softplus MLP used for saliency-box embeddings.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
452
453
454
455
456
457
class CGBDMMLP(_LayoutMLP):
    """Softplus MLP used for saliency-box embeddings."""

    def __init__(self, input_dim: int, hidden_dim: int, output_dim: int) -> None:
        """Initialize saliency-box embedding layers."""
        super().__init__(input_dim, hidden_dim, output_dim, num_layers=3)

__init__

__init__(
    input_dim: int, hidden_dim: int, output_dim: int
) -> None

Initialize saliency-box embedding layers.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
455
456
457
def __init__(self, input_dim: int, hidden_dim: int, output_dim: int) -> None:
    """Initialize saliency-box embedding layers."""
    super().__init__(input_dim, hidden_dim, output_dim, num_layers=3)

CGBDMTransformerModel

Bases: ModelMixin, ConfigMixin

CGB-DM transformer denoiser with image and saliency conditioning.

Parameters:

Name Type Description Default
num_labels int

Internal class-channel count including invalid/pad.

4
max_seq_length int

Maximum number of layout elements.

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

Image tensor size as (height, width).

(384, 256)
patch_size int

Image patch size.

32
dim_model int

Hidden dimension.

512
n_head int

Attention head count.

8
feature_dim int

Feed-forward hidden dimension.

1024
num_layers int

Number of decoder layers.

4
num_train_timesteps int

Number of training diffusion steps.

1000

Examples:

>>> model = CGBDMTransformerModel(num_labels=4, max_seq_length=2, image_size=(32, 32), dim_model=16, n_head=2, feature_dim=32, num_layers=1)
>>> model.seq_dim
8
Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
class CGBDMTransformerModel(ModelMixin, ConfigMixin):
    """CGB-DM transformer denoiser with image and saliency conditioning.

    Args:
        num_labels: Internal class-channel count including invalid/pad.
        max_seq_length: Maximum number of layout elements.
        image_size: Image tensor size as ``(height, width)``.
        patch_size: Image patch size.
        dim_model: Hidden dimension.
        n_head: Attention head count.
        feature_dim: Feed-forward hidden dimension.
        num_layers: Number of decoder layers.
        num_train_timesteps: Number of training diffusion steps.

    Examples:
        >>> model = CGBDMTransformerModel(num_labels=4, max_seq_length=2, image_size=(32, 32), dim_model=16, n_head=2, feature_dim=32, num_layers=1)
        >>> model.seq_dim
        8
    """

    config_name = "model_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        num_labels: int = 4,
        max_seq_length: int = 16,
        image_size: tuple[int, int] | list[int] = (384, 256),
        patch_size: int = 32,
        dim_model: int = 512,
        n_head: int = 8,
        feature_dim: int = 1024,
        num_layers: int = 4,
        num_train_timesteps: int = 1000,
    ) -> None:
        """Initialize the CGB-DM denoising network."""
        super().__init__()
        self.num_labels = int(num_labels)
        self.max_seq_length = int(max_seq_length)
        self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
        self.seq_dim = self.num_labels + 4
        self.img_encoder = CGBDMImageEncoder(
            image_size=self.image_size,
            patch_size=patch_size,
            in_channels=4,
            dim_model=dim_model,
            depth=6,
            heads=8,
            mlp_dim=2048,
            dropout=0.1,
            emb_dropout=0.1,
        )
        self.layout_encoder = CGBDMLayoutModule(
            seq_dim=self.seq_dim,
            dim_model=dim_model,
            n_head=n_head,
            feature_dim=feature_dim,
            num_layers=num_layers // 2,
            num_train_timesteps=num_train_timesteps,
            max_seq_length=self.max_seq_length,
            if_encoder=True,
        )
        self.layout_decoder = CGBDMLayoutModule(
            seq_dim=self.seq_dim,
            dim_model=dim_model,
            n_head=n_head,
            feature_dim=feature_dim,
            num_layers=num_layers,
            num_train_timesteps=num_train_timesteps,
            max_seq_length=self.max_seq_length,
            if_encoder=False,
        )
        self.cgbwp = CGBDMQFormer(in_dim=dim_model, num_tokens=1)
        self.salbox_encoder = CGBDMMLP(4, dim_model, dim_model)

    def forward(
        self,
        sample: Float[torch.Tensor, "batch elements channels"],
        image: Float[torch.Tensor, "batch 4 height width"],
        saliency_box: Float[torch.Tensor, "batch 1 4"],
        timestep: Int[torch.Tensor, "batch"],
        return_dict: bool = True,
    ) -> CGBDMModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
        """Predict epsilon for a noisy layout tensor.

        Args:
            sample: Noisy class-plus-box layout tensor.
            image: Four-channel RGB/saliency tensor in ``[-1, 1]``.
            saliency_box: Saliency box tensor in internal ``[-1, 1]`` center xywh.
            timestep: Per-example diffusion timestep ids.
            return_dict: Whether to return ``CGBDMModelOutput``.

        Returns:
            Output dataclass or one-item tuple containing predicted epsilon.
        """
        image_tokens = self.img_encoder(image)
        saliency_tokens = self.salbox_encoder(saliency_box)
        encoded = self.layout_encoder(sample, None, None, None, timestep)
        cgb_weight = self.cgbwp(image_tokens)
        pred = self.layout_decoder(
            encoded,
            image_tokens,
            cgb_weight,
            saliency_tokens,
            timestep,
        )
        if not return_dict:
            return (pred,)
        return CGBDMModelOutput(sample=pred, cgb_weight=cgb_weight)

__init__

__init__(
    *,
    num_labels: int = 4,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
    patch_size: int = 32,
    dim_model: int = 512,
    n_head: int = 8,
    feature_dim: int = 1024,
    num_layers: int = 4,
    num_train_timesteps: int = 1000,
) -> None

Initialize the CGB-DM denoising network.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
@register_to_config
def __init__(
    self,
    *,
    num_labels: int = 4,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
    patch_size: int = 32,
    dim_model: int = 512,
    n_head: int = 8,
    feature_dim: int = 1024,
    num_layers: int = 4,
    num_train_timesteps: int = 1000,
) -> None:
    """Initialize the CGB-DM denoising network."""
    super().__init__()
    self.num_labels = int(num_labels)
    self.max_seq_length = int(max_seq_length)
    self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
    self.seq_dim = self.num_labels + 4
    self.img_encoder = CGBDMImageEncoder(
        image_size=self.image_size,
        patch_size=patch_size,
        in_channels=4,
        dim_model=dim_model,
        depth=6,
        heads=8,
        mlp_dim=2048,
        dropout=0.1,
        emb_dropout=0.1,
    )
    self.layout_encoder = CGBDMLayoutModule(
        seq_dim=self.seq_dim,
        dim_model=dim_model,
        n_head=n_head,
        feature_dim=feature_dim,
        num_layers=num_layers // 2,
        num_train_timesteps=num_train_timesteps,
        max_seq_length=self.max_seq_length,
        if_encoder=True,
    )
    self.layout_decoder = CGBDMLayoutModule(
        seq_dim=self.seq_dim,
        dim_model=dim_model,
        n_head=n_head,
        feature_dim=feature_dim,
        num_layers=num_layers,
        num_train_timesteps=num_train_timesteps,
        max_seq_length=self.max_seq_length,
        if_encoder=False,
    )
    self.cgbwp = CGBDMQFormer(in_dim=dim_model, num_tokens=1)
    self.salbox_encoder = CGBDMMLP(4, dim_model, dim_model)

forward

forward(
    sample: Float[Tensor, "batch elements channels"],
    image: Float[Tensor, "batch 4 height width"],
    saliency_box: Float[Tensor, "batch 1 4"],
    timestep: Int[Tensor, "batch"],
    return_dict: bool = True,
) -> (
    CGBDMModelOutput
    | tuple[Float[torch.Tensor, "batch elements channels"]]
)

Predict epsilon for a noisy layout tensor.

Parameters:

Name Type Description Default
sample Float[Tensor, 'batch elements channels']

Noisy class-plus-box layout tensor.

required
image Float[Tensor, 'batch 4 height width']

Four-channel RGB/saliency tensor in [-1, 1].

required
saliency_box Float[Tensor, 'batch 1 4']

Saliency box tensor in internal [-1, 1] center xywh.

required
timestep Int[Tensor, 'batch']

Per-example diffusion timestep ids.

required
return_dict bool

Whether to return CGBDMModelOutput.

True

Returns:

Type Description
CGBDMModelOutput | tuple[Float[Tensor, 'batch elements channels']]

Output dataclass or one-item tuple containing predicted epsilon.

Source code in models/cgb-dm/src/cgb_dm/modeling_cgb_dm.py
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
def forward(
    self,
    sample: Float[torch.Tensor, "batch elements channels"],
    image: Float[torch.Tensor, "batch 4 height width"],
    saliency_box: Float[torch.Tensor, "batch 1 4"],
    timestep: Int[torch.Tensor, "batch"],
    return_dict: bool = True,
) -> CGBDMModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
    """Predict epsilon for a noisy layout tensor.

    Args:
        sample: Noisy class-plus-box layout tensor.
        image: Four-channel RGB/saliency tensor in ``[-1, 1]``.
        saliency_box: Saliency box tensor in internal ``[-1, 1]`` center xywh.
        timestep: Per-example diffusion timestep ids.
        return_dict: Whether to return ``CGBDMModelOutput``.

    Returns:
        Output dataclass or one-item tuple containing predicted epsilon.
    """
    image_tokens = self.img_encoder(image)
    saliency_tokens = self.salbox_encoder(saliency_box)
    encoded = self.layout_encoder(sample, None, None, None, timestep)
    cgb_weight = self.cgbwp(image_tokens)
    pred = self.layout_decoder(
        encoded,
        image_tokens,
        cgb_weight,
        saliency_tokens,
        timestep,
    )
    if not return_dict:
        return (pred,)
    return CGBDMModelOutput(sample=pred, cgb_weight=cgb_weight)

pipeline_cgb_dm

Diffusers pipeline for CGB-DM content-aware layout generation.

OutputType

Bases: StrEnum

Supported CGB-DM pipeline output containers.

Source code in models/cgb-dm/src/cgb_dm/pipeline_cgb_dm.py
25
26
27
28
29
class OutputType(StrEnum):
    """Supported CGB-DM pipeline output containers."""

    dataclass = auto()
    dict = auto()

CGBDMPipeline

Bases: DiffusionPipeline

Generate content-aware poster layouts with CGB-DM.

Parameters:

Name Type Description Default
model CGBDMTransformerModel

CGB-DM denoiser.

required
scheduler CGBDMScheduler

CGB-DM scheduler.

required
processor CGBDMProcessor

Processor for images and layouts.

required

Examples:

>>> model = CGBDMTransformerModel(num_labels=4, max_seq_length=2, image_size=(32, 32), dim_model=16, n_head=2, feature_dim=32, num_layers=1)
>>> pipe = CGBDMPipeline(model=model, scheduler=CGBDMScheduler(num_train_timesteps=10, ddim_num_steps=1), processor=CGBDMProcessor(max_seq_length=2, image_size=(32, 32)))
>>> pipe.processor.seq_dim
8
Source code in models/cgb-dm/src/cgb_dm/pipeline_cgb_dm.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
class CGBDMPipeline(DiffusionPipeline):
    """Generate content-aware poster layouts with CGB-DM.

    Args:
        model: CGB-DM denoiser.
        scheduler: CGB-DM scheduler.
        processor: Processor for images and layouts.

    Examples:
        >>> model = CGBDMTransformerModel(num_labels=4, max_seq_length=2, image_size=(32, 32), dim_model=16, n_head=2, feature_dim=32, num_layers=1)
        >>> pipe = CGBDMPipeline(model=model, scheduler=CGBDMScheduler(num_train_timesteps=10, ddim_num_steps=1), processor=CGBDMProcessor(max_seq_length=2, image_size=(32, 32)))
        >>> pipe.processor.seq_dim
        8
    """

    model_cpu_offload_seq = "model"

    def __init__(
        self,
        model: CGBDMTransformerModel,
        scheduler: CGBDMScheduler,
        processor: CGBDMProcessor,
    ) -> None:
        """Initialize pipeline components."""
        super().__init__()
        self.register_modules(model=model, scheduler=scheduler, processor=processor)
        self.model = model
        self.scheduler = scheduler
        self.processor = processor
        self.model.eval()

    @property
    def components(
        self,
    ) -> dict[str, CGBDMTransformerModel | CGBDMScheduler | CGBDMProcessor]:
        """Return serializable pipeline components."""
        return {
            "model": self.model,
            "scheduler": self.scheduler,
            "processor": self.processor,
        }

    @torch.no_grad()
    def __call__(
        self,
        *,
        image: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | None = None,
        content: dict[
            str,
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes],
        ]
        | None = None,
        saliency: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | None = None,
        saliency_isnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | None = None,
        saliency_basnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | None = None,
        saliency_box: Float[torch.Tensor, "..."] | None = None,
        pixel_values: Float[torch.Tensor, "batch channels height width"] | 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, "..."]
        | Int[np.ndarray, "..."]
        | list[list[int]]
        | list[int]
        | list[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "..."]
        | Float[np.ndarray, "..."]
        | list[list[list[float]]]
        | list[list[float]]
        | list[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "..."]
        | Bool[np.ndarray, "..."]
        | list[list[bool]]
        | list[bool]
        | list[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "..."] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        completion_ratio: float = 0.2,
        output_type: OutputType | str = OutputType.dataclass,
        return_intermediates: bool = False,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | dict[int, str]
            | list[Float[torch.Tensor, "..."]]
            | dict[str, str | Float[torch.Tensor, "..."] | None]
            | None,
        ]
    ):
        """Run DDIM sampling and return generated layouts.

        Args:
            image: RGB image or batch of images.
            content: Optional content container with ``image`` and saliency keys.
            saliency: Optional merged saliency map.
            saliency_isnet: Optional first saliency map.
            saliency_basnet: Optional second saliency map.
            saliency_box: Optional normalized center ``xywh`` saliency box.
            pixel_values: Preprocessed four-channel image tensor.
            batch_size: Number of layouts when ``pixel_values`` is synthetic.
            seed: Convenience seed used when ``generator`` is absent.
            generator: Optional torch generator. Takes precedence over ``seed``.
            condition_type: Canonical condition mode or alias.
            labels: Conditioning labels for constrained modes.
            bbox: Conditioning boxes for constrained modes.
            mask: Optional valid-element mask.
            num_elements: Accepted for interface compatibility.
            box_format: Input box format.
            normalized: Whether input boxes are normalized.
            canvas_size: Canvas size required for pixel boxes.
            num_inference_steps: DDIM step count.
            completion_ratio: Completion conditioning keep ratio.
            output_type: ``"dataclass"`` or ``"dict"``.
            return_intermediates: Whether to include trajectory/debug tensors.

        Returns:
            Layout output dataclass or dictionary.

        Raises:
            ValueError: If required content or conditioning inputs are absent.
        """
        del num_elements
        canonical = normalize_condition_type(condition_type)
        out_type = normalize_output_type(output_type)
        if generator is None and seed is not None:
            generator = torch.Generator(device=self.device).manual_seed(seed)
        if pixel_values is None:
            if content is not None:
                image = content.get("image", image)
                saliency = content.get("saliency", saliency)
            encoded_content = self.processor(
                image,
                saliency=saliency,
                saliency_isnet=saliency_isnet,
                saliency_basnet=saliency_basnet,
                saliency_box=saliency_box,
            )
            pixel_values = encoded_content["pixel_values"]
            resolved_saliency_box = encoded_content["saliency_box"]
        else:
            if saliency_box is None:
                resolved_saliency_box = torch.zeros(pixel_values.shape[0], 1, 4)
            else:
                resolved_saliency_box = 2 * (
                    torch.as_tensor(saliency_box, dtype=torch.float32).clamp(0.0, 1.0)
                    - 0.5
                )
                if resolved_saliency_box.ndim == 2:
                    resolved_saliency_box = resolved_saliency_box.unsqueeze(1)
        batch_size = int(
            pixel_values.shape[0] if pixel_values is not None else batch_size
        )
        encoded_layout = None
        if canonical is not ConditionType.content_image:
            if bbox is None or labels is None:
                raise ValueError(
                    f"bbox and labels are required for condition_type={condition_type}"
                )

            encoded_layout = self.processor.encode_layout(
                bbox=bbox,
                labels=labels,
                mask=mask,
                box_format=box_format,
                normalized=normalized,
                canvas_size=canvas_size,
            )
            batch_size = encoded_layout["layout"].shape[0]
        self.scheduler.set_timesteps(num_inference_steps, device=self.device)
        sample = self.scheduler.initial_sample(
            batch_size,
            self.processor.max_seq_length,
            self.processor.seq_dim,
            device=self.device,
            generator=generator,
        )
        real_layout = (
            None if encoded_layout is None else encoded_layout["layout"].to(self.device)
        )
        fix_mask = None
        if real_layout is not None:
            fix_mask = self.scheduler.condition_mask(
                real_layout,
                canonical,
                completion_ratio=completion_ratio,
                generator=generator,
            )
            sample = torch.where(fix_mask, real_layout, sample)
        pixel_values = pixel_values.to(self.device)
        resolved_saliency_box = resolved_saliency_box.to(self.device)
        trajectory: list[Float[torch.Tensor, "..."]] = []
        cgb_weights: list[Float[torch.Tensor, "..."]] = []
        for index, timestep in enumerate(self.scheduler.timesteps):
            timestep_batch = torch.full(
                (batch_size,),
                int(timestep.item()),
                device=self.device,
                dtype=torch.long,
            )
            model_out = self.model(
                sample, pixel_values, resolved_saliency_box, timestep_batch
            )
            step = self.scheduler.step(
                model_out.sample,
                timestep_batch,
                sample,
                len(self.scheduler.timesteps) - index - 1,
                generator=generator,
            )
            sample = step.prev_sample
            if real_layout is not None and fix_mask is not None:
                sample = torch.where(fix_mask, real_layout, sample)
            if return_intermediates:
                trajectory.append(sample.detach().cpu())
                if model_out.cgb_weight is not None:
                    cgb_weights.append(model_out.cgb_weight.detach().cpu())
        intermediates = None
        if return_intermediates:
            intermediates = {
                "condition_type": str(canonical),
                "saliency_box": resolved_saliency_box.detach().cpu(),
                "cgb_weight": cgb_weights[-1] if cgb_weights else None,
            }
        return self._decode(sample, out_type, trajectory, intermediates)

    def _decode(
        self,
        sample: Float[torch.Tensor, "..."],
        output_type: OutputType,
        trajectory: list[Float[torch.Tensor, "..."]],
        intermediates: dict[str, str | Float[torch.Tensor, "..."] | None] | None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | dict[int, str]
            | list[Float[torch.Tensor, "..."]]
            | dict[str, str | Float[torch.Tensor, "..."] | None]
            | None,
        ]
    ):
        decoded = self.processor.decode(
            sample.detach().cpu(),
            output_type="dataclass",
            intermediates=intermediates,
        )
        output = LayoutGenerationOutput(
            bbox=decoded.bbox,
            labels=decoded.labels,
            mask=decoded.mask,
            id2label=decoded.id2label,
            sequences=decoded.sequences,
            scores=decoded.scores,
            trajectory=trajectory or None,
            intermediates=decoded.intermediates,
        )
        if output_type is OutputType.dict:
            return dict(output)
        return output

components property

components: dict[
    str,
    CGBDMTransformerModel | CGBDMScheduler | CGBDMProcessor,
]

Return serializable pipeline components.

__init__

__init__(
    model: CGBDMTransformerModel,
    scheduler: CGBDMScheduler,
    processor: CGBDMProcessor,
) -> None

Initialize pipeline components.

Source code in models/cgb-dm/src/cgb_dm/pipeline_cgb_dm.py
110
111
112
113
114
115
116
117
118
119
120
121
122
def __init__(
    self,
    model: CGBDMTransformerModel,
    scheduler: CGBDMScheduler,
    processor: CGBDMProcessor,
) -> None:
    """Initialize pipeline components."""
    super().__init__()
    self.register_modules(model=model, scheduler=scheduler, processor=processor)
    self.model = model
    self.scheduler = scheduler
    self.processor = processor
    self.model.eval()

__call__

__call__(
    *,
    image: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    content: dict[
        str,
        Float[Tensor, "..."]
        | Image
        | str
        | bytes
        | Path
        | IO[bytes],
    ]
    | None = None,
    saliency: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_isnet: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_basnet: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_box: Float[Tensor, "..."] | None = None,
    pixel_values: Float[
        Tensor, "batch channels height width"
    ]
    | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.content_image,
    labels: Int[Tensor, "..."]
    | Int[ndarray, "..."]
    | list[list[int]]
    | list[int]
    | list[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "..."]
    | Float[ndarray, "..."]
    | list[list[list[float]]]
    | list[list[float]]
    | list[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "..."]
    | Bool[ndarray, "..."]
    | list[list[bool]]
    | list[bool]
    | list[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "..."]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    completion_ratio: float = 0.2,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | dict[int, str]
        | list[Float[torch.Tensor, "..."]]
        | dict[str, str | Float[torch.Tensor, "..."] | None]
        | None,
    ]
)

Run DDIM sampling and return generated layouts.

Parameters:

Name Type Description Default
image Float[Tensor, '...'] | Image | str | bytes | Path | IO[bytes] | None

RGB image or batch of images.

None
content dict[str, Float[Tensor, '...'] | Image | str | bytes | Path | IO[bytes]] | None

Optional content container with image and saliency keys.

None
saliency Float[Tensor, '...'] | Image | str | bytes | Path | IO[bytes] | None

Optional merged saliency map.

None
saliency_isnet Float[Tensor, '...'] | Image | str | bytes | Path | IO[bytes] | None

Optional first saliency map.

None
saliency_basnet Float[Tensor, '...'] | Image | str | bytes | Path | IO[bytes] | None

Optional second saliency map.

None
saliency_box Float[Tensor, '...'] | None

Optional normalized center xywh saliency box.

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

Preprocessed four-channel image tensor.

None
batch_size int

Number of layouts when pixel_values is synthetic.

1
seed int | None

Convenience seed used when generator is absent.

None
generator Generator | None

Optional torch generator. Takes precedence over seed.

None
condition_type ConditionType | str

Canonical condition mode or alias.

content_image
labels Int[Tensor, '...'] | Int[ndarray, '...'] | list[list[int]] | list[int] | list[ArrayLikeInput] | None

Conditioning labels for constrained modes.

None
bbox Float[Tensor, '...'] | Float[ndarray, '...'] | list[list[list[float]]] | list[list[float]] | list[ArrayLikeInput] | None

Conditioning boxes for constrained modes.

None
mask Bool[Tensor, '...'] | Bool[ndarray, '...'] | list[list[bool]] | list[bool] | list[ArrayLikeInput] | None

Optional valid-element mask.

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

Accepted for interface compatibility.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether input boxes are normalized.

True
canvas_size tuple[int, int] | None

Canvas size required for pixel boxes.

None
num_inference_steps int | None

DDIM step count.

None
completion_ratio float

Completion conditioning keep ratio.

0.2
output_type OutputType | str

"dataclass" or "dict".

dataclass
return_intermediates bool

Whether to include trajectory/debug tensors.

False

Returns:

Type Description
LayoutGenerationOutput | dict[str, Float[Tensor, '...'] | Int[Tensor, '...'] | Bool[Tensor, '...'] | dict[int, str] | list[Float[Tensor, '...']] | dict[str, str | Float[Tensor, '...'] | None] | None]

Layout output dataclass or dictionary.

Raises:

Type Description
ValueError

If required content or conditioning inputs are absent.

Source code in models/cgb-dm/src/cgb_dm/pipeline_cgb_dm.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
@torch.no_grad()
def __call__(
    self,
    *,
    image: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    content: dict[
        str,
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes],
    ]
    | None = None,
    saliency: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_isnet: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_basnet: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | None = None,
    saliency_box: Float[torch.Tensor, "..."] | None = None,
    pixel_values: Float[torch.Tensor, "batch channels height width"] | 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, "..."]
    | Int[np.ndarray, "..."]
    | list[list[int]]
    | list[int]
    | list[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "..."]
    | Float[np.ndarray, "..."]
    | list[list[list[float]]]
    | list[list[float]]
    | list[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "..."]
    | Bool[np.ndarray, "..."]
    | list[list[bool]]
    | list[bool]
    | list[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "..."] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    completion_ratio: float = 0.2,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | dict[int, str]
        | list[Float[torch.Tensor, "..."]]
        | dict[str, str | Float[torch.Tensor, "..."] | None]
        | None,
    ]
):
    """Run DDIM sampling and return generated layouts.

    Args:
        image: RGB image or batch of images.
        content: Optional content container with ``image`` and saliency keys.
        saliency: Optional merged saliency map.
        saliency_isnet: Optional first saliency map.
        saliency_basnet: Optional second saliency map.
        saliency_box: Optional normalized center ``xywh`` saliency box.
        pixel_values: Preprocessed four-channel image tensor.
        batch_size: Number of layouts when ``pixel_values`` is synthetic.
        seed: Convenience seed used when ``generator`` is absent.
        generator: Optional torch generator. Takes precedence over ``seed``.
        condition_type: Canonical condition mode or alias.
        labels: Conditioning labels for constrained modes.
        bbox: Conditioning boxes for constrained modes.
        mask: Optional valid-element mask.
        num_elements: Accepted for interface compatibility.
        box_format: Input box format.
        normalized: Whether input boxes are normalized.
        canvas_size: Canvas size required for pixel boxes.
        num_inference_steps: DDIM step count.
        completion_ratio: Completion conditioning keep ratio.
        output_type: ``"dataclass"`` or ``"dict"``.
        return_intermediates: Whether to include trajectory/debug tensors.

    Returns:
        Layout output dataclass or dictionary.

    Raises:
        ValueError: If required content or conditioning inputs are absent.
    """
    del num_elements
    canonical = normalize_condition_type(condition_type)
    out_type = normalize_output_type(output_type)
    if generator is None and seed is not None:
        generator = torch.Generator(device=self.device).manual_seed(seed)
    if pixel_values is None:
        if content is not None:
            image = content.get("image", image)
            saliency = content.get("saliency", saliency)
        encoded_content = self.processor(
            image,
            saliency=saliency,
            saliency_isnet=saliency_isnet,
            saliency_basnet=saliency_basnet,
            saliency_box=saliency_box,
        )
        pixel_values = encoded_content["pixel_values"]
        resolved_saliency_box = encoded_content["saliency_box"]
    else:
        if saliency_box is None:
            resolved_saliency_box = torch.zeros(pixel_values.shape[0], 1, 4)
        else:
            resolved_saliency_box = 2 * (
                torch.as_tensor(saliency_box, dtype=torch.float32).clamp(0.0, 1.0)
                - 0.5
            )
            if resolved_saliency_box.ndim == 2:
                resolved_saliency_box = resolved_saliency_box.unsqueeze(1)
    batch_size = int(
        pixel_values.shape[0] if pixel_values is not None else batch_size
    )
    encoded_layout = None
    if canonical is not ConditionType.content_image:
        if bbox is None or labels is None:
            raise ValueError(
                f"bbox and labels are required for condition_type={condition_type}"
            )

        encoded_layout = self.processor.encode_layout(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        batch_size = encoded_layout["layout"].shape[0]
    self.scheduler.set_timesteps(num_inference_steps, device=self.device)
    sample = self.scheduler.initial_sample(
        batch_size,
        self.processor.max_seq_length,
        self.processor.seq_dim,
        device=self.device,
        generator=generator,
    )
    real_layout = (
        None if encoded_layout is None else encoded_layout["layout"].to(self.device)
    )
    fix_mask = None
    if real_layout is not None:
        fix_mask = self.scheduler.condition_mask(
            real_layout,
            canonical,
            completion_ratio=completion_ratio,
            generator=generator,
        )
        sample = torch.where(fix_mask, real_layout, sample)
    pixel_values = pixel_values.to(self.device)
    resolved_saliency_box = resolved_saliency_box.to(self.device)
    trajectory: list[Float[torch.Tensor, "..."]] = []
    cgb_weights: list[Float[torch.Tensor, "..."]] = []
    for index, timestep in enumerate(self.scheduler.timesteps):
        timestep_batch = torch.full(
            (batch_size,),
            int(timestep.item()),
            device=self.device,
            dtype=torch.long,
        )
        model_out = self.model(
            sample, pixel_values, resolved_saliency_box, timestep_batch
        )
        step = self.scheduler.step(
            model_out.sample,
            timestep_batch,
            sample,
            len(self.scheduler.timesteps) - index - 1,
            generator=generator,
        )
        sample = step.prev_sample
        if real_layout is not None and fix_mask is not None:
            sample = torch.where(fix_mask, real_layout, sample)
        if return_intermediates:
            trajectory.append(sample.detach().cpu())
            if model_out.cgb_weight is not None:
                cgb_weights.append(model_out.cgb_weight.detach().cpu())
    intermediates = None
    if return_intermediates:
        intermediates = {
            "condition_type": str(canonical),
            "saliency_box": resolved_saliency_box.detach().cpu(),
            "cgb_weight": cgb_weights[-1] if cgb_weights else None,
        }
    return self._decode(sample, out_type, trajectory, intermediates)

normalize_condition_type

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

Normalize CGB-DM condition aliases.

Parameters:

Name Type Description Default
condition_type ConditionType | str | None

Canonical condition enum, alias, or None.

required

Returns:

Type Description
ConditionType

Canonical condition enum.

Raises:

Type Description
ValueError

If the condition is unsupported.

Examples:

>>> str(normalize_condition_type("uncond"))
'content_image'
Source code in models/cgb-dm/src/cgb_dm/pipeline_cgb_dm.py
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
def normalize_condition_type(
    condition_type: ConditionType | str | None,
) -> ConditionType:
    """Normalize CGB-DM condition aliases.

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

    Returns:
        Canonical condition enum.

    Raises:
        ValueError: If the condition is unsupported.

    Examples:
        >>> str(normalize_condition_type("uncond"))
        '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 = (
            ConditionType.content_image
            if key == "uncond"
            else normalize_shared_condition_type(condition_type)
        )
    if canonical is ConditionType.unconditional:
        raise ValueError(
            "CGB-DM requires image/content; use condition_type='content_image'"
        )

    if canonical not in _SUPPORTED_CONDITION_TYPES:
        raise ValueError(f"Unsupported CGB-DM condition_type: {condition_type}")

    return canonical

normalize_output_type

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

Normalize output container aliases.

Source code in models/cgb-dm/src/cgb_dm/pipeline_cgb_dm.py
83
84
85
86
87
88
89
90
def normalize_output_type(output_type: OutputType | str) -> OutputType:
    """Normalize output container 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_cgb_dm

Processor for CGB-DM content images and layout tensors.

CGBDMEncodedLayout

Bases: TypedDict

Encoded CGB-DM layout tensors.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
30
31
32
33
34
35
36
class CGBDMEncodedLayout(TypedDict):
    """Encoded CGB-DM layout tensors."""

    layout: Float[torch.Tensor, "batch elements channels"]
    bbox: Float[torch.Tensor, "batch elements 4"]
    labels: Int[torch.Tensor, "batch elements"]
    mask: Bool[torch.Tensor, "batch elements"]

CGBDMProcessor

Bases: ProcessorMixin

Prepare RGB/saliency inputs and decode CGB-DM layout tensors.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Poster/content dataset key.

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

Public id-to-label mapping excluding invalid/pad.

None
num_labels int

Internal class-channel count.

4
max_seq_length int

Maximum number of elements.

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

Resize target as (height, width).

(384, 256)

Examples:

>>> CGBDMProcessor().seq_dim
8
Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.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
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
class CGBDMProcessor(ProcessorMixin):
    """Prepare RGB/saliency inputs and decode CGB-DM layout tensors.

    Args:
        dataset_name: Poster/content dataset key.
        id2label: Public id-to-label mapping excluding invalid/pad.
        num_labels: Internal class-channel count.
        max_seq_length: Maximum number of elements.
        image_size: Resize target as ``(height, width)``.

    Examples:
        >>> CGBDMProcessor().seq_dim
        8
    """

    config_name = "processor_config.json"

    def __init__(
        self,
        dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
        id2label: Mapping[int, str] | Mapping[str, str] | None = None,
        num_labels: int = 4,
        max_seq_length: int = 16,
        image_size: tuple[int, int] | list[int] = (384, 256),
    ) -> None:
        """Initialize processor metadata."""
        super().__init__()
        dataset = normalize_dataset_name(dataset_name)
        labels = id2label_for_dataset(dataset)
        public_labels = {
            key: value for key, value in labels.items() if value != "INVALID"
        }
        self.dataset_name = str(dataset)
        self.id2label = {int(k): v for k, v in (id2label or public_labels).items()}
        self.label2id = {v: k for k, v in self.id2label.items()}
        self.num_labels = int(num_labels)
        self.max_seq_length = int(max_seq_length)
        self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
        self.chat_template = None

    @property
    def seq_dim(self) -> int:
        """Return the internal class-plus-box channel count."""
        return self.num_labels + 4

    def __call__(
        self,
        images: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None = None,
        *,
        saliency: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None = None,
        saliency_isnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None = None,
        saliency_basnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None = None,
        saliency_box: Float[torch.Tensor, "..."] | None = None,
        return_tensors: Literal["pt"] = "pt",
    ) -> BatchEncoding:
        """Encode image and saliency inputs into model tensors."""
        if return_tensors != "pt":
            raise ValueError("CGBDMProcessor only supports return_tensors='pt'")

        image_rows = _ensure_batch(images)
        saliency_rows = self._resolve_saliency(
            len(image_rows),
            saliency=saliency,
            saliency_isnet=saliency_isnet,
            saliency_basnet=saliency_basnet,
        )
        pixel_values = []
        boxes = []
        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)
                if sal is None
                else _to_l_tensor(sal, self.image_size)
            )
            pixel_values.append(torch.cat((rgb, sal_tensor), dim=0))
            boxes.append(_saliency_box_from_tensor(sal_tensor))
        resolved_box = (
            torch.stack(boxes)
            if saliency_box is None
            else torch.as_tensor(saliency_box, dtype=torch.float32)
        )
        if resolved_box.ndim == 1:
            resolved_box = resolved_box.reshape(1, 1, 4)
        elif resolved_box.ndim == 2:
            resolved_box = resolved_box.unsqueeze(1)
        return BatchEncoding(
            {
                "pixel_values": torch.stack(pixel_values),
                "saliency_box": 2 * (resolved_box.clamp(0.0, 1.0) - 0.5),
            }
        )

    def encode_layout(
        self,
        *,
        bbox: Float[torch.Tensor, "..."]
        | Float[np.ndarray, "..."]
        | Sequence[Sequence[Sequence[float]]]
        | Sequence[Sequence[float]]
        | Sequence[ArrayLikeInput],
        labels: Int[torch.Tensor, "..."]
        | Int[np.ndarray, "..."]
        | Sequence[Sequence[int]]
        | Sequence[int]
        | Sequence[ArrayLikeInput],
        mask: Bool[torch.Tensor, "..."]
        | Bool[np.ndarray, "..."]
        | Sequence[Sequence[bool]]
        | Sequence[bool]
        | Sequence[ArrayLikeInput]
        | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
    ) -> CGBDMEncodedLayout:
        """Encode public layout tensors into CGB-DM latent layout format."""
        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)
        layout = self.encode(bbox_t, labels_t, mask_t)
        return {
            CGBDM_LAYOUT_KEY: layout,
            CGBDM_BBOX_KEY: bbox_t,
            CGBDM_LABELS_KEY: labels_t,
            CGBDM_MASK_KEY: mask_t,
        }

    def pad(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
    ) -> tuple[
        Float[torch.Tensor, "batch elements 4"],
        Int[torch.Tensor, "batch elements"],
        Bool[torch.Tensor, "batch elements"],
    ]:
        """Pad public layout tensors to ``max_seq_length``."""
        if mask is None:
            mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
        if bbox.shape[1] > self.max_seq_length:
            raise ValueError(f"CGB-DM supports at most {self.max_seq_length} elements")

        pad_count = self.max_seq_length - bbox.shape[1]
        if pad_count:
            bbox = torch.nn.functional.pad(bbox, (0, 0, 0, pad_count))
            labels = torch.nn.functional.pad(labels, (0, pad_count))
            mask = torch.nn.functional.pad(mask, (0, pad_count))
        labels = labels.clone()
        labels[~mask] = 0
        return bbox, labels, mask

    def encode(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Encode normalized boxes and public labels into internal tensors."""
        bbox, labels, mask = self.pad(bbox, labels, mask)
        if self.dataset_name == str(DatasetName.pku_posterlayout):
            internal_labels = labels.clone().clamp_min(0) + 1
            internal_labels[~mask] = 0
        else:
            internal_labels = labels.clone().clamp_min(0)
            internal_labels[~mask] = 0
        one_hot = torch.nn.functional.one_hot(
            internal_labels.clamp(0, self.num_labels - 1),
            num_classes=self.num_labels,
        ).to(dtype=bbox.dtype, device=bbox.device)
        bbox_in = 2 * (bbox.clamp(0.0, 1.0) - 0.5)
        return torch.cat((one_hot, bbox_in), dim=-1)

    def decode(
        self,
        layout: Float[torch.Tensor, "batch elements channels"],
        *,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        scores: Float[torch.Tensor, "..."] | None = None,
        intermediates: dict[str, str | Float[torch.Tensor, "..."] | None] | None = None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | dict[int, str]
            | dict[str, str | Float[torch.Tensor, "..."] | None]
            | None,
        ]
    ):
        """Decode internal layout tensors into the public schema."""
        bbox = (layout[:, :, self.num_labels :].clamp(-1.0, 1.0) / 2 + 0.5).cpu()
        class_logits = layout[:, :, : self.num_labels]
        class_ids = class_logits.argmax(dim=-1).long().cpu()
        mask = class_ids != 0
        if self.dataset_name == str(DatasetName.pku_posterlayout):
            labels = (class_ids - 1).clamp(0, max(self.id2label)).cpu()
        else:
            labels = class_ids.clamp(0, max(self.id2label)).cpu()
        resolved_scores = (
            scores
            if scores is not None
            else class_logits.softmax(dim=-1).max(dim=-1).values
        )
        output = LayoutGenerationOutput(
            bbox=bbox,
            labels=labels,
            mask=mask,
            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 _resolve_saliency(
        self,
        batch_size: int,
        *,
        saliency: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None,
        saliency_isnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None,
        saliency_basnet: Float[torch.Tensor, "..."]
        | Image.Image
        | str
        | bytes
        | Path
        | IO[bytes]
        | Sequence[
            Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
        ]
        | None,
    ) -> list[
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes] | 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[
                    Float[torch.Tensor, "..."]
                    | Image.Image
                    | str
                    | bytes
                    | Path
                    | IO[bytes]
                    | None
                ],
                rows,
            )
        if saliency_isnet is None and saliency_basnet is None:
            return [None] * batch_size
        return cast(
            list[
                Float[torch.Tensor, "..."]
                | Image.Image
                | str
                | bytes
                | Path
                | IO[bytes]
                | None
            ],
            [
                _merge_saliency_pair(left, right, self.image_size)
                for left, right in zip(
                    _optional_batch(saliency_isnet, batch_size),
                    _optional_batch(saliency_basnet, batch_size),
                    strict=True,
                )
            ],
        )

seq_dim property

seq_dim: int

Return the internal class-plus-box channel count.

__init__

__init__(
    dataset_name: DatasetName
    | str = DatasetName.pku_posterlayout,
    id2label: Mapping[int, str]
    | Mapping[str, str]
    | None = None,
    num_labels: int = 4,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
) -> None

Initialize processor metadata.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def __init__(
    self,
    dataset_name: DatasetName | str = DatasetName.pku_posterlayout,
    id2label: Mapping[int, str] | Mapping[str, str] | None = None,
    num_labels: int = 4,
    max_seq_length: int = 16,
    image_size: tuple[int, int] | list[int] = (384, 256),
) -> None:
    """Initialize processor metadata."""
    super().__init__()
    dataset = normalize_dataset_name(dataset_name)
    labels = id2label_for_dataset(dataset)
    public_labels = {
        key: value for key, value in labels.items() if value != "INVALID"
    }
    self.dataset_name = str(dataset)
    self.id2label = {int(k): v for k, v in (id2label or public_labels).items()}
    self.label2id = {v: k for k, v in self.id2label.items()}
    self.num_labels = int(num_labels)
    self.max_seq_length = int(max_seq_length)
    self.image_size: tuple[int, int] = (int(image_size[0]), int(image_size[1]))
    self.chat_template = None

__call__

__call__(
    images: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[Tensor, "..."]
        | Image
        | str
        | bytes
        | Path
        | IO[bytes]
    ]
    | None = None,
    *,
    saliency: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[Tensor, "..."]
        | Image
        | str
        | bytes
        | Path
        | IO[bytes]
    ]
    | None = None,
    saliency_isnet: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[Tensor, "..."]
        | Image
        | str
        | bytes
        | Path
        | IO[bytes]
    ]
    | None = None,
    saliency_basnet: Float[Tensor, "..."]
    | Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[Tensor, "..."]
        | Image
        | str
        | bytes
        | Path
        | IO[bytes]
    ]
    | None = None,
    saliency_box: Float[Tensor, "..."] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding

Encode image and saliency inputs into model tensors.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
 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
def __call__(
    self,
    images: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
    ]
    | None = None,
    *,
    saliency: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
    ]
    | None = None,
    saliency_isnet: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
    ]
    | None = None,
    saliency_basnet: Float[torch.Tensor, "..."]
    | Image.Image
    | str
    | bytes
    | Path
    | IO[bytes]
    | Sequence[
        Float[torch.Tensor, "..."] | Image.Image | str | bytes | Path | IO[bytes]
    ]
    | None = None,
    saliency_box: Float[torch.Tensor, "..."] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding:
    """Encode image and saliency inputs into model tensors."""
    if return_tensors != "pt":
        raise ValueError("CGBDMProcessor only supports return_tensors='pt'")

    image_rows = _ensure_batch(images)
    saliency_rows = self._resolve_saliency(
        len(image_rows),
        saliency=saliency,
        saliency_isnet=saliency_isnet,
        saliency_basnet=saliency_basnet,
    )
    pixel_values = []
    boxes = []
    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)
            if sal is None
            else _to_l_tensor(sal, self.image_size)
        )
        pixel_values.append(torch.cat((rgb, sal_tensor), dim=0))
        boxes.append(_saliency_box_from_tensor(sal_tensor))
    resolved_box = (
        torch.stack(boxes)
        if saliency_box is None
        else torch.as_tensor(saliency_box, dtype=torch.float32)
    )
    if resolved_box.ndim == 1:
        resolved_box = resolved_box.reshape(1, 1, 4)
    elif resolved_box.ndim == 2:
        resolved_box = resolved_box.unsqueeze(1)
    return BatchEncoding(
        {
            "pixel_values": torch.stack(pixel_values),
            "saliency_box": 2 * (resolved_box.clamp(0.0, 1.0) - 0.5),
        }
    )

encode_layout

encode_layout(
    *,
    bbox: Float[Tensor, "..."]
    | Float[ndarray, "..."]
    | Sequence[Sequence[Sequence[float]]]
    | Sequence[Sequence[float]]
    | Sequence[ArrayLikeInput],
    labels: Int[Tensor, "..."]
    | Int[ndarray, "..."]
    | Sequence[Sequence[int]]
    | Sequence[int]
    | Sequence[ArrayLikeInput],
    mask: Bool[Tensor, "..."]
    | Bool[ndarray, "..."]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> CGBDMEncodedLayout

Encode public layout tensors into CGB-DM latent layout format.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
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
def encode_layout(
    self,
    *,
    bbox: Float[torch.Tensor, "..."]
    | Float[np.ndarray, "..."]
    | Sequence[Sequence[Sequence[float]]]
    | Sequence[Sequence[float]]
    | Sequence[ArrayLikeInput],
    labels: Int[torch.Tensor, "..."]
    | Int[np.ndarray, "..."]
    | Sequence[Sequence[int]]
    | Sequence[int]
    | Sequence[ArrayLikeInput],
    mask: Bool[torch.Tensor, "..."]
    | Bool[np.ndarray, "..."]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> CGBDMEncodedLayout:
    """Encode public layout tensors into CGB-DM latent layout format."""
    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)
    layout = self.encode(bbox_t, labels_t, mask_t)
    return {
        CGBDM_LAYOUT_KEY: layout,
        CGBDM_BBOX_KEY: bbox_t,
        CGBDM_LABELS_KEY: labels_t,
        CGBDM_MASK_KEY: mask_t,
    }

pad

pad(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"] | None = None,
) -> tuple[
    Float[torch.Tensor, "batch elements 4"],
    Int[torch.Tensor, "batch elements"],
    Bool[torch.Tensor, "batch elements"],
]

Pad public layout tensors to max_seq_length.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def pad(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
) -> tuple[
    Float[torch.Tensor, "batch elements 4"],
    Int[torch.Tensor, "batch elements"],
    Bool[torch.Tensor, "batch elements"],
]:
    """Pad public layout tensors to ``max_seq_length``."""
    if mask is None:
        mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
    if bbox.shape[1] > self.max_seq_length:
        raise ValueError(f"CGB-DM supports at most {self.max_seq_length} elements")

    pad_count = self.max_seq_length - bbox.shape[1]
    if pad_count:
        bbox = torch.nn.functional.pad(bbox, (0, 0, 0, pad_count))
        labels = torch.nn.functional.pad(labels, (0, pad_count))
        mask = torch.nn.functional.pad(mask, (0, pad_count))
    labels = labels.clone()
    labels[~mask] = 0
    return bbox, labels, mask

encode

encode(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"] | None = None,
) -> Float[torch.Tensor, "batch elements channels"]

Encode normalized boxes and public labels into internal tensors.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
def encode(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Encode normalized boxes and public labels into internal tensors."""
    bbox, labels, mask = self.pad(bbox, labels, mask)
    if self.dataset_name == str(DatasetName.pku_posterlayout):
        internal_labels = labels.clone().clamp_min(0) + 1
        internal_labels[~mask] = 0
    else:
        internal_labels = labels.clone().clamp_min(0)
        internal_labels[~mask] = 0
    one_hot = torch.nn.functional.one_hot(
        internal_labels.clamp(0, self.num_labels - 1),
        num_classes=self.num_labels,
    ).to(dtype=bbox.dtype, device=bbox.device)
    bbox_in = 2 * (bbox.clamp(0.0, 1.0) - 0.5)
    return torch.cat((one_hot, bbox_in), dim=-1)

decode

decode(
    layout: Float[Tensor, "batch elements channels"],
    *,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    scores: Float[Tensor, "..."] | None = None,
    intermediates: dict[
        str, str | Float[Tensor, "..."] | None
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | dict[int, str]
        | dict[str, str | Float[torch.Tensor, "..."] | None]
        | None,
    ]
)

Decode internal layout tensors into the public schema.

Source code in models/cgb-dm/src/cgb_dm/processing_cgb_dm.py
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
def decode(
    self,
    layout: Float[torch.Tensor, "batch elements channels"],
    *,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    scores: Float[torch.Tensor, "..."] | None = None,
    intermediates: dict[str, str | Float[torch.Tensor, "..."] | None] | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | dict[int, str]
        | dict[str, str | Float[torch.Tensor, "..."] | None]
        | None,
    ]
):
    """Decode internal layout tensors into the public schema."""
    bbox = (layout[:, :, self.num_labels :].clamp(-1.0, 1.0) / 2 + 0.5).cpu()
    class_logits = layout[:, :, : self.num_labels]
    class_ids = class_logits.argmax(dim=-1).long().cpu()
    mask = class_ids != 0
    if self.dataset_name == str(DatasetName.pku_posterlayout):
        labels = (class_ids - 1).clamp(0, max(self.id2label)).cpu()
    else:
        labels = class_ids.clamp(0, max(self.id2label)).cpu()
    resolved_scores = (
        scores
        if scores is not None
        else class_logits.softmax(dim=-1).max(dim=-1).values
    )
    output = LayoutGenerationOutput(
        bbox=bbox,
        labels=labels,
        mask=mask,
        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}")

scheduling_cgb_dm

Schedulers for CGB-DM training noising and DDIM sampling.

CGBDMBetaSchedule

Bases: StrEnum

Supported CGB-DM beta schedules.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
22
23
24
25
26
class CGBDMBetaSchedule(StrEnum):
    """Supported CGB-DM beta schedules."""

    cosine = auto()
    linear = auto()

CGBDMSchedulerOutput dataclass

Bases: BaseOutput

Output returned by one CGB-DM DDIM step.

Attributes:

Name Type Description
prev_sample Float[Tensor, 'batch elements channels']

Layout sample for the next denoising step.

pred_original_sample Float[Tensor, 'batch elements channels']

Estimated clean layout sample.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
29
30
31
32
33
34
35
36
37
38
39
@dataclass
class CGBDMSchedulerOutput(BaseOutput):
    """Output returned by one CGB-DM DDIM step.

    Attributes:
        prev_sample: Layout sample for the next denoising step.
        pred_original_sample: Estimated clean layout sample.
    """

    prev_sample: Float[torch.Tensor, "batch elements channels"]
    pred_original_sample: Float[torch.Tensor, "batch elements channels"]

CGBDMScheduler

Bases: SchedulerMixin, ConfigMixin

CGB-DM scheduler preserving separate training and sampling schedules.

Parameters:

Name Type Description Default
num_train_timesteps int

Number of DDPM training steps.

1000
ddim_num_steps int

Default DDIM inference step count.

100
train_beta_schedule CGBDMBetaSchedule | str

Schedule for training noising buffers.

cosine
sampling_beta_schedule CGBDMBetaSchedule | str

Schedule for DDIM sampling buffers.

linear
eta float

DDIM stochasticity.

0.0

Examples:

>>> scheduler = CGBDMScheduler(num_train_timesteps=10, ddim_num_steps=2)
>>> scheduler.ddim_timesteps.tolist()
[0, 5]
Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
 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
class CGBDMScheduler(SchedulerMixin, ConfigMixin):
    """CGB-DM scheduler preserving separate training and sampling schedules.

    Args:
        num_train_timesteps: Number of DDPM training steps.
        ddim_num_steps: Default DDIM inference step count.
        train_beta_schedule: Schedule for training noising buffers.
        sampling_beta_schedule: Schedule for DDIM sampling buffers.
        eta: DDIM stochasticity.

    Examples:
        >>> scheduler = CGBDMScheduler(num_train_timesteps=10, ddim_num_steps=2)
        >>> scheduler.ddim_timesteps.tolist()
        [0, 5]
    """

    config_name = "scheduler_config.json"
    order = 1

    @register_to_config
    def __init__(
        self,
        *,
        num_train_timesteps: int = 1000,
        ddim_num_steps: int = 100,
        train_beta_schedule: CGBDMBetaSchedule | str = CGBDMBetaSchedule.cosine,
        sampling_beta_schedule: CGBDMBetaSchedule | str = CGBDMBetaSchedule.linear,
        eta: float = 0.0,
    ) -> None:
        """Initialize noising and sampling buffers."""
        self.num_train_timesteps = int(num_train_timesteps)
        self.ddim_num_steps = int(ddim_num_steps)
        self.train_beta_schedule = str(CGBDMBetaSchedule(train_beta_schedule))
        self.sampling_beta_schedule = str(CGBDMBetaSchedule(sampling_beta_schedule))
        self.eta = float(eta)
        train_betas = make_beta_schedule(
            self.train_beta_schedule, self.num_train_timesteps
        ).float()
        train_alphas = 1.0 - train_betas
        self.train_alphas_cumprod = train_alphas.cumprod(dim=0)
        self.alphas_bar_sqrt = torch.sqrt(self.train_alphas_cumprod)
        self.one_minus_alphas_bar_sqrt = torch.sqrt(1.0 - self.train_alphas_cumprod)
        sampling_betas = make_beta_schedule(
            self.sampling_beta_schedule, self.num_train_timesteps
        ).float()
        sampling_alphas = 1.0 - sampling_betas
        self.sampling_alphas_cumprod = sampling_alphas.cumprod(dim=0)
        self.timesteps = torch.empty(0, dtype=torch.long)
        self.set_timesteps(self.ddim_num_steps)

    def set_timesteps(
        self, num_inference_steps: int | None = None, device: torch.device | None = None
    ) -> None:
        """Set DDIM timesteps and derived sampling parameters."""
        steps = int(num_inference_steps or self.ddim_num_steps)
        ddim = make_ddim_timesteps(
            num_ddim_timesteps=steps,
            num_ddpm_timesteps=self.num_train_timesteps,
        )
        self.ddim_timesteps = torch.as_tensor(ddim, dtype=torch.long, device=device)
        self.timesteps = torch.flip(self.ddim_timesteps, dims=(0,))
        alphas = self.sampling_alphas_cumprod.to(device)
        self.ddim_alphas = alphas[self.ddim_timesteps]
        self.ddim_alphas_prev = torch.as_tensor(
            [alphas[0].item()] + alphas[self.ddim_timesteps[:-1]].tolist(),
            dtype=torch.float32,
            device=device,
        )
        self.ddim_sigmas = self.eta * torch.sqrt(
            (1 - self.ddim_alphas_prev)
            / (1 - self.ddim_alphas)
            * (1 - self.ddim_alphas / self.ddim_alphas_prev)
        )
        self.ddim_sqrt_one_minus_alphas = torch.sqrt(1.0 - self.ddim_alphas)

    def sample_timesteps(
        self,
        batch_size: int,
        *,
        device: torch.device,
        generator: torch.Generator | None = None,
        t_max: int | None = None,
    ) -> Int[torch.Tensor, "batch"]:
        """Sample training timesteps."""
        high = int(t_max or self.num_train_timesteps - 1)
        return torch.randint(0, high, (batch_size,), device=device, generator=generator)

    def add_noise(
        self,
        original_samples: Float[torch.Tensor, "batch elements channels"],
        noise: Float[torch.Tensor, "batch elements channels"],
        timesteps: Int[torch.Tensor, "batch"],
        *,
        fix_mask: Bool[torch.Tensor, "batch elements channels"] | None = None,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Add training noise, preserving fixed channels when requested."""
        alphas = self.alphas_bar_sqrt.to(original_samples.device)
        one_minus = self.one_minus_alphas_bar_sqrt.to(original_samples.device)
        sqrt_alpha = torch.gather(alphas, 0, timesteps).reshape(-1, 1, 1)
        sqrt_one_minus = torch.gather(one_minus, 0, timesteps).reshape(-1, 1, 1)
        noised = sqrt_alpha * original_samples + sqrt_one_minus * noise
        if fix_mask is None:
            return noised
        return torch.where(fix_mask, original_samples, noised)

    def initial_sample(
        self,
        batch_size: int,
        seq_len: int,
        seq_dim: int,
        *,
        device: torch.device,
        generator: torch.Generator | None = None,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Create the initial DDIM sample."""
        return torch.randn(
            batch_size, seq_len, seq_dim, device=device, generator=generator
        )

    def condition_mask(
        self,
        layout: Float[torch.Tensor, "batch elements channels"],
        condition_type: ConditionType,
        *,
        completion_ratio: float = 0.2,
        generator: torch.Generator | None = None,
    ) -> Bool[torch.Tensor, "batch elements channels"]:
        """Build a channel-level mask for fixed conditioning values."""
        mask = torch.zeros_like(layout, dtype=torch.bool)
        num_labels = layout.shape[-1] - 4
        if condition_type is ConditionType.content_image:
            return mask

        if condition_type is ConditionType.label:
            mask[:, :, :num_labels] = True
            return mask

        if condition_type is ConditionType.label_size:
            mask[:, :, :num_labels] = True
            mask[:, :, num_labels + 2 : num_labels + 4] = True
            return mask

        if condition_type is ConditionType.completion:
            label_ids = layout[:, :, :num_labels].argmax(dim=-1)
            valid = label_ids != 0
            rand = torch.rand(valid.shape, device=layout.device, generator=generator)
            elem_mask = (rand <= completion_ratio) & valid
            return elem_mask.unsqueeze(-1).expand_as(layout)

        if condition_type is ConditionType.refinement:
            return torch.ones_like(mask)
        raise ValueError(f"Unsupported CGB-DM condition_type: {condition_type}")

    def step(
        self,
        model_output: Float[torch.Tensor, "batch elements channels"],
        timestep: Int[torch.Tensor, "batch"],
        sample: Float[torch.Tensor, "batch elements channels"],
        index: int,
        generator: torch.Generator | None = None,
    ) -> CGBDMSchedulerOutput:
        """Take one DDIM reverse step."""
        del timestep
        alpha_t = self.ddim_alphas[index].to(sample.device)
        alpha_prev = self.ddim_alphas_prev[index].to(sample.device)
        sigma_t = self.ddim_sigmas[index].to(sample.device)
        sqrt_one_minus = self.ddim_sqrt_one_minus_alphas[index].to(sample.device)
        pred_original = (sample - sqrt_one_minus * model_output) / alpha_t.sqrt()
        direction = (1.0 - alpha_prev - sigma_t**2).sqrt() * model_output
        noise = sigma_t * torch.randn(
            sample.shape,
            dtype=sample.dtype,
            device=sample.device,
            generator=generator,
        )
        prev_sample = alpha_prev.sqrt() * pred_original + direction + noise
        return CGBDMSchedulerOutput(
            prev_sample=prev_sample,
            pred_original_sample=pred_original,
        )

__init__

__init__(
    *,
    num_train_timesteps: int = 1000,
    ddim_num_steps: int = 100,
    train_beta_schedule: CGBDMBetaSchedule
    | str = CGBDMBetaSchedule.cosine,
    sampling_beta_schedule: CGBDMBetaSchedule
    | str = CGBDMBetaSchedule.linear,
    eta: float = 0.0,
) -> None

Initialize noising and sampling buffers.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
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
@register_to_config
def __init__(
    self,
    *,
    num_train_timesteps: int = 1000,
    ddim_num_steps: int = 100,
    train_beta_schedule: CGBDMBetaSchedule | str = CGBDMBetaSchedule.cosine,
    sampling_beta_schedule: CGBDMBetaSchedule | str = CGBDMBetaSchedule.linear,
    eta: float = 0.0,
) -> None:
    """Initialize noising and sampling buffers."""
    self.num_train_timesteps = int(num_train_timesteps)
    self.ddim_num_steps = int(ddim_num_steps)
    self.train_beta_schedule = str(CGBDMBetaSchedule(train_beta_schedule))
    self.sampling_beta_schedule = str(CGBDMBetaSchedule(sampling_beta_schedule))
    self.eta = float(eta)
    train_betas = make_beta_schedule(
        self.train_beta_schedule, self.num_train_timesteps
    ).float()
    train_alphas = 1.0 - train_betas
    self.train_alphas_cumprod = train_alphas.cumprod(dim=0)
    self.alphas_bar_sqrt = torch.sqrt(self.train_alphas_cumprod)
    self.one_minus_alphas_bar_sqrt = torch.sqrt(1.0 - self.train_alphas_cumprod)
    sampling_betas = make_beta_schedule(
        self.sampling_beta_schedule, self.num_train_timesteps
    ).float()
    sampling_alphas = 1.0 - sampling_betas
    self.sampling_alphas_cumprod = sampling_alphas.cumprod(dim=0)
    self.timesteps = torch.empty(0, dtype=torch.long)
    self.set_timesteps(self.ddim_num_steps)

set_timesteps

set_timesteps(
    num_inference_steps: int | None = None,
    device: device | None = None,
) -> None

Set DDIM timesteps and derived sampling parameters.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def set_timesteps(
    self, num_inference_steps: int | None = None, device: torch.device | None = None
) -> None:
    """Set DDIM timesteps and derived sampling parameters."""
    steps = int(num_inference_steps or self.ddim_num_steps)
    ddim = make_ddim_timesteps(
        num_ddim_timesteps=steps,
        num_ddpm_timesteps=self.num_train_timesteps,
    )
    self.ddim_timesteps = torch.as_tensor(ddim, dtype=torch.long, device=device)
    self.timesteps = torch.flip(self.ddim_timesteps, dims=(0,))
    alphas = self.sampling_alphas_cumprod.to(device)
    self.ddim_alphas = alphas[self.ddim_timesteps]
    self.ddim_alphas_prev = torch.as_tensor(
        [alphas[0].item()] + alphas[self.ddim_timesteps[:-1]].tolist(),
        dtype=torch.float32,
        device=device,
    )
    self.ddim_sigmas = self.eta * torch.sqrt(
        (1 - self.ddim_alphas_prev)
        / (1 - self.ddim_alphas)
        * (1 - self.ddim_alphas / self.ddim_alphas_prev)
    )
    self.ddim_sqrt_one_minus_alphas = torch.sqrt(1.0 - self.ddim_alphas)

sample_timesteps

sample_timesteps(
    batch_size: int,
    *,
    device: device,
    generator: Generator | None = None,
    t_max: int | None = None,
) -> Int[torch.Tensor, "batch"]

Sample training timesteps.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
168
169
170
171
172
173
174
175
176
177
178
def sample_timesteps(
    self,
    batch_size: int,
    *,
    device: torch.device,
    generator: torch.Generator | None = None,
    t_max: int | None = None,
) -> Int[torch.Tensor, "batch"]:
    """Sample training timesteps."""
    high = int(t_max or self.num_train_timesteps - 1)
    return torch.randint(0, high, (batch_size,), device=device, generator=generator)

add_noise

add_noise(
    original_samples: Float[
        Tensor, "batch elements channels"
    ],
    noise: Float[Tensor, "batch elements channels"],
    timesteps: Int[Tensor, "batch"],
    *,
    fix_mask: Bool[Tensor, "batch elements channels"]
    | None = None,
) -> Float[torch.Tensor, "batch elements channels"]

Add training noise, preserving fixed channels when requested.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
def add_noise(
    self,
    original_samples: Float[torch.Tensor, "batch elements channels"],
    noise: Float[torch.Tensor, "batch elements channels"],
    timesteps: Int[torch.Tensor, "batch"],
    *,
    fix_mask: Bool[torch.Tensor, "batch elements channels"] | None = None,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Add training noise, preserving fixed channels when requested."""
    alphas = self.alphas_bar_sqrt.to(original_samples.device)
    one_minus = self.one_minus_alphas_bar_sqrt.to(original_samples.device)
    sqrt_alpha = torch.gather(alphas, 0, timesteps).reshape(-1, 1, 1)
    sqrt_one_minus = torch.gather(one_minus, 0, timesteps).reshape(-1, 1, 1)
    noised = sqrt_alpha * original_samples + sqrt_one_minus * noise
    if fix_mask is None:
        return noised
    return torch.where(fix_mask, original_samples, noised)

initial_sample

initial_sample(
    batch_size: int,
    seq_len: int,
    seq_dim: int,
    *,
    device: device,
    generator: Generator | None = None,
) -> Float[torch.Tensor, "batch elements channels"]

Create the initial DDIM sample.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
198
199
200
201
202
203
204
205
206
207
208
209
210
def initial_sample(
    self,
    batch_size: int,
    seq_len: int,
    seq_dim: int,
    *,
    device: torch.device,
    generator: torch.Generator | None = None,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Create the initial DDIM sample."""
    return torch.randn(
        batch_size, seq_len, seq_dim, device=device, generator=generator
    )

condition_mask

condition_mask(
    layout: Float[Tensor, "batch elements channels"],
    condition_type: ConditionType,
    *,
    completion_ratio: float = 0.2,
    generator: Generator | None = None,
) -> Bool[torch.Tensor, "batch elements channels"]

Build a channel-level mask for fixed conditioning values.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
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
def condition_mask(
    self,
    layout: Float[torch.Tensor, "batch elements channels"],
    condition_type: ConditionType,
    *,
    completion_ratio: float = 0.2,
    generator: torch.Generator | None = None,
) -> Bool[torch.Tensor, "batch elements channels"]:
    """Build a channel-level mask for fixed conditioning values."""
    mask = torch.zeros_like(layout, dtype=torch.bool)
    num_labels = layout.shape[-1] - 4
    if condition_type is ConditionType.content_image:
        return mask

    if condition_type is ConditionType.label:
        mask[:, :, :num_labels] = True
        return mask

    if condition_type is ConditionType.label_size:
        mask[:, :, :num_labels] = True
        mask[:, :, num_labels + 2 : num_labels + 4] = True
        return mask

    if condition_type is ConditionType.completion:
        label_ids = layout[:, :, :num_labels].argmax(dim=-1)
        valid = label_ids != 0
        rand = torch.rand(valid.shape, device=layout.device, generator=generator)
        elem_mask = (rand <= completion_ratio) & valid
        return elem_mask.unsqueeze(-1).expand_as(layout)

    if condition_type is ConditionType.refinement:
        return torch.ones_like(mask)
    raise ValueError(f"Unsupported CGB-DM condition_type: {condition_type}")

step

step(
    model_output: Float[Tensor, "batch elements channels"],
    timestep: Int[Tensor, "batch"],
    sample: Float[Tensor, "batch elements channels"],
    index: int,
    generator: Generator | None = None,
) -> CGBDMSchedulerOutput

Take one DDIM reverse step.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
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
def step(
    self,
    model_output: Float[torch.Tensor, "batch elements channels"],
    timestep: Int[torch.Tensor, "batch"],
    sample: Float[torch.Tensor, "batch elements channels"],
    index: int,
    generator: torch.Generator | None = None,
) -> CGBDMSchedulerOutput:
    """Take one DDIM reverse step."""
    del timestep
    alpha_t = self.ddim_alphas[index].to(sample.device)
    alpha_prev = self.ddim_alphas_prev[index].to(sample.device)
    sigma_t = self.ddim_sigmas[index].to(sample.device)
    sqrt_one_minus = self.ddim_sqrt_one_minus_alphas[index].to(sample.device)
    pred_original = (sample - sqrt_one_minus * model_output) / alpha_t.sqrt()
    direction = (1.0 - alpha_prev - sigma_t**2).sqrt() * model_output
    noise = sigma_t * torch.randn(
        sample.shape,
        dtype=sample.dtype,
        device=sample.device,
        generator=generator,
    )
    prev_sample = alpha_prev.sqrt() * pred_original + direction + noise
    return CGBDMSchedulerOutput(
        prev_sample=prev_sample,
        pred_original_sample=pred_original,
    )

make_beta_schedule

make_beta_schedule(
    schedule: CGBDMBetaSchedule | str,
    num_timesteps: int,
    *,
    start: float = 0.0002,
    end: float = 0.04,
) -> Float[torch.Tensor, "timesteps"]

Create a CGB-DM beta schedule.

Parameters:

Name Type Description Default
schedule CGBDMBetaSchedule | str

Schedule name.

required
num_timesteps int

Number of diffusion timesteps.

required
start float

Linear schedule start.

0.0002
end float

Linear schedule end.

0.04

Returns:

Type Description
Float[Tensor, 'timesteps']

Beta tensor.

Raises:

Type Description
ValueError

If the schedule is unsupported.

Examples:

>>> make_beta_schedule("linear", 4).shape
torch.Size([4])
Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.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 make_beta_schedule(
    schedule: CGBDMBetaSchedule | str,
    num_timesteps: int,
    *,
    start: float = 2.0e-4,
    end: float = 4.0e-2,
) -> Float[torch.Tensor, "timesteps"]:
    """Create a CGB-DM beta schedule.

    Args:
        schedule: Schedule name.
        num_timesteps: Number of diffusion timesteps.
        start: Linear schedule start.
        end: Linear schedule end.

    Returns:
        Beta tensor.

    Raises:
        ValueError: If the schedule is unsupported.

    Examples:
        >>> make_beta_schedule("linear", 4).shape
        torch.Size([4])
    """
    canonical = CGBDMBetaSchedule(schedule)
    if canonical in {CGBDMBetaSchedule.linear, CGBDMBetaSchedule.cosine}:
        return make_continuous_beta_schedule(
            str(canonical),
            num_timesteps=num_timesteps,
            start=start,
            end=end,
        ).float()
    raise ValueError(f"Unsupported beta schedule: {schedule}")

make_ddim_timesteps

make_ddim_timesteps(
    *,
    num_ddim_timesteps: int,
    num_ddpm_timesteps: int,
    mode: Literal["uniform", "refine"] = "uniform",
) -> Int[np.ndarray, "timesteps"]

Create DDIM timestep ids using CGB-DM discretization rules.

Source code in models/cgb-dm/src/cgb_dm/scheduling_cgb_dm.py
78
79
80
81
82
83
84
85
86
87
88
89
90
def make_ddim_timesteps(
    *,
    num_ddim_timesteps: int,
    num_ddpm_timesteps: int,
    mode: Literal["uniform", "refine"] = "uniform",
) -> Int[np.ndarray, "timesteps"]:
    """Create DDIM timestep ids using CGB-DM discretization rules."""
    if mode == "uniform":
        stride = num_ddpm_timesteps // num_ddim_timesteps
        return np.asarray(list(range(0, num_ddpm_timesteps, stride)))
    if mode == "refine":
        return np.asarray(list(range(0, num_ddpm_timesteps, 2)))
    raise ValueError(f"Unsupported DDIM mode: {mode}")

training

Training utilities for CGB-DM.

config

Training configuration literals for CGB-DM.

datamodule

PyTorch Lightning data module for CGB-DM training.

CGBDMDataModule

Bases: LightningDataModule

Data module for original-zip or synthetic CGB-DM rows.

Source code in models/cgb-dm/src/cgb_dm/training/datamodule.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
class CGBDMDataModule(LightningDataModule):
    """Data module for original-zip or synthetic CGB-DM rows."""

    def __init__(
        self,
        *,
        config: CGBDMConfig
        | dict[
            str,
            str
            | int
            | tuple[int, int]
            | list[int]
            | list[str]
            | dict[int | str, str]
            | None,
        ],
        source: CGBDMDataSource = "synthetic",
        data_root: str | None = None,
        batch_size: int = 2,
        num_workers: int = 0,
        source_order_manifest: str | None = None,
        original_encoding: Literal["public", "reference"] = "reference",
    ) -> None:
        """Initialize data module options."""
        super().__init__()
        self.config = (
            config if isinstance(config, CGBDMConfig) else CGBDMConfig(**config)
        )
        self.source = source
        self.data_root = data_root
        self.batch_size = batch_size
        self.num_workers = num_workers
        self.source_order_manifest = source_order_manifest
        self.original_encoding = original_encoding

    def setup(self, stage: str | None = None) -> None:
        """Create train and validation datasets."""
        del stage
        if self.source == "original_zip":
            if self.data_root is None:
                raise ValueError("data_root is required for source='original_zip'")

            processor = CGBDMProcessor(
                dataset_name=self.config.dataset_name,
                id2label=self.config.id2label,
                num_labels=self.config.num_labels,
                max_seq_length=self.config.max_seq_length,
                image_size=self.config.image_size,
            )
            self.train_dataset = CGBDMOriginalDataset(
                self.data_root,
                split="train",
                processor=processor,
                name_manifest=self.source_order_manifest,
                encoding=self.original_encoding,
            )
            self.val_dataset = CGBDMOriginalDataset(
                self.data_root,
                split="val",
                processor=processor,
                encoding=self.original_encoding,
            )
            return
        if self.source != "synthetic":
            raise ValueError(f"Unsupported CGB-DM data source: {self.source}")

        kwargs: _CGBDMSyntheticDatasetKwargs = {
            "max_seq_length": self.config.max_seq_length,
            "seq_dim": self.config.seq_dim,
            "image_size": self.config.image_size,
        }
        self.train_dataset = CGBDMSyntheticDataset(**kwargs)
        self.val_dataset = CGBDMSyntheticDataset(length=2, **kwargs)

    def train_dataloader(self) -> DataLoader[dict[str, Float[torch.Tensor, "..."]]]:
        """Return the training dataloader."""
        return DataLoader(
            self.train_dataset,
            batch_size=self.batch_size,
            num_workers=self.num_workers,
            shuffle=True,
        )

    def val_dataloader(self) -> DataLoader[dict[str, Float[torch.Tensor, "..."]]]:
        """Return the validation dataloader."""
        return DataLoader(
            self.val_dataset,
            batch_size=self.batch_size,
            num_workers=self.num_workers,
        )
__init__
__init__(
    *,
    config: CGBDMConfig
    | dict[
        str,
        str
        | int
        | tuple[int, int]
        | list[int]
        | list[str]
        | dict[int | str, str]
        | None,
    ],
    source: CGBDMDataSource = "synthetic",
    data_root: str | None = None,
    batch_size: int = 2,
    num_workers: int = 0,
    source_order_manifest: str | None = None,
    original_encoding: Literal[
        "public", "reference"
    ] = "reference",
) -> None

Initialize data module options.

Source code in models/cgb-dm/src/cgb_dm/training/datamodule.py
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
def __init__(
    self,
    *,
    config: CGBDMConfig
    | dict[
        str,
        str
        | int
        | tuple[int, int]
        | list[int]
        | list[str]
        | dict[int | str, str]
        | None,
    ],
    source: CGBDMDataSource = "synthetic",
    data_root: str | None = None,
    batch_size: int = 2,
    num_workers: int = 0,
    source_order_manifest: str | None = None,
    original_encoding: Literal["public", "reference"] = "reference",
) -> None:
    """Initialize data module options."""
    super().__init__()
    self.config = (
        config if isinstance(config, CGBDMConfig) else CGBDMConfig(**config)
    )
    self.source = source
    self.data_root = data_root
    self.batch_size = batch_size
    self.num_workers = num_workers
    self.source_order_manifest = source_order_manifest
    self.original_encoding = original_encoding
setup
setup(stage: str | None = None) -> None

Create train and validation datasets.

Source code in models/cgb-dm/src/cgb_dm/training/datamodule.py
 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
def setup(self, stage: str | None = None) -> None:
    """Create train and validation datasets."""
    del stage
    if self.source == "original_zip":
        if self.data_root is None:
            raise ValueError("data_root is required for source='original_zip'")

        processor = CGBDMProcessor(
            dataset_name=self.config.dataset_name,
            id2label=self.config.id2label,
            num_labels=self.config.num_labels,
            max_seq_length=self.config.max_seq_length,
            image_size=self.config.image_size,
        )
        self.train_dataset = CGBDMOriginalDataset(
            self.data_root,
            split="train",
            processor=processor,
            name_manifest=self.source_order_manifest,
            encoding=self.original_encoding,
        )
        self.val_dataset = CGBDMOriginalDataset(
            self.data_root,
            split="val",
            processor=processor,
            encoding=self.original_encoding,
        )
        return
    if self.source != "synthetic":
        raise ValueError(f"Unsupported CGB-DM data source: {self.source}")

    kwargs: _CGBDMSyntheticDatasetKwargs = {
        "max_seq_length": self.config.max_seq_length,
        "seq_dim": self.config.seq_dim,
        "image_size": self.config.image_size,
    }
    self.train_dataset = CGBDMSyntheticDataset(**kwargs)
    self.val_dataset = CGBDMSyntheticDataset(length=2, **kwargs)
train_dataloader
train_dataloader() -> DataLoader[
    dict[str, Float[torch.Tensor, "..."]]
]

Return the training dataloader.

Source code in models/cgb-dm/src/cgb_dm/training/datamodule.py
102
103
104
105
106
107
108
109
def train_dataloader(self) -> DataLoader[dict[str, Float[torch.Tensor, "..."]]]:
    """Return the training dataloader."""
    return DataLoader(
        self.train_dataset,
        batch_size=self.batch_size,
        num_workers=self.num_workers,
        shuffle=True,
    )
val_dataloader
val_dataloader() -> DataLoader[
    dict[str, Float[torch.Tensor, "..."]]
]

Return the validation dataloader.

Source code in models/cgb-dm/src/cgb_dm/training/datamodule.py
111
112
113
114
115
116
117
def val_dataloader(self) -> DataLoader[dict[str, Float[torch.Tensor, "..."]]]:
    """Return the validation dataloader."""
    return DataLoader(
        self.val_dataset,
        batch_size=self.batch_size,
        num_workers=self.num_workers,
    )

dataset

Training datasets for CGB-DM.

CGBDMOriginalDataset

Bases: Dataset[dict[str, Float[Tensor, '...']]]

Read an extracted CGB-DM dataset split without downloading assets.

Parameters:

Name Type Description Default
root str | Path

Extracted dataset root.

required
split Literal['train', 'val', 'test']

Dataset split name.

'train'
processor CGBDMProcessor | None

Processor used for image/layout normalization.

None

Examples:

>>> CGBDMDataPaths(Path("/tmp/data")).annotation_csv.name
'train.csv'
Source code in models/cgb-dm/src/cgb_dm/data.py
 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
class CGBDMOriginalDataset(Dataset[dict[str, Float[torch.Tensor, "..."]]]):
    """Read an extracted CGB-DM dataset split without downloading assets.

    Args:
        root: Extracted dataset root.
        split: Dataset split name.
        processor: Processor used for image/layout normalization.

    Examples:
        >>> CGBDMDataPaths(Path("/tmp/data")).annotation_csv.name
        'train.csv'
    """

    def __init__(
        self,
        root: str | Path,
        *,
        split: Literal["train", "val", "test"] = "train",
        processor: CGBDMProcessor | None = None,
        name_manifest: str | Path | list[str] | tuple[str, ...] | None = None,
        encoding: Literal["public", "reference"] = "public",
    ) -> None:
        """Initialize file lists and CSV indexes."""
        self.paths = CGBDMDataPaths(Path(root), split)
        self.processor = processor or CGBDMProcessor()
        self.names = _load_names(self.paths.inpaint_dir, name_manifest)
        self.encoding = encoding
        self.annotations = _read_grouped_boxes(self.paths.annotation_csv)
        self.saliency_boxes = _read_grouped_boxes(self.paths.saliency_csv)

    def __len__(self) -> int:
        """Return number of image rows."""
        return len(self.names)

    def __getitem__(self, index: int) -> dict[str, Float[torch.Tensor, "..."]]:
        """Return one normalized CGB-DM training row."""
        name = self.names[index]
        image_path = self.paths.inpaint_dir / name
        image = Image.open(image_path).convert("RGB")
        width, height = image.size
        saliency = Image.open(self.paths.saliency_dir / name).convert("L")
        saliency_sub = Image.open(self.paths.saliency_sub_dir / name).convert("L")
        if self.encoding == "reference":
            return _encode_reference_row(
                image=image,
                saliency=saliency,
                saliency_sub=saliency_sub,
                annotations=self.annotations[name],
                saliency_box=self.saliency_boxes[name][0],
                width=width,
                height=height,
                max_seq_length=self.processor.max_seq_length,
                num_labels=self.processor.num_labels,
                image_size=self.processor.image_size,
            )
        if self.encoding != "public":
            raise ValueError(f"Unsupported CGB-DM dataset encoding: {self.encoding}")

        content = self.processor(
            image,
            saliency_isnet=saliency,
            saliency_basnet=saliency_sub,
            saliency_box=_normalize_ltrb(self.saliency_boxes[name][0], width, height),
        )
        boxes, labels = zip(*self.annotations[name], strict=False)
        public_labels = (
            [label - 1 for label in labels]
            if self.processor.dataset_name == "pku_posterlayout"
            else list(labels)
        )
        layout = self.processor.encode_layout(
            bbox=[[_normalize_ltrb(box, width, height).tolist() for box in boxes]],
            labels=[public_labels],
        )["layout"][0]
        return {
            "pixel_values": content["pixel_values"][0],
            "layout": layout,
            "saliency_box": content["saliency_box"][0],
        }
__init__
__init__(
    root: str | Path,
    *,
    split: Literal["train", "val", "test"] = "train",
    processor: CGBDMProcessor | None = None,
    name_manifest: str
    | Path
    | list[str]
    | tuple[str, ...]
    | None = None,
    encoding: Literal["public", "reference"] = "public",
) -> None

Initialize file lists and CSV indexes.

Source code in models/cgb-dm/src/cgb_dm/data.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def __init__(
    self,
    root: str | Path,
    *,
    split: Literal["train", "val", "test"] = "train",
    processor: CGBDMProcessor | None = None,
    name_manifest: str | Path | list[str] | tuple[str, ...] | None = None,
    encoding: Literal["public", "reference"] = "public",
) -> None:
    """Initialize file lists and CSV indexes."""
    self.paths = CGBDMDataPaths(Path(root), split)
    self.processor = processor or CGBDMProcessor()
    self.names = _load_names(self.paths.inpaint_dir, name_manifest)
    self.encoding = encoding
    self.annotations = _read_grouped_boxes(self.paths.annotation_csv)
    self.saliency_boxes = _read_grouped_boxes(self.paths.saliency_csv)
__len__
__len__() -> int

Return number of image rows.

Source code in models/cgb-dm/src/cgb_dm/data.py
86
87
88
def __len__(self) -> int:
    """Return number of image rows."""
    return len(self.names)
__getitem__
__getitem__(
    index: int,
) -> dict[str, Float[torch.Tensor, "..."]]

Return one normalized CGB-DM training row.

Source code in models/cgb-dm/src/cgb_dm/data.py
 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
def __getitem__(self, index: int) -> dict[str, Float[torch.Tensor, "..."]]:
    """Return one normalized CGB-DM training row."""
    name = self.names[index]
    image_path = self.paths.inpaint_dir / name
    image = Image.open(image_path).convert("RGB")
    width, height = image.size
    saliency = Image.open(self.paths.saliency_dir / name).convert("L")
    saliency_sub = Image.open(self.paths.saliency_sub_dir / name).convert("L")
    if self.encoding == "reference":
        return _encode_reference_row(
            image=image,
            saliency=saliency,
            saliency_sub=saliency_sub,
            annotations=self.annotations[name],
            saliency_box=self.saliency_boxes[name][0],
            width=width,
            height=height,
            max_seq_length=self.processor.max_seq_length,
            num_labels=self.processor.num_labels,
            image_size=self.processor.image_size,
        )
    if self.encoding != "public":
        raise ValueError(f"Unsupported CGB-DM dataset encoding: {self.encoding}")

    content = self.processor(
        image,
        saliency_isnet=saliency,
        saliency_basnet=saliency_sub,
        saliency_box=_normalize_ltrb(self.saliency_boxes[name][0], width, height),
    )
    boxes, labels = zip(*self.annotations[name], strict=False)
    public_labels = (
        [label - 1 for label in labels]
        if self.processor.dataset_name == "pku_posterlayout"
        else list(labels)
    )
    layout = self.processor.encode_layout(
        bbox=[[_normalize_ltrb(box, width, height).tolist() for box in boxes]],
        labels=[public_labels],
    )["layout"][0]
    return {
        "pixel_values": content["pixel_values"][0],
        "layout": layout,
        "saliency_box": content["saliency_box"][0],
    }

CGBDMSyntheticDataset

Bases: Dataset[dict[str, Float[Tensor, '...']]]

Tiny deterministic dataset used by tests and smoke configs.

Source code in models/cgb-dm/src/cgb_dm/training/dataset.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
class CGBDMSyntheticDataset(Dataset[dict[str, Float[torch.Tensor, "..."]]]):
    """Tiny deterministic dataset used by tests and smoke configs."""

    def __init__(
        self,
        *,
        length: int = 4,
        max_seq_length: int = 4,
        seq_dim: int = 8,
        image_size: tuple[int, int] = (32, 32),
    ) -> None:
        """Initialize synthetic tensor shapes."""
        self.length = length
        self.max_seq_length = max_seq_length
        self.seq_dim = seq_dim
        self.image_size = image_size

    def __len__(self) -> int:
        """Return dataset length."""
        return self.length

    def __getitem__(self, index: int) -> dict[str, Float[torch.Tensor, "..."]]:
        """Return one deterministic row."""
        generator = torch.Generator().manual_seed(index)
        labels = torch.zeros(self.max_seq_length, self.seq_dim - 4)
        labels[:, 0] = 1
        bbox = torch.rand(self.max_seq_length, 4, generator=generator) * 2 - 1
        return {
            "pixel_values": torch.rand(4, *self.image_size, generator=generator) * 2
            - 1,
            "layout": torch.cat((labels, bbox), dim=-1),
            "saliency_box": torch.zeros(1, 4),
        }
__init__
__init__(
    *,
    length: int = 4,
    max_seq_length: int = 4,
    seq_dim: int = 8,
    image_size: tuple[int, int] = (32, 32),
) -> None

Initialize synthetic tensor shapes.

Source code in models/cgb-dm/src/cgb_dm/training/dataset.py
15
16
17
18
19
20
21
22
23
24
25
26
27
def __init__(
    self,
    *,
    length: int = 4,
    max_seq_length: int = 4,
    seq_dim: int = 8,
    image_size: tuple[int, int] = (32, 32),
) -> None:
    """Initialize synthetic tensor shapes."""
    self.length = length
    self.max_seq_length = max_seq_length
    self.seq_dim = seq_dim
    self.image_size = image_size
__len__
__len__() -> int

Return dataset length.

Source code in models/cgb-dm/src/cgb_dm/training/dataset.py
29
30
31
def __len__(self) -> int:
    """Return dataset length."""
    return self.length
__getitem__
__getitem__(
    index: int,
) -> dict[str, Float[torch.Tensor, "..."]]

Return one deterministic row.

Source code in models/cgb-dm/src/cgb_dm/training/dataset.py
33
34
35
36
37
38
39
40
41
42
43
44
def __getitem__(self, index: int) -> dict[str, Float[torch.Tensor, "..."]]:
    """Return one deterministic row."""
    generator = torch.Generator().manual_seed(index)
    labels = torch.zeros(self.max_seq_length, self.seq_dim - 4)
    labels[:, 0] = 1
    bbox = torch.rand(self.max_seq_length, 4, generator=generator) * 2 - 1
    return {
        "pixel_values": torch.rand(4, *self.image_size, generator=generator) * 2
        - 1,
        "layout": torch.cat((labels, bbox), dim=-1),
        "saliency_box": torch.zeros(1, 4),
    }

lightning_module

PyTorch Lightning module for CGB-DM training.

CGBDMTrainingModule

Bases: LightningModule

Training wrapper that mirrors CGB-DM denoising-step order.

Source code in models/cgb-dm/src/cgb_dm/training/lightning_module.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 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
class CGBDMTrainingModule(LightningModule):
    """Training wrapper that mirrors CGB-DM denoising-step order."""

    def __init__(
        self,
        *,
        config: CGBDMConfig
        | dict[
            str,
            str
            | int
            | tuple[int, int]
            | list[int]
            | list[str]
            | dict[int | str, str]
            | None,
        ],
        optimizer: OptimizerCallable | None = None,
        lr_scheduler: LRSchedulerCallable | None = None,
        model: CGBDMTransformerModel | None = None,
        condition_type: CGBDMCondition = "content_image",
        seed_mode: CGBDMSeedMode = "default",
    ) -> None:
        """Initialize model, scheduler, and optimizer settings."""
        super().__init__()
        self.config_obj = (
            config if isinstance(config, CGBDMConfig) else CGBDMConfig(**config)
        )
        self.model = model or CGBDMTransformerModel(
            num_labels=self.config_obj.num_labels,
            max_seq_length=self.config_obj.max_seq_length,
            image_size=self.config_obj.image_size,
            dim_model=self.config_obj.dim_model,
            n_head=self.config_obj.n_head,
            feature_dim=self.config_obj.feature_dim,
            num_layers=self.config_obj.num_layers,
            num_train_timesteps=self.config_obj.num_train_timesteps,
        )
        self.scheduler = CGBDMScheduler(
            num_train_timesteps=self.config_obj.num_train_timesteps,
            ddim_num_steps=self.config_obj.ddim_num_steps,
            train_beta_schedule=self.config_obj.train_beta_schedule,
            sampling_beta_schedule=self.config_obj.sampling_beta_schedule,
        )
        self.optimizer = optimizer
        self.lr_scheduler = lr_scheduler
        self.condition_type = ConditionType(condition_type)
        self.seed_mode = seed_mode
        self.latest_step_trace: dict[str, Float[torch.Tensor, "..."]] = {}

    def forward(
        self,
        sample: Float[torch.Tensor, "batch elements channels"],
        image: Float[torch.Tensor, "batch channels height width"],
        saliency_box: Float[torch.Tensor, "batch 1 4"],
        timestep: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Predict epsilon for a training sample."""
        return self.model(sample, image, saliency_box, timestep).sample

    def training_step(
        self, batch: dict[str, Float[torch.Tensor, "..."]], batch_idx: int
    ) -> Float[torch.Tensor, ""]:
        """Run one CGB-DM denoising training step."""
        del batch_idx
        layout = batch["layout"]
        image = batch["pixel_values"]
        saliency_box = batch["saliency_box"]
        timesteps = self.scheduler.sample_timesteps(
            layout.shape[0], device=layout.device
        )
        noise = torch.randn_like(layout)
        fix_mask = self.scheduler.condition_mask(layout, self.condition_type)
        noisy = self.scheduler.add_noise(layout, noise, timesteps, fix_mask=fix_mask)
        model_output = self.model(noisy, image, saliency_box, timesteps)
        pred = model_output.sample if hasattr(model_output, "sample") else model_output
        cgb_weight = getattr(model_output, "cgb_weight", None)
        loss = denoising_mse(pred, noise)
        self.latest_step_trace = {
            "pixel_values": image.detach(),
            "layout": layout.detach(),
            "saliency_box": saliency_box.detach(),
            "t": timesteps.detach(),
            "noise": noise.detach(),
            "fix_mask": fix_mask.detach(),
            "noisy_layout": noisy.detach(),
            "predicted_epsilon": pred.detach(),
            "cgb_weight": (
                cgb_weight.detach()
                if isinstance(cgb_weight, torch.Tensor)
                else torch.empty(0, device=layout.device)
            ),
            "loss": loss.detach().reshape(1),
        }
        if hasattr(self, "log"):
            self.log("train_loss", loss)
            self.log(
                "Loss/train",
                loss,
                on_step=False,
                on_epoch=True,
                batch_size=layout.shape[0],
            )
        return loss

    def configure_optimizers(self) -> OptimizerLRScheduler:
        """Build optimizers injected by LightningCLI."""
        optimizer = (
            self.optimizer(self.parameters())  # type: ignore[call-arg]
            if self.optimizer is not None
            else torch.optim.Adam(
                self.parameters(),
                lr=1.0e-4,
                weight_decay=0.0,
                betas=(0.9, 0.999),
                amsgrad=False,
                eps=1.0e-8,
            )
        )
        if self.lr_scheduler is None:
            return optimizer
        scheduler = self.lr_scheduler(optimizer)  # type: ignore[call-arg]
        return {"optimizer": optimizer, "lr_scheduler": scheduler}
__init__
__init__(
    *,
    config: CGBDMConfig
    | dict[
        str,
        str
        | int
        | tuple[int, int]
        | list[int]
        | list[str]
        | dict[int | str, str]
        | None,
    ],
    optimizer: OptimizerCallable | None = None,
    lr_scheduler: LRSchedulerCallable | None = None,
    model: CGBDMTransformerModel | None = None,
    condition_type: CGBDMCondition = "content_image",
    seed_mode: CGBDMSeedMode = "default",
) -> None

Initialize model, scheduler, and optimizer settings.

Source code in models/cgb-dm/src/cgb_dm/training/lightning_module.py
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
def __init__(
    self,
    *,
    config: CGBDMConfig
    | dict[
        str,
        str
        | int
        | tuple[int, int]
        | list[int]
        | list[str]
        | dict[int | str, str]
        | None,
    ],
    optimizer: OptimizerCallable | None = None,
    lr_scheduler: LRSchedulerCallable | None = None,
    model: CGBDMTransformerModel | None = None,
    condition_type: CGBDMCondition = "content_image",
    seed_mode: CGBDMSeedMode = "default",
) -> None:
    """Initialize model, scheduler, and optimizer settings."""
    super().__init__()
    self.config_obj = (
        config if isinstance(config, CGBDMConfig) else CGBDMConfig(**config)
    )
    self.model = model or CGBDMTransformerModel(
        num_labels=self.config_obj.num_labels,
        max_seq_length=self.config_obj.max_seq_length,
        image_size=self.config_obj.image_size,
        dim_model=self.config_obj.dim_model,
        n_head=self.config_obj.n_head,
        feature_dim=self.config_obj.feature_dim,
        num_layers=self.config_obj.num_layers,
        num_train_timesteps=self.config_obj.num_train_timesteps,
    )
    self.scheduler = CGBDMScheduler(
        num_train_timesteps=self.config_obj.num_train_timesteps,
        ddim_num_steps=self.config_obj.ddim_num_steps,
        train_beta_schedule=self.config_obj.train_beta_schedule,
        sampling_beta_schedule=self.config_obj.sampling_beta_schedule,
    )
    self.optimizer = optimizer
    self.lr_scheduler = lr_scheduler
    self.condition_type = ConditionType(condition_type)
    self.seed_mode = seed_mode
    self.latest_step_trace: dict[str, Float[torch.Tensor, "..."]] = {}
forward
forward(
    sample: Float[Tensor, "batch elements channels"],
    image: Float[Tensor, "batch channels height width"],
    saliency_box: Float[Tensor, "batch 1 4"],
    timestep: Int[Tensor, "batch"],
) -> Float[torch.Tensor, "batch elements channels"]

Predict epsilon for a training sample.

Source code in models/cgb-dm/src/cgb_dm/training/lightning_module.py
70
71
72
73
74
75
76
77
78
def forward(
    self,
    sample: Float[torch.Tensor, "batch elements channels"],
    image: Float[torch.Tensor, "batch channels height width"],
    saliency_box: Float[torch.Tensor, "batch 1 4"],
    timestep: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch elements channels"]:
    """Predict epsilon for a training sample."""
    return self.model(sample, image, saliency_box, timestep).sample
training_step
training_step(
    batch: dict[str, Float[Tensor, "..."]], batch_idx: int
) -> Float[torch.Tensor, ""]

Run one CGB-DM denoising training step.

Source code in models/cgb-dm/src/cgb_dm/training/lightning_module.py
 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
def training_step(
    self, batch: dict[str, Float[torch.Tensor, "..."]], batch_idx: int
) -> Float[torch.Tensor, ""]:
    """Run one CGB-DM denoising training step."""
    del batch_idx
    layout = batch["layout"]
    image = batch["pixel_values"]
    saliency_box = batch["saliency_box"]
    timesteps = self.scheduler.sample_timesteps(
        layout.shape[0], device=layout.device
    )
    noise = torch.randn_like(layout)
    fix_mask = self.scheduler.condition_mask(layout, self.condition_type)
    noisy = self.scheduler.add_noise(layout, noise, timesteps, fix_mask=fix_mask)
    model_output = self.model(noisy, image, saliency_box, timesteps)
    pred = model_output.sample if hasattr(model_output, "sample") else model_output
    cgb_weight = getattr(model_output, "cgb_weight", None)
    loss = denoising_mse(pred, noise)
    self.latest_step_trace = {
        "pixel_values": image.detach(),
        "layout": layout.detach(),
        "saliency_box": saliency_box.detach(),
        "t": timesteps.detach(),
        "noise": noise.detach(),
        "fix_mask": fix_mask.detach(),
        "noisy_layout": noisy.detach(),
        "predicted_epsilon": pred.detach(),
        "cgb_weight": (
            cgb_weight.detach()
            if isinstance(cgb_weight, torch.Tensor)
            else torch.empty(0, device=layout.device)
        ),
        "loss": loss.detach().reshape(1),
    }
    if hasattr(self, "log"):
        self.log("train_loss", loss)
        self.log(
            "Loss/train",
            loss,
            on_step=False,
            on_epoch=True,
            batch_size=layout.shape[0],
        )
    return loss
configure_optimizers
configure_optimizers() -> OptimizerLRScheduler

Build optimizers injected by LightningCLI.

Source code in models/cgb-dm/src/cgb_dm/training/lightning_module.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def configure_optimizers(self) -> OptimizerLRScheduler:
    """Build optimizers injected by LightningCLI."""
    optimizer = (
        self.optimizer(self.parameters())  # type: ignore[call-arg]
        if self.optimizer is not None
        else torch.optim.Adam(
            self.parameters(),
            lr=1.0e-4,
            weight_decay=0.0,
            betas=(0.9, 0.999),
            amsgrad=False,
            eps=1.0e-8,
        )
    )
    if self.lr_scheduler is None:
        return optimizer
    scheduler = self.lr_scheduler(optimizer)  # type: ignore[call-arg]
    return {"optimizer": optimizer, "lr_scheduler": scheduler}

losses

Loss functions for CGB-DM training.

denoising_mse

denoising_mse(
    predicted: Float[Tensor, "..."],
    target: Float[Tensor, "..."],
) -> Float[torch.Tensor, ""]

Return the CGB-DM epsilon prediction MSE.

Source code in models/cgb-dm/src/cgb_dm/training/losses.py
 9
10
11
12
13
def denoising_mse(
    predicted: Float[torch.Tensor, "..."], target: Float[torch.Tensor, "..."]
) -> Float[torch.Tensor, ""]:
    """Return the CGB-DM epsilon prediction MSE."""
    return torch.nn.functional.mse_loss(predicted, target)

parity

S0-S2 parity adapters for CGB-DM.

CGBDMStepTraceAdapter

Adapter exposing comparable CGB-DM training-step trace tensors.

Source code in models/cgb-dm/src/cgb_dm/training/parity.py
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
class CGBDMStepTraceAdapter:
    """Adapter exposing comparable CGB-DM training-step trace tensors."""

    trace_points = (
        "pixel_values",
        "layout",
        "saliency_box",
        "t",
        "noise",
        "fix_mask",
        "noisy_layout",
        "predicted_epsilon",
        "cgb_weight",
        "loss",
    )

    def comparable_batch(
        self,
        batch: Mapping[str, Float[torch.Tensor, "..."]]
        | tuple[
            Float[torch.Tensor, "..."],
            Float[torch.Tensor, "..."],
            Float[torch.Tensor, "..."],
        ],
    ) -> Mapping[str, Float[torch.Tensor, "..."]]:
        """Normalize dict or tuple batches to comparable tensor mappings."""
        if isinstance(batch, tuple):
            image, layout, saliency_box = batch
            result: dict[str, Float[torch.Tensor, "..."]] = {}
            result["pixel_values"] = cast(Float[torch.Tensor, "..."], image)
            result["layout"] = cast(Float[torch.Tensor, "..."], layout)
            result["saliency_box"] = cast(Float[torch.Tensor, "..."], saliency_box)
            return result
        return batch
comparable_batch
comparable_batch(
    batch: Mapping[str, Float[Tensor, "..."]]
    | tuple[
        Float[Tensor, "..."],
        Float[Tensor, "..."],
        Float[Tensor, "..."],
    ],
) -> Mapping[str, Float[torch.Tensor, "..."]]

Normalize dict or tuple batches to comparable tensor mappings.

Source code in models/cgb-dm/src/cgb_dm/training/parity.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def comparable_batch(
    self,
    batch: Mapping[str, Float[torch.Tensor, "..."]]
    | tuple[
        Float[torch.Tensor, "..."],
        Float[torch.Tensor, "..."],
        Float[torch.Tensor, "..."],
    ],
) -> Mapping[str, Float[torch.Tensor, "..."]]:
    """Normalize dict or tuple batches to comparable tensor mappings."""
    if isinstance(batch, tuple):
        image, layout, saliency_box = batch
        result: dict[str, Float[torch.Tensor, "..."]] = {}
        result["pixel_values"] = cast(Float[torch.Tensor, "..."], image)
        result["layout"] = cast(Float[torch.Tensor, "..."], layout)
        result["saliency_box"] = cast(Float[torch.Tensor, "..."], saliency_box)
        return result
    return batch

capture_source_order

capture_source_order(
    data_root: str | Path, *, split: str = "train"
) -> list[str]

Capture the filename order used by the original CGB-DM training loader.

Source code in models/cgb-dm/src/cgb_dm/training/parity.py
52
53
54
def capture_source_order(data_root: str | Path, *, split: str = "train") -> list[str]:
    """Capture the filename order used by the original CGB-DM training loader."""
    return list(os.listdir(Path(data_root) / split / "inpaint"))

write_source_order_manifest

write_source_order_manifest(
    *,
    data_root: str | Path,
    output: str | Path,
    dataset: str,
    split: str = "train",
    seed: int = 1,
) -> Path

Write a regenerated source-order manifest outside the repository.

Source code in models/cgb-dm/src/cgb_dm/training/parity.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def write_source_order_manifest(
    *,
    data_root: str | Path,
    output: str | Path,
    dataset: str,
    split: str = "train",
    seed: int = 1,
) -> Path:
    """Write a regenerated source-order manifest outside the repository."""
    names = capture_source_order(data_root, split=split)
    path = Path(output)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        json.dumps(
            {
                "dataset": dataset,
                "split": split,
                "seed": seed,
                "source": "reference train_dataset os.listdir order",
                "data_root": str(data_root),
                "names": names,
            },
            indent=2,
        ),
        encoding="utf-8",
    )
    return path

load_source_order_manifest

load_source_order_manifest(path: str | Path) -> list[str]

Load names from a regenerated source-order manifest.

Source code in models/cgb-dm/src/cgb_dm/training/parity.py
86
87
88
89
def load_source_order_manifest(path: str | Path) -> list[str]:
    """Load names from a regenerated source-order manifest."""
    payload = json.loads(Path(path).read_text(encoding="utf-8"))
    return [str(name) for name in payload["names"]]

build_reference_dataset

build_reference_dataset(
    data_root: str | Path,
    *,
    manifest: str | Path,
    split: Literal["train", "val", "test"] = "train",
) -> CGBDMOriginalDataset

Build a CGB-DM dataset that replays captured source order and encoding.

Source code in models/cgb-dm/src/cgb_dm/training/parity.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def build_reference_dataset(
    data_root: str | Path,
    *,
    manifest: str | Path,
    split: Literal["train", "val", "test"] = "train",
) -> CGBDMOriginalDataset:
    """Build a CGB-DM dataset that replays captured source order and encoding."""
    return CGBDMOriginalDataset(
        data_root,
        split=split,
        name_manifest=manifest,
        encoding="reference",
    )

seed

Seed helpers for CGB-DM training.

apply_seed_mode

apply_seed_mode(
    mode: CGBDMSeedMode, seed: int = 1
) -> dict[str, str | int | bool]

Apply CGB-DM seed behavior and return metadata.

Source code in models/cgb-dm/src/cgb_dm/training/seed.py
13
14
15
16
17
18
19
20
21
22
def apply_seed_mode(mode: CGBDMSeedMode, seed: int = 1) -> dict[str, str | int | bool]:
    """Apply CGB-DM seed behavior and return metadata."""
    metadata: dict[str, str | int | bool] = {"mode": mode, "seed": seed}
    if mode == "deterministic":
        random.seed(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
        torch.use_deterministic_algorithms(True)
        metadata["deterministic_algorithms"] = True
    return metadata