Skip to content

Layout flow

Diffusers-compatible LayoutFlow components.

LayoutFlowConfig

Bases: ConfigMixin

Configuration saved with converted LayoutFlow pipelines.

Source code in models/layout-flow/src/layout_flow/configuration_layout_flow.py
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
class LayoutFlowConfig(ConfigMixin):
    """Configuration saved with converted LayoutFlow pipelines."""

    config_name: str = "layout_flow_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: str = "publaynet",
        id2label: dict[int | str, str] | None = None,
        max_length: int = 20,
        latent_dim: int = 128,
        d_model: int = 512,
        nhead: int = 8,
        dim_feedforward: int = 2048,
        num_layers: int = 4,
        dropout: float = 0.1,
        use_pos_enc: bool = False,
        tr_enc_only: bool = True,
        attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
        seq_type: SeqType = SeqType.stacked,
        distribution: InitialDistributionName = InitialDistributionName.gaussian,
        sample_padding: bool = False,
        inference_steps: int = 100,
        ode_solver: OdeSolverName = OdeSolverName.euler,
        bbox_format: BoxFormat | str = "xywh",
        coordinate_range: CoordinateRange = CoordinateRange.normalized_0_1,
    ) -> None:
        """Initialize LayoutFlow pipeline and model settings.

        Args:
            dataset_name: Dataset variant or alias.
            id2label: Optional explicit id-to-label mapping.
            max_length: Maximum number of layout elements.
            latent_dim: Latent dimension.
            d_model: Transformer hidden size.
            nhead: Number of attention heads.
            dim_feedforward: Feed-forward hidden size.
            num_layers: Number of transformer layers.
            dropout: Dropout probability.
            use_pos_enc: Whether to add sinusoidal position encodings.
            tr_enc_only: Whether to use the encoder-only path.
            attr_encoding: Attribute encoding used by the checkpoint.
            seq_type: Sequence layout type.
            distribution: Initial sampling distribution.
            sample_padding: Whether sampling includes padded elements.
            inference_steps: Default Euler inference steps.
            ode_solver: ODE solver name.
            bbox_format: Public bounding-box format.
            coordinate_range: Public coordinate range.

        Raises:
            ValueError: If ``dataset_name`` is unsupported.
        """
        self.dataset_name = str(normalize_dataset_name(dataset_name))
        raw_id2label = id2label or default_id2label(self.dataset_name)
        self.id2label = {int(k): v for k, v in raw_id2label.items()}

        self.max_length = max_length
        self.latent_dim = latent_dim
        self.d_model = d_model
        self.nhead = nhead
        self.dim_feedforward = dim_feedforward
        self.num_layers = num_layers
        self.dropout = dropout
        self.use_pos_enc = use_pos_enc
        self.tr_enc_only = tr_enc_only

        self.attr_encoding = str(AttrEncoding(attr_encoding))
        self.seq_type = str(SeqType(seq_type))
        self.distribution = str(InitialDistributionName(distribution))
        self.sample_padding = sample_padding

        self.inference_steps = inference_steps
        self.ode_solver = str(OdeSolverName(ode_solver))
        self.bbox_format = str(BoxFormat(bbox_format))
        self.coordinate_range = str(CoordinateRange(coordinate_range))

    @property
    def label2id(self) -> dict[str, int]:
        """Return the label-name to integer-id mapping."""
        return {v: k for k, v in self.id2label.items()}

    @property
    def num_labels(self) -> int:
        """Return the number of labels, including the background label."""
        return len(self.id2label)

    @property
    def attr_dim(self) -> int:
        """Return the analog-bit attribute dimensionality."""
        if AttrEncoding(self.attr_encoding) is AttrEncoding.analog_bit:
            return int(math.ceil(math.log2(self.num_labels)))
        return 1

    @property
    def sample_dim(self) -> int:
        """Return the model-state dimensionality per layout element."""
        return 4 + self.attr_dim

label2id property

label2id: dict[str, int]

Return the label-name to integer-id mapping.

num_labels property

num_labels: int

Return the number of labels, including the background label.

attr_dim property

attr_dim: int

Return the analog-bit attribute dimensionality.

sample_dim property

sample_dim: int

Return the model-state dimensionality per layout element.

__init__

__init__(
    *,
    dataset_name: str = "publaynet",
    id2label: dict[int | str, str] | None = None,
    max_length: int = 20,
    latent_dim: int = 128,
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    num_layers: int = 4,
    dropout: float = 0.1,
    use_pos_enc: bool = False,
    tr_enc_only: bool = True,
    attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
    seq_type: SeqType = SeqType.stacked,
    distribution: InitialDistributionName = InitialDistributionName.gaussian,
    sample_padding: bool = False,
    inference_steps: int = 100,
    ode_solver: OdeSolverName = OdeSolverName.euler,
    bbox_format: BoxFormat | str = "xywh",
    coordinate_range: CoordinateRange = CoordinateRange.normalized_0_1,
) -> None

Initialize LayoutFlow pipeline and model settings.

Parameters:

Name Type Description Default
dataset_name str

Dataset variant or alias.

'publaynet'
id2label dict[int | str, str] | None

Optional explicit id-to-label mapping.

None
max_length int

Maximum number of layout elements.

20
latent_dim int

Latent dimension.

128
d_model int

Transformer hidden size.

512
nhead int

Number of attention heads.

8
dim_feedforward int

Feed-forward hidden size.

2048
num_layers int

Number of transformer layers.

4
dropout float

Dropout probability.

0.1
use_pos_enc bool

Whether to add sinusoidal position encodings.

False
tr_enc_only bool

Whether to use the encoder-only path.

True
attr_encoding AttrEncoding

Attribute encoding used by the checkpoint.

analog_bit
seq_type SeqType

Sequence layout type.

stacked
distribution InitialDistributionName

Initial sampling distribution.

gaussian
sample_padding bool

Whether sampling includes padded elements.

False
inference_steps int

Default Euler inference steps.

100
ode_solver OdeSolverName

ODE solver name.

euler
bbox_format BoxFormat | str

Public bounding-box format.

'xywh'
coordinate_range CoordinateRange

Public coordinate range.

normalized_0_1

Raises:

Type Description
ValueError

If dataset_name is unsupported.

Source code in models/layout-flow/src/layout_flow/configuration_layout_flow.py
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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: str = "publaynet",
    id2label: dict[int | str, str] | None = None,
    max_length: int = 20,
    latent_dim: int = 128,
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    num_layers: int = 4,
    dropout: float = 0.1,
    use_pos_enc: bool = False,
    tr_enc_only: bool = True,
    attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
    seq_type: SeqType = SeqType.stacked,
    distribution: InitialDistributionName = InitialDistributionName.gaussian,
    sample_padding: bool = False,
    inference_steps: int = 100,
    ode_solver: OdeSolverName = OdeSolverName.euler,
    bbox_format: BoxFormat | str = "xywh",
    coordinate_range: CoordinateRange = CoordinateRange.normalized_0_1,
) -> None:
    """Initialize LayoutFlow pipeline and model settings.

    Args:
        dataset_name: Dataset variant or alias.
        id2label: Optional explicit id-to-label mapping.
        max_length: Maximum number of layout elements.
        latent_dim: Latent dimension.
        d_model: Transformer hidden size.
        nhead: Number of attention heads.
        dim_feedforward: Feed-forward hidden size.
        num_layers: Number of transformer layers.
        dropout: Dropout probability.
        use_pos_enc: Whether to add sinusoidal position encodings.
        tr_enc_only: Whether to use the encoder-only path.
        attr_encoding: Attribute encoding used by the checkpoint.
        seq_type: Sequence layout type.
        distribution: Initial sampling distribution.
        sample_padding: Whether sampling includes padded elements.
        inference_steps: Default Euler inference steps.
        ode_solver: ODE solver name.
        bbox_format: Public bounding-box format.
        coordinate_range: Public coordinate range.

    Raises:
        ValueError: If ``dataset_name`` is unsupported.
    """
    self.dataset_name = str(normalize_dataset_name(dataset_name))
    raw_id2label = id2label or default_id2label(self.dataset_name)
    self.id2label = {int(k): v for k, v in raw_id2label.items()}

    self.max_length = max_length
    self.latent_dim = latent_dim
    self.d_model = d_model
    self.nhead = nhead
    self.dim_feedforward = dim_feedforward
    self.num_layers = num_layers
    self.dropout = dropout
    self.use_pos_enc = use_pos_enc
    self.tr_enc_only = tr_enc_only

    self.attr_encoding = str(AttrEncoding(attr_encoding))
    self.seq_type = str(SeqType(seq_type))
    self.distribution = str(InitialDistributionName(distribution))
    self.sample_padding = sample_padding

    self.inference_steps = inference_steps
    self.ode_solver = str(OdeSolverName(ode_solver))
    self.bbox_format = str(BoxFormat(bbox_format))
    self.coordinate_range = str(CoordinateRange(coordinate_range))

LayoutFlowModelOutput dataclass

Bases: BaseOutput

Output of LayoutFlowTransformerModel.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
329
330
331
332
333
@dataclass
class LayoutFlowModelOutput(BaseOutput):
    """Output of ``LayoutFlowTransformerModel``."""

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

LayoutFlowTransformerModel

Bases: ModelMixin, ConfigMixin

Diffusers model wrapper around the LayoutFlow backbone.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
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
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
class LayoutFlowTransformerModel(ModelMixin, ConfigMixin):
    """Diffusers model wrapper around the LayoutFlow backbone."""

    config_name: str = "layout_flow_model_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        num_labels: int = 6,
        latent_dim: int = 128,
        tr_enc_only: bool = True,
        d_model: int = 512,
        nhead: int = 8,
        dim_feedforward: int = 2048,
        num_layers: int = 4,
        dropout: float = 0.1,
        use_pos_enc: bool = False,
        attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
        seq_type: SeqType = SeqType.stacked,
    ) -> None:
        """Initialize the converted LayoutFlow transformer model.

        Args:
            num_labels: Number of dataset labels.
            latent_dim: Latent dimension.
            tr_enc_only: Whether to use the encoder-only path.
            d_model: Transformer hidden size.
            nhead: Number of attention heads.
            dim_feedforward: Feed-forward hidden size.
            num_layers: Number of transformer layers.
            dropout: Dropout probability.
            use_pos_enc: Whether to add positional encodings.
            attr_encoding: Attribute encoding.
            seq_type: Sequence type.
        """
        super().__init__()
        self.geom_dim = 4
        self.attr_dim = (
            int(np.ceil(np.log2(num_labels)))
            if AttrEncoding(attr_encoding) is AttrEncoding.analog_bit
            else 1
        )
        self.backbone = LayoutDMBackbone(
            latent_dim=latent_dim,
            tr_enc_only=tr_enc_only,
            d_model=d_model,
            nhead=nhead,
            dim_feedforward=dim_feedforward,
            num_layers=num_layers,
            dropout=dropout,
            use_pos_enc=use_pos_enc,
            num_cat=num_labels,
            attr_encoding=attr_encoding,
            seq_type=seq_type,
        )

    def forward(
        self,
        sample: Float[torch.Tensor, "batch elements channels"],
        timestep: Float[torch.Tensor, ""],
        cond_mask: Bool[torch.Tensor, "batch elements channels"],
        return_dict: bool = True,
    ) -> LayoutFlowModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
        """Predict the vector field for a model state.

        Args:
            sample: Current model state.
            timestep: Current integration timestep.
            cond_mask: Condition mask.
            return_dict: Whether to return a dataclass output.

        Returns:
            Model output dataclass or single-item tuple.
        """
        timestep = timestep.to(device=sample.device, dtype=sample.dtype)
        if timestep.ndim == 0:
            timestep = timestep.repeat(sample.shape[0])
        geom = sample[:, :, : self.geom_dim]
        attr = sample[:, :, self.geom_dim :]
        out = self.backbone(geom, attr, cond_mask.to(torch.long), timestep)
        if not return_dict:
            return (out,)
        return LayoutFlowModelOutput(sample=out)

__init__

__init__(
    *,
    num_labels: int = 6,
    latent_dim: int = 128,
    tr_enc_only: bool = True,
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    num_layers: int = 4,
    dropout: float = 0.1,
    use_pos_enc: bool = False,
    attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
    seq_type: SeqType = SeqType.stacked,
) -> None

Initialize the converted LayoutFlow transformer model.

Parameters:

Name Type Description Default
num_labels int

Number of dataset labels.

6
latent_dim int

Latent dimension.

128
tr_enc_only bool

Whether to use the encoder-only path.

True
d_model int

Transformer hidden size.

512
nhead int

Number of attention heads.

8
dim_feedforward int

Feed-forward hidden size.

2048
num_layers int

Number of transformer layers.

4
dropout float

Dropout probability.

0.1
use_pos_enc bool

Whether to add positional encodings.

False
attr_encoding AttrEncoding

Attribute encoding.

analog_bit
seq_type SeqType

Sequence type.

stacked
Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
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
390
391
@register_to_config
def __init__(
    self,
    *,
    num_labels: int = 6,
    latent_dim: int = 128,
    tr_enc_only: bool = True,
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    num_layers: int = 4,
    dropout: float = 0.1,
    use_pos_enc: bool = False,
    attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
    seq_type: SeqType = SeqType.stacked,
) -> None:
    """Initialize the converted LayoutFlow transformer model.

    Args:
        num_labels: Number of dataset labels.
        latent_dim: Latent dimension.
        tr_enc_only: Whether to use the encoder-only path.
        d_model: Transformer hidden size.
        nhead: Number of attention heads.
        dim_feedforward: Feed-forward hidden size.
        num_layers: Number of transformer layers.
        dropout: Dropout probability.
        use_pos_enc: Whether to add positional encodings.
        attr_encoding: Attribute encoding.
        seq_type: Sequence type.
    """
    super().__init__()
    self.geom_dim = 4
    self.attr_dim = (
        int(np.ceil(np.log2(num_labels)))
        if AttrEncoding(attr_encoding) is AttrEncoding.analog_bit
        else 1
    )
    self.backbone = LayoutDMBackbone(
        latent_dim=latent_dim,
        tr_enc_only=tr_enc_only,
        d_model=d_model,
        nhead=nhead,
        dim_feedforward=dim_feedforward,
        num_layers=num_layers,
        dropout=dropout,
        use_pos_enc=use_pos_enc,
        num_cat=num_labels,
        attr_encoding=attr_encoding,
        seq_type=seq_type,
    )

forward

forward(
    sample: Float[Tensor, "batch elements channels"],
    timestep: Float[Tensor, ""],
    cond_mask: Bool[Tensor, "batch elements channels"],
    return_dict: bool = True,
) -> (
    LayoutFlowModelOutput
    | tuple[Float[torch.Tensor, "batch elements channels"]]
)

Predict the vector field for a model state.

Parameters:

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

Current model state.

required
timestep Float[Tensor, '']

Current integration timestep.

required
cond_mask Bool[Tensor, 'batch elements channels']

Condition mask.

required
return_dict bool

Whether to return a dataclass output.

True

Returns:

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

Model output dataclass or single-item tuple.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
def forward(
    self,
    sample: Float[torch.Tensor, "batch elements channels"],
    timestep: Float[torch.Tensor, ""],
    cond_mask: Bool[torch.Tensor, "batch elements channels"],
    return_dict: bool = True,
) -> LayoutFlowModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
    """Predict the vector field for a model state.

    Args:
        sample: Current model state.
        timestep: Current integration timestep.
        cond_mask: Condition mask.
        return_dict: Whether to return a dataclass output.

    Returns:
        Model output dataclass or single-item tuple.
    """
    timestep = timestep.to(device=sample.device, dtype=sample.dtype)
    if timestep.ndim == 0:
        timestep = timestep.repeat(sample.shape[0])
    geom = sample[:, :, : self.geom_dim]
    attr = sample[:, :, self.geom_dim :]
    out = self.backbone(geom, attr, cond_mask.to(torch.long), timestep)
    if not return_dict:
        return (out,)
    return LayoutFlowModelOutput(sample=out)

LayoutFlowPipeline

Bases: DiffusionPipeline

Generate layouts with a converted LayoutFlow checkpoint.

Source code in models/layout-flow/src/layout_flow/pipeline_layout_flow.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
class LayoutFlowPipeline(DiffusionPipeline):
    """Generate layouts with a converted LayoutFlow checkpoint."""

    model_cpu_offload_seq: str = "model"

    def __init__(
        self,
        model: LayoutFlowTransformerModel,
        scheduler: LayoutFlowEulerScheduler,
        config: LayoutFlowConfig,
        processor: LayoutFlowProcessor | None = None,
    ) -> None:
        """Create a LayoutFlow pipeline.

        Args:
            model: Converted LayoutFlow transformer model.
            scheduler: Increasing-time Euler scheduler.
            config: Pipeline configuration.
            processor: Optional input/output processor.
        """
        super().__init__()
        self.register_modules(model=model, scheduler=scheduler)
        self.layout_flow_config = config
        self.processor = processor or LayoutFlowProcessor(self.layout_flow_config)
        self.model.eval()

    @torch.no_grad()
    def __call__(
        self,
        *,
        batch_size: int = 1,
        seed: int | None = None,
        generator: torch.Generator | None = None,
        condition_type: ConditionType | str = ConditionType.unconditional,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = "xywh",
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        guidance_scale: float = 0.0,
        output_type: OutputType | str = "dataclass",
        return_intermediates: bool = False,
    ) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
        """Generate layout boxes and labels.

        Args:
            batch_size: Number of layouts to generate.
            seed: Optional seed used when ``generator`` is omitted.
            generator: Optional torch random generator.
            condition_type: Public condition name or supported alias.
            labels: Optional condition labels.
            bbox: Optional condition boxes.
            mask: Optional valid-element mask.
            num_elements: Optional element counts for unconditional masks.
            box_format: Input and output box format.
            normalized: Whether coordinates are normalized.
            canvas_size: Pixel canvas size for denormalized coordinates.
            num_inference_steps: Number of Euler steps.
            guidance_scale: Classifier-free guidance scale.
            output_type: ``"dataclass"`` or ``"dict"``.
            return_intermediates: Whether to include intermediate samples.

        Returns:
            Layout generation output dataclass, or a dictionary when requested.

        Raises:
            ValueError: If ``condition_type``, ``box_format``, or ``output_type``
                is unsupported.

        Examples:
            >>> pipe = LayoutFlowPipeline(
            ...     model=LayoutFlowTransformerModel(
            ...         num_labels=6, latent_dim=8, d_model=16, nhead=4,
            ...         dim_feedforward=32, num_layers=1
            ...     ),
            ...     scheduler=LayoutFlowEulerScheduler(num_inference_steps=2),
            ...     config=LayoutFlowConfig(max_length=2, latent_dim=8, d_model=16),
            ... )
            >>> out = pipe(batch_size=1, num_elements=1, seed=0, num_inference_steps=2)
            >>> out.bbox.shape[-1]
            4
        """
        if generator is None and seed is not None:
            generator = torch.Generator(device=self.device).manual_seed(seed)
        canonical = normalize_condition_type(condition_type)
        output_kind = OutputType(output_type)
        processed = self.processor(
            bbox=bbox,
            labels=labels,
            mask=mask,
            num_elements=num_elements,
            batch_size=batch_size,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
            device=self.device,
        )
        batch_size = processed["bbox"].shape[0]
        cond_mask = self.processor.make_condition_mask(
            canonical, mask=processed["mask"], generator=generator
        )
        cond_state = self.processor.preprocess_state(
            self.processor.model_state(processed["bbox"], processed["labels"])
        )
        sample = sample_initial_state(
            batch_size=batch_size,
            max_length=self.layout_flow_config.max_length,
            lengths=processed["length"],
            dim=self.layout_flow_config.sample_dim,
            distribution=self.layout_flow_config.distribution,
            generator=generator,
            device=self.device,
            dtype=cond_state.dtype,
        )
        if canonical is ConditionType.refinement:
            sample = cond_state
            self.scheduler.set_timesteps(
                num_inference_steps, device=self.device, start=0.97, end=1.0
            )
        else:
            self.scheduler.set_timesteps(num_inference_steps, device=self.device)
        trajectory = [] if return_intermediates else None
        for i, timestep in enumerate(self.scheduler.timesteps[:-1]):
            x_in = (1 - cond_mask) * cond_state + cond_mask * sample
            t_batch = timestep.repeat(batch_size)
            vector = self.model(
                sample=x_in, timestep=t_batch, cond_mask=cond_mask
            ).sample
            if guidance_scale:
                uncond_mask = torch.ones_like(cond_mask)
                uncond = self.model(
                    sample=x_in, timestep=t_batch, cond_mask=uncond_mask
                ).sample
                vector = (1 + guidance_scale) * vector - guidance_scale * uncond
            sample = self.scheduler.step(
                vector,
                timestep,
                sample,
                next_timestep=self.scheduler.timesteps[i + 1],
            ).prev_sample
            if trajectory is not None:
                trajectory.append(sample.detach().cpu())
        final_state = (1 - cond_mask) * cond_state + cond_mask * sample
        decoded = self.processor.postprocess(
            final_state,
            mask=processed["mask"],
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        output = LayoutGenerationOutput(
            bbox=decoded["bbox"].detach().cpu(),
            labels=decoded["labels"].detach().cpu(),
            mask=decoded["mask"].detach().cpu(),
            id2label=self.layout_flow_config.id2label,
            trajectory=trajectory,
            intermediates={"condition_type": str(canonical)}
            if return_intermediates
            else None,
        )
        if output_kind is OutputType.dict:
            return dict(output)
        if output_kind is OutputType.dataclass:
            return output
        assert_never(output_kind)

    generate = __call__

    def save_pretrained(self, save_directory: str | Path) -> None:
        """Save pipeline components and LayoutFlow config.

        Args:
            save_directory: Output directory.
        """
        super().save_pretrained(save_directory)
        self.layout_flow_config.save_config(save_directory)

    @classmethod
    def from_pretrained(
        cls, pretrained_model_name_or_path: str | Path
    ) -> LayoutFlowPipeline:
        """Load a saved LayoutFlow pipeline.

        Args:
            pretrained_model_name_or_path: Local directory or Hub id.

        Returns:
            Loaded LayoutFlow pipeline.
        """
        config_dict, _ = LayoutFlowConfig.load_config(
            pretrained_model_name_or_path,
            return_unused_kwargs=True,
        )
        config = cast(LayoutFlowConfig, LayoutFlowConfig.from_config(config_dict))
        pipe = super().from_pretrained(pretrained_model_name_or_path, config=config)
        pipe.layout_flow_config = config
        pipe.processor = LayoutFlowProcessor(config)
        return pipe

__init__

__init__(
    model: LayoutFlowTransformerModel,
    scheduler: LayoutFlowEulerScheduler,
    config: LayoutFlowConfig,
    processor: LayoutFlowProcessor | None = None,
) -> None

Create a LayoutFlow pipeline.

Parameters:

Name Type Description Default
model LayoutFlowTransformerModel

Converted LayoutFlow transformer model.

required
scheduler LayoutFlowEulerScheduler

Increasing-time Euler scheduler.

required
config LayoutFlowConfig

Pipeline configuration.

required
processor LayoutFlowProcessor | None

Optional input/output processor.

None
Source code in models/layout-flow/src/layout_flow/pipeline_layout_flow.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def __init__(
    self,
    model: LayoutFlowTransformerModel,
    scheduler: LayoutFlowEulerScheduler,
    config: LayoutFlowConfig,
    processor: LayoutFlowProcessor | None = None,
) -> None:
    """Create a LayoutFlow pipeline.

    Args:
        model: Converted LayoutFlow transformer model.
        scheduler: Increasing-time Euler scheduler.
        config: Pipeline configuration.
        processor: Optional input/output processor.
    """
    super().__init__()
    self.register_modules(model=model, scheduler=scheduler)
    self.layout_flow_config = config
    self.processor = processor or LayoutFlowProcessor(self.layout_flow_config)
    self.model.eval()

__call__

__call__(
    *,
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.unconditional,
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = "xywh",
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    guidance_scale: float = 0.0,
    output_type: OutputType | str = "dataclass",
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[str, Shaped[torch.Tensor, "..."]]
)

Generate layout boxes and labels.

Parameters:

Name Type Description Default
batch_size int

Number of layouts to generate.

1
seed int | None

Optional seed used when generator is omitted.

None
generator Generator | None

Optional torch random generator.

None
condition_type ConditionType | str

Public condition name or supported alias.

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

Optional condition labels.

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

Optional condition boxes.

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

Optional valid-element mask.

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

Optional element counts for unconditional masks.

None
box_format BoxFormat | str

Input and output box format.

'xywh'
normalized bool

Whether coordinates are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size for denormalized coordinates.

None
num_inference_steps int | None

Number of Euler steps.

None
guidance_scale float

Classifier-free guidance scale.

0.0
output_type OutputType | str

"dataclass" or "dict".

'dataclass'
return_intermediates bool

Whether to include intermediate samples.

False

Returns:

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

Layout generation output dataclass, or a dictionary when requested.

Raises:

Type Description
ValueError

If condition_type, box_format, or output_type is unsupported.

Examples:

>>> pipe = LayoutFlowPipeline(
...     model=LayoutFlowTransformerModel(
...         num_labels=6, latent_dim=8, d_model=16, nhead=4,
...         dim_feedforward=32, num_layers=1
...     ),
...     scheduler=LayoutFlowEulerScheduler(num_inference_steps=2),
...     config=LayoutFlowConfig(max_length=2, latent_dim=8, d_model=16),
... )
>>> out = pipe(batch_size=1, num_elements=1, seed=0, num_inference_steps=2)
>>> out.bbox.shape[-1]
4
Source code in models/layout-flow/src/layout_flow/pipeline_layout_flow.py
 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
@torch.no_grad()
def __call__(
    self,
    *,
    batch_size: int = 1,
    seed: int | None = None,
    generator: torch.Generator | None = None,
    condition_type: ConditionType | str = ConditionType.unconditional,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = "xywh",
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    guidance_scale: float = 0.0,
    output_type: OutputType | str = "dataclass",
    return_intermediates: bool = False,
) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
    """Generate layout boxes and labels.

    Args:
        batch_size: Number of layouts to generate.
        seed: Optional seed used when ``generator`` is omitted.
        generator: Optional torch random generator.
        condition_type: Public condition name or supported alias.
        labels: Optional condition labels.
        bbox: Optional condition boxes.
        mask: Optional valid-element mask.
        num_elements: Optional element counts for unconditional masks.
        box_format: Input and output box format.
        normalized: Whether coordinates are normalized.
        canvas_size: Pixel canvas size for denormalized coordinates.
        num_inference_steps: Number of Euler steps.
        guidance_scale: Classifier-free guidance scale.
        output_type: ``"dataclass"`` or ``"dict"``.
        return_intermediates: Whether to include intermediate samples.

    Returns:
        Layout generation output dataclass, or a dictionary when requested.

    Raises:
        ValueError: If ``condition_type``, ``box_format``, or ``output_type``
            is unsupported.

    Examples:
        >>> pipe = LayoutFlowPipeline(
        ...     model=LayoutFlowTransformerModel(
        ...         num_labels=6, latent_dim=8, d_model=16, nhead=4,
        ...         dim_feedforward=32, num_layers=1
        ...     ),
        ...     scheduler=LayoutFlowEulerScheduler(num_inference_steps=2),
        ...     config=LayoutFlowConfig(max_length=2, latent_dim=8, d_model=16),
        ... )
        >>> out = pipe(batch_size=1, num_elements=1, seed=0, num_inference_steps=2)
        >>> out.bbox.shape[-1]
        4
    """
    if generator is None and seed is not None:
        generator = torch.Generator(device=self.device).manual_seed(seed)
    canonical = normalize_condition_type(condition_type)
    output_kind = OutputType(output_type)
    processed = self.processor(
        bbox=bbox,
        labels=labels,
        mask=mask,
        num_elements=num_elements,
        batch_size=batch_size,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
        device=self.device,
    )
    batch_size = processed["bbox"].shape[0]
    cond_mask = self.processor.make_condition_mask(
        canonical, mask=processed["mask"], generator=generator
    )
    cond_state = self.processor.preprocess_state(
        self.processor.model_state(processed["bbox"], processed["labels"])
    )
    sample = sample_initial_state(
        batch_size=batch_size,
        max_length=self.layout_flow_config.max_length,
        lengths=processed["length"],
        dim=self.layout_flow_config.sample_dim,
        distribution=self.layout_flow_config.distribution,
        generator=generator,
        device=self.device,
        dtype=cond_state.dtype,
    )
    if canonical is ConditionType.refinement:
        sample = cond_state
        self.scheduler.set_timesteps(
            num_inference_steps, device=self.device, start=0.97, end=1.0
        )
    else:
        self.scheduler.set_timesteps(num_inference_steps, device=self.device)
    trajectory = [] if return_intermediates else None
    for i, timestep in enumerate(self.scheduler.timesteps[:-1]):
        x_in = (1 - cond_mask) * cond_state + cond_mask * sample
        t_batch = timestep.repeat(batch_size)
        vector = self.model(
            sample=x_in, timestep=t_batch, cond_mask=cond_mask
        ).sample
        if guidance_scale:
            uncond_mask = torch.ones_like(cond_mask)
            uncond = self.model(
                sample=x_in, timestep=t_batch, cond_mask=uncond_mask
            ).sample
            vector = (1 + guidance_scale) * vector - guidance_scale * uncond
        sample = self.scheduler.step(
            vector,
            timestep,
            sample,
            next_timestep=self.scheduler.timesteps[i + 1],
        ).prev_sample
        if trajectory is not None:
            trajectory.append(sample.detach().cpu())
    final_state = (1 - cond_mask) * cond_state + cond_mask * sample
    decoded = self.processor.postprocess(
        final_state,
        mask=processed["mask"],
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    output = LayoutGenerationOutput(
        bbox=decoded["bbox"].detach().cpu(),
        labels=decoded["labels"].detach().cpu(),
        mask=decoded["mask"].detach().cpu(),
        id2label=self.layout_flow_config.id2label,
        trajectory=trajectory,
        intermediates={"condition_type": str(canonical)}
        if return_intermediates
        else None,
    )
    if output_kind is OutputType.dict:
        return dict(output)
    if output_kind is OutputType.dataclass:
        return output
    assert_never(output_kind)

save_pretrained

save_pretrained(save_directory: str | Path) -> None

Save pipeline components and LayoutFlow config.

Parameters:

Name Type Description Default
save_directory str | Path

Output directory.

required
Source code in models/layout-flow/src/layout_flow/pipeline_layout_flow.py
216
217
218
219
220
221
222
223
def save_pretrained(self, save_directory: str | Path) -> None:
    """Save pipeline components and LayoutFlow config.

    Args:
        save_directory: Output directory.
    """
    super().save_pretrained(save_directory)
    self.layout_flow_config.save_config(save_directory)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
) -> LayoutFlowPipeline

Load a saved LayoutFlow pipeline.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Local directory or Hub id.

required

Returns:

Type Description
LayoutFlowPipeline

Loaded LayoutFlow pipeline.

Source code in models/layout-flow/src/layout_flow/pipeline_layout_flow.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
@classmethod
def from_pretrained(
    cls, pretrained_model_name_or_path: str | Path
) -> LayoutFlowPipeline:
    """Load a saved LayoutFlow pipeline.

    Args:
        pretrained_model_name_or_path: Local directory or Hub id.

    Returns:
        Loaded LayoutFlow pipeline.
    """
    config_dict, _ = LayoutFlowConfig.load_config(
        pretrained_model_name_or_path,
        return_unused_kwargs=True,
    )
    config = cast(LayoutFlowConfig, LayoutFlowConfig.from_config(config_dict))
    pipe = super().from_pretrained(pretrained_model_name_or_path, config=config)
    pipe.layout_flow_config = config
    pipe.processor = LayoutFlowProcessor(config)
    return pipe

ConditionType

Bases: StrEnum

Canonical condition names used by layout generation interfaces.

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

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

LayoutFlowProcessor

Bases: ProcessorMixin

Prepare public layout tensors for the LayoutFlow model.

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

    config_name = "processor_config.json"

    def __init__(self, config: LayoutFlowConfig) -> None:
        """Create a processor for a LayoutFlow configuration.

        Args:
            config: LayoutFlow pipeline configuration.
        """
        super().__init__()
        self.config = config
        bit_mask = [1 << k for k in range(config.attr_dim)]
        self.bit_mask = torch.tensor(bit_mask, dtype=torch.long)

    def __call__(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        batch_size: int = 1,
        box_format: BoxFormat | str = "xywh",
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        device: torch.device | str | None = None,
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Convert public inputs into padded model-ready tensors.

        Args:
            bbox: Optional boxes in the requested ``box_format``.
            labels: Optional dataset-local label ids.
            mask: Optional valid-element mask.
            num_elements: Optional element counts used when ``mask`` is omitted.
            batch_size: Batch size used when tensors are omitted.
            box_format: Format of ``bbox``.
            normalized: Whether ``bbox`` is already normalized to ``[0, 1]``.
            canvas_size: Pixel canvas size required for denormalized boxes.
            device: Target torch device.

        Returns:
            Dictionary with ``bbox``, ``labels``, ``mask``, and ``length`` tensors.

        Raises:
            ValueError: If denormalized boxes are missing ``canvas_size`` or the
                box format is unsupported.
        """
        device = torch.device(device) if device is not None else torch.device("cpu")
        max_length = self.config.max_length
        if bbox is None:
            bbox_t = torch.zeros(
                batch_size, max_length, 4, dtype=torch.float32, device=device
            )
        else:
            bbox_t = torch.as_tensor(bbox, dtype=torch.float32, device=device)
            if bbox_t.ndim == 2:
                bbox_t = bbox_t.unsqueeze(0)
            batch_size = bbox_t.shape[0]
            if not normalized:
                if canvas_size is None:
                    raise ValueError("canvas_size is required when normalized=False")

                bbox_t = normalize_boxes(
                    bbox_t, canvas_size=canvas_size, box_format=box_format
                )
            else:
                fmt = BoxFormat(box_format)
                if fmt is BoxFormat.ltwh:
                    bbox_t = ltwh_to_xywh(bbox_t)
                elif fmt is BoxFormat.ltrb:
                    bbox_t = ltrb_to_xywh(bbox_t)
                elif fmt is not BoxFormat.xywh:
                    assert_never(fmt)
            bbox_t = self._pad_tensor(bbox_t, max_length, 0.0)
        if labels is None:
            labels_t = torch.zeros(
                batch_size, max_length, dtype=torch.long, device=device
            )
        else:
            labels_t = torch.as_tensor(labels, dtype=torch.long, device=device)
            if labels_t.ndim == 1:
                labels_t = labels_t.unsqueeze(0)
            labels_t = self._pad_tensor(labels_t, max_length, 0)
        if mask is None:
            lengths = self._num_elements_to_lengths(
                num_elements, batch_size, max_length, device
            )
            mask_t = torch.arange(max_length, device=device)[None, :] < lengths[:, None]
        else:
            mask_t = torch.as_tensor(mask, dtype=torch.bool, device=device)
            if mask_t.ndim == 1:
                mask_t = mask_t.unsqueeze(0)
            mask_t = self._pad_tensor(mask_t, max_length, False)
            lengths = mask_t.sum(dim=1).long()
        bbox_t = bbox_t * mask_t.unsqueeze(-1)
        labels_t = labels_t * mask_t.long()
        return {"bbox": bbox_t, "labels": labels_t, "mask": mask_t, "length": lengths}

    def encode_labels(
        self, labels: Int[torch.Tensor, "batch elements"]
    ) -> Float[torch.Tensor, "batch elements bits"]:
        """Encode integer labels as analog-bit vectors."""
        bit_mask = self.bit_mask.to(labels.device)
        return (
            torch.bitwise_and(labels.unsqueeze(-1), bit_mask).float() / bit_mask.float()
        )

    def decode_labels(
        self, bits: Float[torch.Tensor, "batch elements bits"]
    ) -> Int[torch.Tensor, "batch elements"]:
        """Decode analog-bit vectors into integer labels."""
        bit_mask = self.bit_mask.to(bits.device)
        active = (bits - 0.5 >= 0).long()
        return (
            (active * bit_mask).sum(dim=-1).clamp(0, self.config.num_labels - 1).long()
        )

    def model_state(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Concatenate normalized boxes and analog-bit labels."""
        return torch.cat([bbox, self.encode_labels(labels)], dim=-1)

    def preprocess_state(
        self,
        state: Float[torch.Tensor, "batch elements channels"],
        *,
        reverse: bool = False,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Map between public ``[0, 1]`` state and model distribution range."""
        if self.config.distribution in {"gaussian", "gmm", "uniform", "gauss_uniform"}:
            return (state + 1) / 2 if reverse else 2 * state - 1
        return state

    def make_condition_mask(
        self,
        condition_type: ConditionType,
        *,
        mask: Bool[torch.Tensor, "batch elements"],
        generator: torch.Generator | None = None,
    ) -> Int[torch.Tensor, "batch elements channels"]:
        """Create the condition mask for a conditioning mode.

        Args:
            condition_type: Canonical condition or alias.
            mask: Valid-element mask.
            generator: Optional generator used by completion masking.

        Returns:
            Long tensor where ``1`` means generated and ``0`` means conditioned.

        Raises:
            ValueError: If the condition type is unsupported.
        """
        batch, seq = mask.shape
        cond_mask = torch.ones(
            batch,
            seq,
            self.config.sample_dim,
            dtype=torch.long,
            device=mask.device,
        )
        if condition_type in {ConditionType.label, ConditionType.refinement}:
            cond_mask[:, :, 4:] = 0
        elif condition_type is ConditionType.label_size:
            cond_mask[:, :, 2:] = 0
        elif condition_type is ConditionType.completion:
            cond_mask = self._completion_mask(cond_mask, mask, generator)
        elif condition_type is ConditionType.unconditional:
            pass
        else:
            raise ValueError(f"Unsupported LayoutFlow condition_type: {condition_type}")

        return cond_mask

    def postprocess(
        self,
        state: Float[torch.Tensor, "batch elements channels"],
        *,
        mask: Bool[torch.Tensor, "batch elements"],
        box_format: BoxFormat | str = "xywh",
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Convert model state back to public layout tensors.

        Args:
            state: Model state tensor.
            mask: Valid-element mask.
            box_format: Requested output box format.
            normalized: Whether to return normalized coordinates.
            canvas_size: Pixel canvas size for denormalized coordinates.

        Returns:
            Dictionary with ``bbox``, ``labels``, and ``mask``.

        Raises:
            ValueError: If denormalized output is requested without
                ``canvas_size``.
        """
        restored = self.preprocess_state(state, reverse=True)
        bbox = clamp_boxes(restored[:, :, :4]) * mask.unsqueeze(-1)
        labels = self.decode_labels(restored[:, :, 4:]) * mask.long()
        fmt = BoxFormat(box_format)
        if fmt is not BoxFormat.xywh:
            if not normalized and canvas_size is None:
                raise ValueError("canvas_size is required for denormalized output")

            if canvas_size is None:
                canvas_size = (1, 1)
            bbox = denormalize_boxes(bbox, canvas_size=canvas_size, box_format=fmt)
            if normalized:
                scale = torch.tensor(
                    (*canvas_size, *canvas_size), dtype=bbox.dtype, device=bbox.device
                )
                bbox = bbox / scale
        elif not normalized:
            if canvas_size is None:
                raise ValueError("canvas_size is required for denormalized output")

            bbox = denormalize_boxes(
                bbox, canvas_size=canvas_size, box_format=BoxFormat.xywh
            )
        return {"bbox": bbox, "labels": labels, "mask": mask}

    def _completion_mask(
        self,
        cond_mask: Int[torch.Tensor, "batch elements channels"],
        mask: Bool[torch.Tensor, "batch elements"],
        generator: torch.Generator | None,
    ) -> Int[torch.Tensor, "batch elements channels"]:
        for i, length in enumerate(mask.sum(dim=1).tolist()):
            if length <= 1:
                continue
            keep = max(1, int(length * 0.2))
            scores = torch.rand(length, device=mask.device, generator=generator)
            idx = scores.topk(keep).indices
            cond_mask[i, idx] = 0
        return cond_mask

    @staticmethod
    def _pad_tensor(
        tensor: Shaped[torch.Tensor, "..."], max_length: int, value: float | int | bool
    ) -> Shaped[torch.Tensor, "..."]:
        if tensor.shape[1] > max_length:
            return tensor[:, :max_length]
        if tensor.shape[1] == max_length:
            return tensor
        pad_shape = (tensor.shape[0], max_length - tensor.shape[1], *tensor.shape[2:])
        pad = torch.full(pad_shape, value, dtype=tensor.dtype, device=tensor.device)
        return torch.cat([tensor, pad], dim=1)

    @staticmethod
    def _num_elements_to_lengths(
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None,
        batch_size: int,
        max_length: int,
        device: torch.device,
    ) -> Int[torch.Tensor, "batch"]:
        if num_elements is None:
            return torch.full(
                (batch_size,), max_length, dtype=torch.long, device=device
            )
        lengths = torch.as_tensor(num_elements, dtype=torch.long, device=device)
        if lengths.ndim == 0:
            lengths = lengths.repeat(batch_size)
        return lengths.clamp(0, max_length)

__init__

__init__(config: LayoutFlowConfig) -> None

Create a processor for a LayoutFlow configuration.

Parameters:

Name Type Description Default
config LayoutFlowConfig

LayoutFlow pipeline configuration.

required
Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
34
35
36
37
38
39
40
41
42
43
def __init__(self, config: LayoutFlowConfig) -> None:
    """Create a processor for a LayoutFlow configuration.

    Args:
        config: LayoutFlow pipeline configuration.
    """
    super().__init__()
    self.config = config
    bit_mask = [1 << k for k in range(config.attr_dim)]
    self.bit_mask = torch.tensor(bit_mask, dtype=torch.long)

__call__

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

Convert public inputs into padded model-ready tensors.

Parameters:

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

Optional boxes in the requested box_format.

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

Optional dataset-local label ids.

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

Optional valid-element mask.

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

Optional element counts used when mask is omitted.

None
batch_size int

Batch size used when tensors are omitted.

1
box_format BoxFormat | str

Format of bbox.

'xywh'
normalized bool

Whether bbox is already normalized to [0, 1].

True
canvas_size tuple[int, int] | None

Pixel canvas size required for denormalized boxes.

None
device device | str | None

Target torch device.

None

Returns:

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

Dictionary with bbox, labels, mask, and length tensors.

Raises:

Type Description
ValueError

If denormalized boxes are missing canvas_size or the box format is unsupported.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
 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
def __call__(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    batch_size: int = 1,
    box_format: BoxFormat | str = "xywh",
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    device: torch.device | str | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Convert public inputs into padded model-ready tensors.

    Args:
        bbox: Optional boxes in the requested ``box_format``.
        labels: Optional dataset-local label ids.
        mask: Optional valid-element mask.
        num_elements: Optional element counts used when ``mask`` is omitted.
        batch_size: Batch size used when tensors are omitted.
        box_format: Format of ``bbox``.
        normalized: Whether ``bbox`` is already normalized to ``[0, 1]``.
        canvas_size: Pixel canvas size required for denormalized boxes.
        device: Target torch device.

    Returns:
        Dictionary with ``bbox``, ``labels``, ``mask``, and ``length`` tensors.

    Raises:
        ValueError: If denormalized boxes are missing ``canvas_size`` or the
            box format is unsupported.
    """
    device = torch.device(device) if device is not None else torch.device("cpu")
    max_length = self.config.max_length
    if bbox is None:
        bbox_t = torch.zeros(
            batch_size, max_length, 4, dtype=torch.float32, device=device
        )
    else:
        bbox_t = torch.as_tensor(bbox, dtype=torch.float32, device=device)
        if bbox_t.ndim == 2:
            bbox_t = bbox_t.unsqueeze(0)
        batch_size = bbox_t.shape[0]
        if not normalized:
            if canvas_size is None:
                raise ValueError("canvas_size is required when normalized=False")

            bbox_t = normalize_boxes(
                bbox_t, canvas_size=canvas_size, box_format=box_format
            )
        else:
            fmt = BoxFormat(box_format)
            if fmt is BoxFormat.ltwh:
                bbox_t = ltwh_to_xywh(bbox_t)
            elif fmt is BoxFormat.ltrb:
                bbox_t = ltrb_to_xywh(bbox_t)
            elif fmt is not BoxFormat.xywh:
                assert_never(fmt)
        bbox_t = self._pad_tensor(bbox_t, max_length, 0.0)
    if labels is None:
        labels_t = torch.zeros(
            batch_size, max_length, dtype=torch.long, device=device
        )
    else:
        labels_t = torch.as_tensor(labels, dtype=torch.long, device=device)
        if labels_t.ndim == 1:
            labels_t = labels_t.unsqueeze(0)
        labels_t = self._pad_tensor(labels_t, max_length, 0)
    if mask is None:
        lengths = self._num_elements_to_lengths(
            num_elements, batch_size, max_length, device
        )
        mask_t = torch.arange(max_length, device=device)[None, :] < lengths[:, None]
    else:
        mask_t = torch.as_tensor(mask, dtype=torch.bool, device=device)
        if mask_t.ndim == 1:
            mask_t = mask_t.unsqueeze(0)
        mask_t = self._pad_tensor(mask_t, max_length, False)
        lengths = mask_t.sum(dim=1).long()
    bbox_t = bbox_t * mask_t.unsqueeze(-1)
    labels_t = labels_t * mask_t.long()
    return {"bbox": bbox_t, "labels": labels_t, "mask": mask_t, "length": lengths}

encode_labels

encode_labels(
    labels: Int[Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements bits"]

Encode integer labels as analog-bit vectors.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
138
139
140
141
142
143
144
145
def encode_labels(
    self, labels: Int[torch.Tensor, "batch elements"]
) -> Float[torch.Tensor, "batch elements bits"]:
    """Encode integer labels as analog-bit vectors."""
    bit_mask = self.bit_mask.to(labels.device)
    return (
        torch.bitwise_and(labels.unsqueeze(-1), bit_mask).float() / bit_mask.float()
    )

decode_labels

decode_labels(
    bits: Float[Tensor, "batch elements bits"],
) -> Int[torch.Tensor, "batch elements"]

Decode analog-bit vectors into integer labels.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
147
148
149
150
151
152
153
154
155
def decode_labels(
    self, bits: Float[torch.Tensor, "batch elements bits"]
) -> Int[torch.Tensor, "batch elements"]:
    """Decode analog-bit vectors into integer labels."""
    bit_mask = self.bit_mask.to(bits.device)
    active = (bits - 0.5 >= 0).long()
    return (
        (active * bit_mask).sum(dim=-1).clamp(0, self.config.num_labels - 1).long()
    )

model_state

model_state(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements channels"]

Concatenate normalized boxes and analog-bit labels.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
157
158
159
160
161
162
163
def model_state(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements channels"]:
    """Concatenate normalized boxes and analog-bit labels."""
    return torch.cat([bbox, self.encode_labels(labels)], dim=-1)

preprocess_state

preprocess_state(
    state: Float[Tensor, "batch elements channels"],
    *,
    reverse: bool = False,
) -> Float[torch.Tensor, "batch elements channels"]

Map between public [0, 1] state and model distribution range.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
165
166
167
168
169
170
171
172
173
174
def preprocess_state(
    self,
    state: Float[torch.Tensor, "batch elements channels"],
    *,
    reverse: bool = False,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Map between public ``[0, 1]`` state and model distribution range."""
    if self.config.distribution in {"gaussian", "gmm", "uniform", "gauss_uniform"}:
        return (state + 1) / 2 if reverse else 2 * state - 1
    return state

make_condition_mask

make_condition_mask(
    condition_type: ConditionType,
    *,
    mask: Bool[Tensor, "batch elements"],
    generator: Generator | None = None,
) -> Int[torch.Tensor, "batch elements channels"]

Create the condition mask for a conditioning mode.

Parameters:

Name Type Description Default
condition_type ConditionType

Canonical condition or alias.

required
mask Bool[Tensor, 'batch elements']

Valid-element mask.

required
generator Generator | None

Optional generator used by completion masking.

None

Returns:

Type Description
Int[Tensor, 'batch elements channels']

Long tensor where 1 means generated and 0 means conditioned.

Raises:

Type Description
ValueError

If the condition type is unsupported.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
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
def make_condition_mask(
    self,
    condition_type: ConditionType,
    *,
    mask: Bool[torch.Tensor, "batch elements"],
    generator: torch.Generator | None = None,
) -> Int[torch.Tensor, "batch elements channels"]:
    """Create the condition mask for a conditioning mode.

    Args:
        condition_type: Canonical condition or alias.
        mask: Valid-element mask.
        generator: Optional generator used by completion masking.

    Returns:
        Long tensor where ``1`` means generated and ``0`` means conditioned.

    Raises:
        ValueError: If the condition type is unsupported.
    """
    batch, seq = mask.shape
    cond_mask = torch.ones(
        batch,
        seq,
        self.config.sample_dim,
        dtype=torch.long,
        device=mask.device,
    )
    if condition_type in {ConditionType.label, ConditionType.refinement}:
        cond_mask[:, :, 4:] = 0
    elif condition_type is ConditionType.label_size:
        cond_mask[:, :, 2:] = 0
    elif condition_type is ConditionType.completion:
        cond_mask = self._completion_mask(cond_mask, mask, generator)
    elif condition_type is ConditionType.unconditional:
        pass
    else:
        raise ValueError(f"Unsupported LayoutFlow condition_type: {condition_type}")

    return cond_mask

postprocess

postprocess(
    state: Float[Tensor, "batch elements channels"],
    *,
    mask: Bool[Tensor, "batch elements"],
    box_format: BoxFormat | str = "xywh",
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]

Convert model state back to public layout tensors.

Parameters:

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

Model state tensor.

required
mask Bool[Tensor, 'batch elements']

Valid-element mask.

required
box_format BoxFormat | str

Requested output box format.

'xywh'
normalized bool

Whether to return normalized coordinates.

True
canvas_size tuple[int, int] | None

Pixel canvas size for denormalized coordinates.

None

Returns:

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

Dictionary with bbox, labels, and mask.

Raises:

Type Description
ValueError

If denormalized output is requested without canvas_size.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def postprocess(
    self,
    state: Float[torch.Tensor, "batch elements channels"],
    *,
    mask: Bool[torch.Tensor, "batch elements"],
    box_format: BoxFormat | str = "xywh",
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Convert model state back to public layout tensors.

    Args:
        state: Model state tensor.
        mask: Valid-element mask.
        box_format: Requested output box format.
        normalized: Whether to return normalized coordinates.
        canvas_size: Pixel canvas size for denormalized coordinates.

    Returns:
        Dictionary with ``bbox``, ``labels``, and ``mask``.

    Raises:
        ValueError: If denormalized output is requested without
            ``canvas_size``.
    """
    restored = self.preprocess_state(state, reverse=True)
    bbox = clamp_boxes(restored[:, :, :4]) * mask.unsqueeze(-1)
    labels = self.decode_labels(restored[:, :, 4:]) * mask.long()
    fmt = BoxFormat(box_format)
    if fmt is not BoxFormat.xywh:
        if not normalized and canvas_size is None:
            raise ValueError("canvas_size is required for denormalized output")

        if canvas_size is None:
            canvas_size = (1, 1)
        bbox = denormalize_boxes(bbox, canvas_size=canvas_size, box_format=fmt)
        if normalized:
            scale = torch.tensor(
                (*canvas_size, *canvas_size), dtype=bbox.dtype, device=bbox.device
            )
            bbox = bbox / scale
    elif not normalized:
        if canvas_size is None:
            raise ValueError("canvas_size is required for denormalized output")

        bbox = denormalize_boxes(
            bbox, canvas_size=canvas_size, box_format=BoxFormat.xywh
        )
    return {"bbox": bbox, "labels": labels, "mask": mask}

LayoutFlowEulerScheduler

Bases: SchedulerMixin, ConfigMixin

Increasing-time Euler scheduler used by LayoutFlow.

Source code in models/layout-flow/src/layout_flow/scheduling_layout_flow.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 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
class LayoutFlowEulerScheduler(SchedulerMixin, ConfigMixin):
    """Increasing-time Euler scheduler used by LayoutFlow."""

    config_name: str = "scheduler_config.json"
    order: int = 1

    @register_to_config
    def __init__(
        self, num_inference_steps: int = 100, start: float = 0.0, end: float = 1.0
    ) -> None:
        """Initialize the scheduler.

        Args:
            num_inference_steps: Number of Euler steps.
            start: Initial integration time.
            end: Final integration time.
        """
        self.num_inference_steps = num_inference_steps
        self.start = start
        self.end = end
        self.timesteps = torch.linspace(start, end, num_inference_steps)

    def set_timesteps(
        self,
        num_inference_steps: int | None = None,
        *,
        device: torch.device | str | None = None,
        start: float | None = None,
        end: float | None = None,
    ) -> None:
        """Set the integration timesteps.

        Args:
            num_inference_steps: Optional number of inference steps.
            device: Optional target device.
            start: Optional start time.
            end: Optional end time.
        """
        steps = num_inference_steps or self.config.num_inference_steps
        start = self.config.start if start is None else start
        end = self.config.end if end is None else end
        self.timesteps = torch.linspace(start, end, steps, device=device)

    def scale_model_input(
        self,
        sample: Float[torch.Tensor, "batch elements channels"],
        timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Return the sample unchanged for Diffusers scheduler compatibility."""
        del timestep
        return sample

    @overload
    def step(
        self,
        model_output: Float[torch.Tensor, "batch elements channels"],
        timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
        sample: Float[torch.Tensor, "batch elements channels"],
        *,
        next_timestep: Float[torch.Tensor, ""]
        | Float[torch.Tensor, "batch"]
        | float
        | None = None,
        return_dict: Literal[True] = True,
    ) -> LayoutFlowSchedulerOutput: ...

    @overload
    def step(
        self,
        model_output: Float[torch.Tensor, "batch elements channels"],
        timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
        sample: Float[torch.Tensor, "batch elements channels"],
        *,
        next_timestep: Float[torch.Tensor, ""]
        | Float[torch.Tensor, "batch"]
        | float
        | None = None,
        return_dict: Literal[False],
    ) -> tuple[Float[torch.Tensor, "batch elements channels"]]: ...

    def step(
        self,
        model_output: Float[torch.Tensor, "batch elements channels"],
        timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
        sample: Float[torch.Tensor, "batch elements channels"],
        *,
        next_timestep: Float[torch.Tensor, ""]
        | Float[torch.Tensor, "batch"]
        | float
        | None = None,
        return_dict: bool = True,
    ) -> (
        LayoutFlowSchedulerOutput
        | tuple[Float[torch.Tensor, "batch elements channels"]]
    ):
        """Advance the sample with one Euler step.

        Args:
            model_output: Predicted vector field.
            timestep: Current integration time.
            sample: Current sample state.
            next_timestep: Optional next integration time.
            return_dict: Whether to return a scheduler output dataclass.

        Returns:
            Scheduler output dataclass or single-item tuple.
        """
        t = torch.as_tensor(timestep, device=sample.device, dtype=sample.dtype)
        if next_timestep is None:
            matches = torch.isclose(self.timesteps.to(sample.device, sample.dtype), t)
            idx = int(matches.nonzero()[0].item())
            if idx >= len(self.timesteps) - 1:
                next_timestep = t
            else:
                next_timestep = self.timesteps[idx + 1]
        t_next = torch.as_tensor(
            next_timestep, device=sample.device, dtype=sample.dtype
        )
        prev_sample = sample + (t_next - t) * model_output
        if not return_dict:
            return (prev_sample,)
        return LayoutFlowSchedulerOutput(prev_sample=prev_sample)

__init__

__init__(
    num_inference_steps: int = 100,
    start: float = 0.0,
    end: float = 1.0,
) -> None

Initialize the scheduler.

Parameters:

Name Type Description Default
num_inference_steps int

Number of Euler steps.

100
start float

Initial integration time.

0.0
end float

Final integration time.

1.0
Source code in models/layout-flow/src/layout_flow/scheduling_layout_flow.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@register_to_config
def __init__(
    self, num_inference_steps: int = 100, start: float = 0.0, end: float = 1.0
) -> None:
    """Initialize the scheduler.

    Args:
        num_inference_steps: Number of Euler steps.
        start: Initial integration time.
        end: Final integration time.
    """
    self.num_inference_steps = num_inference_steps
    self.start = start
    self.end = end
    self.timesteps = torch.linspace(start, end, num_inference_steps)

set_timesteps

set_timesteps(
    num_inference_steps: int | None = None,
    *,
    device: device | str | None = None,
    start: float | None = None,
    end: float | None = None,
) -> None

Set the integration timesteps.

Parameters:

Name Type Description Default
num_inference_steps int | None

Optional number of inference steps.

None
device device | str | None

Optional target device.

None
start float | None

Optional start time.

None
end float | None

Optional end time.

None
Source code in models/layout-flow/src/layout_flow/scheduling_layout_flow.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def set_timesteps(
    self,
    num_inference_steps: int | None = None,
    *,
    device: torch.device | str | None = None,
    start: float | None = None,
    end: float | None = None,
) -> None:
    """Set the integration timesteps.

    Args:
        num_inference_steps: Optional number of inference steps.
        device: Optional target device.
        start: Optional start time.
        end: Optional end time.
    """
    steps = num_inference_steps or self.config.num_inference_steps
    start = self.config.start if start is None else start
    end = self.config.end if end is None else end
    self.timesteps = torch.linspace(start, end, steps, device=device)

scale_model_input

scale_model_input(
    sample: Float[Tensor, "batch elements channels"],
    timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float,
) -> Float[torch.Tensor, "batch elements channels"]

Return the sample unchanged for Diffusers scheduler compatibility.

Source code in models/layout-flow/src/layout_flow/scheduling_layout_flow.py
65
66
67
68
69
70
71
72
def scale_model_input(
    self,
    sample: Float[torch.Tensor, "batch elements channels"],
    timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Return the sample unchanged for Diffusers scheduler compatibility."""
    del timestep
    return sample

step

step(
    model_output: Float[Tensor, "batch elements channels"],
    timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float,
    sample: Float[Tensor, "batch elements channels"],
    *,
    next_timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float
    | None = None,
    return_dict: Literal[True] = True,
) -> LayoutFlowSchedulerOutput
step(
    model_output: Float[Tensor, "batch elements channels"],
    timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float,
    sample: Float[Tensor, "batch elements channels"],
    *,
    next_timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float
    | None = None,
    return_dict: Literal[False],
) -> tuple[Float[torch.Tensor, "batch elements channels"]]
step(
    model_output: Float[Tensor, "batch elements channels"],
    timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float,
    sample: Float[Tensor, "batch elements channels"],
    *,
    next_timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float
    | None = None,
    return_dict: bool = True,
) -> (
    LayoutFlowSchedulerOutput
    | tuple[Float[torch.Tensor, "batch elements channels"]]
)

Advance the sample with one Euler step.

Parameters:

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

Predicted vector field.

required
timestep Float[Tensor, ''] | Float[Tensor, 'batch'] | float

Current integration time.

required
sample Float[Tensor, 'batch elements channels']

Current sample state.

required
next_timestep Float[Tensor, ''] | Float[Tensor, 'batch'] | float | None

Optional next integration time.

None
return_dict bool

Whether to return a scheduler output dataclass.

True

Returns:

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

Scheduler output dataclass or single-item tuple.

Source code in models/layout-flow/src/layout_flow/scheduling_layout_flow.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def step(
    self,
    model_output: Float[torch.Tensor, "batch elements channels"],
    timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
    sample: Float[torch.Tensor, "batch elements channels"],
    *,
    next_timestep: Float[torch.Tensor, ""]
    | Float[torch.Tensor, "batch"]
    | float
    | None = None,
    return_dict: bool = True,
) -> (
    LayoutFlowSchedulerOutput
    | tuple[Float[torch.Tensor, "batch elements channels"]]
):
    """Advance the sample with one Euler step.

    Args:
        model_output: Predicted vector field.
        timestep: Current integration time.
        sample: Current sample state.
        next_timestep: Optional next integration time.
        return_dict: Whether to return a scheduler output dataclass.

    Returns:
        Scheduler output dataclass or single-item tuple.
    """
    t = torch.as_tensor(timestep, device=sample.device, dtype=sample.dtype)
    if next_timestep is None:
        matches = torch.isclose(self.timesteps.to(sample.device, sample.dtype), t)
        idx = int(matches.nonzero()[0].item())
        if idx >= len(self.timesteps) - 1:
            next_timestep = t
        else:
            next_timestep = self.timesteps[idx + 1]
    t_next = torch.as_tensor(
        next_timestep, device=sample.device, dtype=sample.dtype
    )
    prev_sample = sample + (t_next - t) * model_output
    if not return_dict:
        return (prev_sample,)
    return LayoutFlowSchedulerOutput(prev_sample=prev_sample)

normalize_condition_type

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

Normalize condition aliases to a canonical ConditionType.

Parameters:

Name Type Description Default
condition_type ConditionType | str

Canonical condition enum or a public/release alias.

required

Returns:

Type Description
ConditionType

Canonical condition enum.

Raises:

Type Description
ValueError

If the condition type is unknown.

Examples:

>>> str(normalize_condition_type("gen_t"))
'label'
>>> str(normalize_condition_type("gen_r"))
'relation'
Source code in lib/laygen/src/laygen/common/conditions.py
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 normalize_condition_type(condition_type: ConditionType | str) -> ConditionType:
    """Normalize condition aliases to a canonical ``ConditionType``.

    Args:
        condition_type: Canonical condition enum or a public/release alias.

    Returns:
        Canonical condition enum.

    Raises:
        ValueError: If the condition type is unknown.

    Examples:
        >>> str(normalize_condition_type("gen_t"))
        'label'
        >>> str(normalize_condition_type("gen_r"))
        'relation'
    """
    if isinstance(condition_type, ConditionType):
        return condition_type
    try:
        return _CONDITION_ALIASES[
            ConditionAlias(condition_type.lower().replace("-", "_"))
        ]
    except ValueError as exc:
        raise ValueError(f"Unknown condition_type: {condition_type}") from exc

configuration_layout_flow

Configuration objects and dataset metadata for LayoutFlow.

AttrEncoding

Bases: StrEnum

Closed set of LayoutFlow attribute encodings.

Source code in models/layout-flow/src/layout_flow/configuration_layout_flow.py
18
19
20
21
22
23
class AttrEncoding(StrEnum):
    """Closed set of LayoutFlow attribute encodings."""

    analog_bit = "AnalogBit"
    continuous = auto()
    discrete = auto()

SeqType

Bases: StrEnum

Closed set of LayoutFlow sequence layouts.

Source code in models/layout-flow/src/layout_flow/configuration_layout_flow.py
26
27
28
29
30
31
class SeqType(StrEnum):
    """Closed set of LayoutFlow sequence layouts."""

    stacked = auto()
    seq = auto()
    seq_cond = auto()

InitialDistributionName

Bases: StrEnum

Closed set of initial-state distributions accepted by LayoutFlow config.

Source code in models/layout-flow/src/layout_flow/configuration_layout_flow.py
34
35
36
37
38
39
40
class InitialDistributionName(StrEnum):
    """Closed set of initial-state distributions accepted by LayoutFlow config."""

    gaussian = auto()
    uniform = auto()
    gmm = auto()
    gauss_uniform = auto()

OdeSolverName

Bases: StrEnum

Closed set of ODE solvers accepted by LayoutFlow config.

Source code in models/layout-flow/src/layout_flow/configuration_layout_flow.py
43
44
45
46
class OdeSolverName(StrEnum):
    """Closed set of ODE solvers accepted by LayoutFlow config."""

    euler = auto()

CoordinateRange

Bases: StrEnum

Closed set of public coordinate ranges.

Source code in models/layout-flow/src/layout_flow/configuration_layout_flow.py
49
50
51
52
class CoordinateRange(StrEnum):
    """Closed set of public coordinate ranges."""

    normalized_0_1 = auto()

LayoutFlowConfig

Bases: ConfigMixin

Configuration saved with converted LayoutFlow pipelines.

Source code in models/layout-flow/src/layout_flow/configuration_layout_flow.py
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
class LayoutFlowConfig(ConfigMixin):
    """Configuration saved with converted LayoutFlow pipelines."""

    config_name: str = "layout_flow_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: str = "publaynet",
        id2label: dict[int | str, str] | None = None,
        max_length: int = 20,
        latent_dim: int = 128,
        d_model: int = 512,
        nhead: int = 8,
        dim_feedforward: int = 2048,
        num_layers: int = 4,
        dropout: float = 0.1,
        use_pos_enc: bool = False,
        tr_enc_only: bool = True,
        attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
        seq_type: SeqType = SeqType.stacked,
        distribution: InitialDistributionName = InitialDistributionName.gaussian,
        sample_padding: bool = False,
        inference_steps: int = 100,
        ode_solver: OdeSolverName = OdeSolverName.euler,
        bbox_format: BoxFormat | str = "xywh",
        coordinate_range: CoordinateRange = CoordinateRange.normalized_0_1,
    ) -> None:
        """Initialize LayoutFlow pipeline and model settings.

        Args:
            dataset_name: Dataset variant or alias.
            id2label: Optional explicit id-to-label mapping.
            max_length: Maximum number of layout elements.
            latent_dim: Latent dimension.
            d_model: Transformer hidden size.
            nhead: Number of attention heads.
            dim_feedforward: Feed-forward hidden size.
            num_layers: Number of transformer layers.
            dropout: Dropout probability.
            use_pos_enc: Whether to add sinusoidal position encodings.
            tr_enc_only: Whether to use the encoder-only path.
            attr_encoding: Attribute encoding used by the checkpoint.
            seq_type: Sequence layout type.
            distribution: Initial sampling distribution.
            sample_padding: Whether sampling includes padded elements.
            inference_steps: Default Euler inference steps.
            ode_solver: ODE solver name.
            bbox_format: Public bounding-box format.
            coordinate_range: Public coordinate range.

        Raises:
            ValueError: If ``dataset_name`` is unsupported.
        """
        self.dataset_name = str(normalize_dataset_name(dataset_name))
        raw_id2label = id2label or default_id2label(self.dataset_name)
        self.id2label = {int(k): v for k, v in raw_id2label.items()}

        self.max_length = max_length
        self.latent_dim = latent_dim
        self.d_model = d_model
        self.nhead = nhead
        self.dim_feedforward = dim_feedforward
        self.num_layers = num_layers
        self.dropout = dropout
        self.use_pos_enc = use_pos_enc
        self.tr_enc_only = tr_enc_only

        self.attr_encoding = str(AttrEncoding(attr_encoding))
        self.seq_type = str(SeqType(seq_type))
        self.distribution = str(InitialDistributionName(distribution))
        self.sample_padding = sample_padding

        self.inference_steps = inference_steps
        self.ode_solver = str(OdeSolverName(ode_solver))
        self.bbox_format = str(BoxFormat(bbox_format))
        self.coordinate_range = str(CoordinateRange(coordinate_range))

    @property
    def label2id(self) -> dict[str, int]:
        """Return the label-name to integer-id mapping."""
        return {v: k for k, v in self.id2label.items()}

    @property
    def num_labels(self) -> int:
        """Return the number of labels, including the background label."""
        return len(self.id2label)

    @property
    def attr_dim(self) -> int:
        """Return the analog-bit attribute dimensionality."""
        if AttrEncoding(self.attr_encoding) is AttrEncoding.analog_bit:
            return int(math.ceil(math.log2(self.num_labels)))
        return 1

    @property
    def sample_dim(self) -> int:
        """Return the model-state dimensionality per layout element."""
        return 4 + self.attr_dim

label2id property

label2id: dict[str, int]

Return the label-name to integer-id mapping.

num_labels property

num_labels: int

Return the number of labels, including the background label.

attr_dim property

attr_dim: int

Return the analog-bit attribute dimensionality.

sample_dim property

sample_dim: int

Return the model-state dimensionality per layout element.

__init__

__init__(
    *,
    dataset_name: str = "publaynet",
    id2label: dict[int | str, str] | None = None,
    max_length: int = 20,
    latent_dim: int = 128,
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    num_layers: int = 4,
    dropout: float = 0.1,
    use_pos_enc: bool = False,
    tr_enc_only: bool = True,
    attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
    seq_type: SeqType = SeqType.stacked,
    distribution: InitialDistributionName = InitialDistributionName.gaussian,
    sample_padding: bool = False,
    inference_steps: int = 100,
    ode_solver: OdeSolverName = OdeSolverName.euler,
    bbox_format: BoxFormat | str = "xywh",
    coordinate_range: CoordinateRange = CoordinateRange.normalized_0_1,
) -> None

Initialize LayoutFlow pipeline and model settings.

Parameters:

Name Type Description Default
dataset_name str

Dataset variant or alias.

'publaynet'
id2label dict[int | str, str] | None

Optional explicit id-to-label mapping.

None
max_length int

Maximum number of layout elements.

20
latent_dim int

Latent dimension.

128
d_model int

Transformer hidden size.

512
nhead int

Number of attention heads.

8
dim_feedforward int

Feed-forward hidden size.

2048
num_layers int

Number of transformer layers.

4
dropout float

Dropout probability.

0.1
use_pos_enc bool

Whether to add sinusoidal position encodings.

False
tr_enc_only bool

Whether to use the encoder-only path.

True
attr_encoding AttrEncoding

Attribute encoding used by the checkpoint.

analog_bit
seq_type SeqType

Sequence layout type.

stacked
distribution InitialDistributionName

Initial sampling distribution.

gaussian
sample_padding bool

Whether sampling includes padded elements.

False
inference_steps int

Default Euler inference steps.

100
ode_solver OdeSolverName

ODE solver name.

euler
bbox_format BoxFormat | str

Public bounding-box format.

'xywh'
coordinate_range CoordinateRange

Public coordinate range.

normalized_0_1

Raises:

Type Description
ValueError

If dataset_name is unsupported.

Source code in models/layout-flow/src/layout_flow/configuration_layout_flow.py
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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: str = "publaynet",
    id2label: dict[int | str, str] | None = None,
    max_length: int = 20,
    latent_dim: int = 128,
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    num_layers: int = 4,
    dropout: float = 0.1,
    use_pos_enc: bool = False,
    tr_enc_only: bool = True,
    attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
    seq_type: SeqType = SeqType.stacked,
    distribution: InitialDistributionName = InitialDistributionName.gaussian,
    sample_padding: bool = False,
    inference_steps: int = 100,
    ode_solver: OdeSolverName = OdeSolverName.euler,
    bbox_format: BoxFormat | str = "xywh",
    coordinate_range: CoordinateRange = CoordinateRange.normalized_0_1,
) -> None:
    """Initialize LayoutFlow pipeline and model settings.

    Args:
        dataset_name: Dataset variant or alias.
        id2label: Optional explicit id-to-label mapping.
        max_length: Maximum number of layout elements.
        latent_dim: Latent dimension.
        d_model: Transformer hidden size.
        nhead: Number of attention heads.
        dim_feedforward: Feed-forward hidden size.
        num_layers: Number of transformer layers.
        dropout: Dropout probability.
        use_pos_enc: Whether to add sinusoidal position encodings.
        tr_enc_only: Whether to use the encoder-only path.
        attr_encoding: Attribute encoding used by the checkpoint.
        seq_type: Sequence layout type.
        distribution: Initial sampling distribution.
        sample_padding: Whether sampling includes padded elements.
        inference_steps: Default Euler inference steps.
        ode_solver: ODE solver name.
        bbox_format: Public bounding-box format.
        coordinate_range: Public coordinate range.

    Raises:
        ValueError: If ``dataset_name`` is unsupported.
    """
    self.dataset_name = str(normalize_dataset_name(dataset_name))
    raw_id2label = id2label or default_id2label(self.dataset_name)
    self.id2label = {int(k): v for k, v in raw_id2label.items()}

    self.max_length = max_length
    self.latent_dim = latent_dim
    self.d_model = d_model
    self.nhead = nhead
    self.dim_feedforward = dim_feedforward
    self.num_layers = num_layers
    self.dropout = dropout
    self.use_pos_enc = use_pos_enc
    self.tr_enc_only = tr_enc_only

    self.attr_encoding = str(AttrEncoding(attr_encoding))
    self.seq_type = str(SeqType(seq_type))
    self.distribution = str(InitialDistributionName(distribution))
    self.sample_padding = sample_padding

    self.inference_steps = inference_steps
    self.ode_solver = str(OdeSolverName(ode_solver))
    self.bbox_format = str(BoxFormat(bbox_format))
    self.coordinate_range = str(CoordinateRange(coordinate_range))

normalize_dataset_name

normalize_dataset_name(
    dataset_name: DatasetName | str,
) -> DatasetName

Normalize LayoutFlow dataset aliases.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset enum value or string alias.

required

Returns:

Type Description
DatasetName

Canonical shared dataset enum.

Raises:

Type Description
ValueError

If the dataset name is unsupported.

Examples:

>>> str(normalize_dataset_name("rico25_max25"))
'rico25'
Source code in models/layout-flow/src/layout_flow/configuration_layout_flow.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def normalize_dataset_name(dataset_name: DatasetName | str) -> DatasetName:
    """Normalize LayoutFlow dataset aliases.

    Args:
        dataset_name: Dataset enum value or string alias.

    Returns:
        Canonical shared dataset enum.

    Raises:
        ValueError: If the dataset name is unsupported.

    Examples:
        >>> str(normalize_dataset_name("rico25_max25"))
        'rico25'
    """
    return normalize_shared_dataset_name(dataset_name)

default_id2label

default_id2label(
    dataset_name: DatasetName | str,
) -> dict[int, str]

Return the LayoutFlow label vocabulary for a dataset.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset enum value or string alias.

required

Returns:

Type Description
dict[int, str]

Integer-id to label-name mapping.

Raises:

Type Description
ValueError

If the dataset name is unsupported.

Examples:

>>> default_id2label("publaynet")[1]
'text'
Source code in models/layout-flow/src/layout_flow/configuration_layout_flow.py
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
def default_id2label(dataset_name: DatasetName | str) -> dict[int, str]:
    """Return the LayoutFlow label vocabulary for a dataset.

    Args:
        dataset_name: Dataset enum value or string alias.

    Returns:
        Integer-id to label-name mapping.

    Raises:
        ValueError: If the dataset name is unsupported.

    Examples:
        >>> default_id2label("publaynet")[1]
        'text'
    """
    dataset = normalize_dataset_name(dataset_name)
    if dataset is DatasetName.rico25:
        labels = RICO25_LAYOUT_FLOW_LABELS
    elif dataset is DatasetName.publaynet:
        labels = PUBLAYNET_LAYOUT_FLOW_LABELS
    else:
        raise ValueError(f"Unsupported LayoutFlow dataset_name: {dataset_name}")

    return dict(enumerate(labels))

conversion

Checkpoint conversion helpers for LayoutFlow.

build_pipeline

build_pipeline(
    config: LayoutFlowConfig,
) -> LayoutFlowPipeline

Build a randomly initialized pipeline for a LayoutFlow config.

Parameters:

Name Type Description Default
config LayoutFlowConfig

LayoutFlow configuration.

required

Returns:

Type Description
LayoutFlowPipeline

Pipeline with model and scheduler modules initialized from config.

Examples:

>>> pipe = build_pipeline(LayoutFlowConfig(max_length=2, latent_dim=8, d_model=16))
>>> pipe.layout_flow_config.max_length
2
Source code in models/layout-flow/src/layout_flow/conversion.py
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
def build_pipeline(config: LayoutFlowConfig) -> LayoutFlowPipeline:
    """Build a randomly initialized pipeline for a LayoutFlow config.

    Args:
        config: LayoutFlow configuration.

    Returns:
        Pipeline with model and scheduler modules initialized from ``config``.

    Examples:
        >>> pipe = build_pipeline(LayoutFlowConfig(max_length=2, latent_dim=8, d_model=16))
        >>> pipe.layout_flow_config.max_length
        2
    """
    model = LayoutFlowTransformerModel(
        num_labels=config.num_labels,
        latent_dim=config.latent_dim,
        tr_enc_only=config.tr_enc_only,
        d_model=config.d_model,
        nhead=config.nhead,
        dim_feedforward=config.dim_feedforward,
        num_layers=config.num_layers,
        dropout=config.dropout,
        use_pos_enc=config.use_pos_enc,
        attr_encoding=config.attr_encoding,
        seq_type=config.seq_type,
    )
    scheduler = LayoutFlowEulerScheduler(num_inference_steps=config.inference_steps)
    return LayoutFlowPipeline(model=model, scheduler=scheduler, config=config)

convert_lightning_state_dict

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

Convert original Lightning checkpoint keys to local model keys.

Parameters:

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

Original LayoutFlow Lightning state dict.

required

Returns:

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

State dict keyed for LayoutFlowTransformerModel.

Examples:

>>> import torch
>>> out = convert_lightning_state_dict({"model.linear.weight": torch.zeros(1)})
>>> list(out)
['backbone.linear.weight']
>>> out = convert_lightning_state_dict({"model.backbone.linear.weight": torch.zeros(1)})
>>> list(out)
['backbone.linear.weight']
Source code in models/layout-flow/src/layout_flow/conversion.py
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
def convert_lightning_state_dict(
    state_dict: dict[str, Shaped[torch.Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Convert original Lightning checkpoint keys to local model keys.

    Args:
        state_dict: Original LayoutFlow Lightning state dict.

    Returns:
        State dict keyed for ``LayoutFlowTransformerModel``.

    Examples:
        >>> import torch
        >>> out = convert_lightning_state_dict({"model.linear.weight": torch.zeros(1)})
        >>> list(out)
        ['backbone.linear.weight']
        >>> out = convert_lightning_state_dict({"model.backbone.linear.weight": torch.zeros(1)})
        >>> list(out)
        ['backbone.linear.weight']
    """
    converted: dict[str, Shaped[torch.Tensor, "..."]] = {}
    for key, value in state_dict.items():
        if key.startswith("model.backbone."):
            converted[f"backbone.{key.removeprefix('model.backbone.')}"] = value
        elif key.startswith("model."):
            converted[f"backbone.{key.removeprefix('model.')}"] = value
    return converted

model_card

Model-card generation for converted LayoutFlow checkpoints.

layoutflow_model_card

layoutflow_model_card(dataset: str) -> ModelCard

Build a model card for a converted LayoutFlow checkpoint.

Parameters:

Name Type Description Default
dataset str

LayoutFlow dataset name or alias.

required

Returns:

Type Description
ModelCard

Validated Hugging Face model card.

Raises:

Type Description
ValueError

If dataset is unsupported.

Examples:

>>> card = layoutflow_model_card("publaynet")
>>> card.data.to_dict()["library_name"]
'diffusers'
Source code in models/layout-flow/src/layout_flow/model_card.py
 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
def layoutflow_model_card(dataset: str) -> ModelCard:
    """Build a model card for a converted LayoutFlow checkpoint.

    Args:
        dataset: LayoutFlow dataset name or alias.

    Returns:
        Validated Hugging Face model card.

    Raises:
        ValueError: If ``dataset`` is unsupported.

    Examples:
        >>> card = layoutflow_model_card("publaynet")
        >>> card.data.to_dict()["library_name"]
        'diffusers'
    """
    dataset_name = normalize_dataset_name(dataset)
    dataset_id = _dataset_id(dataset_name)
    dataset_value = str(dataset_name)
    model_id = f"creative-graphic-design/layout-flow-{dataset_value}"
    how_to_use = f"""
from layout_flow import LayoutFlowPipeline

pipe = LayoutFlowPipeline.from_pretrained("{model_id}")
out = pipe(batch_size=1, num_elements=8, seed=0, num_inference_steps=100)
print(out.bbox, out.labels, out.mask, out.id2label)
"""
    return build_layout_model_card(
        model_id=model_id,
        model_name=f"LayoutFlow {dataset_value}",
        dataset_ids=[dataset_id],
        license="mit",
        library_name="diffusers",
        pipeline_tag="other",
        tags=[
            "layout-generation",
            "layout-flow",
            "flow-matching",
            "diffusers",
            dataset_value,
        ],
        model_details=(
            "Diffusers-format conversion of the LayoutFlow checkpoint for "
            f"`{dataset_value}`. LayoutFlow is a flow-matching layout generator that "
            "integrates a learned vector field over continuous geometry and "
            "analog-bit category labels. Public outputs are normalized center "
            "`xywh` boxes, dataset-local labels, masks, and `id2label` metadata."
        ),
        intended_uses=(
            "Use this checkpoint for research and evaluation of UI/document "
            "layout generation and controllable layout completion workflows."
        ),
        limitations=(
            "This checkpoint generates layout geometry and category labels only; "
            "it does not render images or text. Generation is stochastic unless a "
            "`torch.Generator` or `seed` is supplied. The converted artifact "
            "preserves the original checkpoint behavior and should be evaluated "
            "within the original dataset domain. Do not use it as an image "
            "renderer, OCR system, accessibility checker, safety classifier, or "
            "unreviewed production UI generator."
        ),
        how_to_use=how_to_use,
        training_data=(
            f"The original checkpoint was trained on `{dataset_id}` using the "
            "splits distributed by the LayoutFlow authors through "
            "`JulianGuerreiro/LayoutFlow`."
        ),
        parity_metrics=[
            {
                "dataset": dataset_value,
                "tokenizer_exact": "n/a",
                "deterministic_exact": "Euler trajectory not measured by parity test",
                "logits_max_abs": 0.0,
                "logits_max_rel": 0.0,
            }
        ],
        citation_bibtex=LAYOUTFLOW_BIBTEX,
        original_implementation_url="https://github.com/julianguerreiro/LayoutFlow",
    )

save_layoutflow_model_card

save_layoutflow_model_card(
    output_dir: str | Path, *, dataset: str
) -> Path

Write a LayoutFlow model card as README.md.

Parameters:

Name Type Description Default
output_dir str | Path

Directory that receives README.md.

required
dataset str

LayoutFlow dataset name or alias.

required

Returns:

Type Description
Path

Path to the written README.

Raises:

Type Description
ValueError

If dataset is unsupported.

Examples:

>>> from tempfile import TemporaryDirectory
>>> with TemporaryDirectory() as tmp:
...     path = save_layoutflow_model_card(tmp, dataset="publaynet")
...     path.name
'README.md'
Source code in models/layout-flow/src/layout_flow/model_card.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def save_layoutflow_model_card(output_dir: str | Path, *, dataset: str) -> Path:
    """Write a LayoutFlow model card as ``README.md``.

    Args:
        output_dir: Directory that receives ``README.md``.
        dataset: LayoutFlow dataset name or alias.

    Returns:
        Path to the written README.

    Raises:
        ValueError: If ``dataset`` is unsupported.

    Examples:
        >>> from tempfile import TemporaryDirectory
        >>> with TemporaryDirectory() as tmp:
        ...     path = save_layoutflow_model_card(tmp, dataset="publaynet")
        ...     path.name
        'README.md'
    """
    output_path = Path(output_dir) / "README.md"
    output_path.write_text(str(layoutflow_model_card(dataset)), encoding="utf-8")
    return output_path

modeling_layout_flow

PyTorch modules for the converted LayoutFlow vector-field model.

PositionalEncoding

Bases: Module

Sinusoidal positional encoding used by the LayoutFlow backbone.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.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
class PositionalEncoding(nn.Module):
    """Sinusoidal positional encoding used by the LayoutFlow backbone."""

    pe: Float[torch.Tensor, "1 tokens channels"]

    def __init__(
        self, d_model: int, dropout: float = 0.1, max_len: int = 10000
    ) -> None:
        """Initialize positional encodings."""
        super().__init__()
        self.dropout = nn.Dropout(p=dropout)
        position = torch.arange(max_len).unsqueeze(1)
        div_term = torch.exp(
            torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model)
        )
        pe = torch.zeros(1, max_len, d_model)
        pe[0, :, 0::2] = torch.sin(position * div_term)
        pe[0, :, 1::2] = torch.cos(position * div_term)
        self.register_buffer("pe", pe)

    def forward(
        self, x: Float[torch.Tensor, "batch tokens channels"]
    ) -> Float[torch.Tensor, "1 tokens channels"]:
        """Return positional encodings matching the sequence length of ``x``."""
        return self.dropout(self.pe[:, : x.shape[1]])

__init__

__init__(
    d_model: int, dropout: float = 0.1, max_len: int = 10000
) -> None

Initialize positional encodings.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def __init__(
    self, d_model: int, dropout: float = 0.1, max_len: int = 10000
) -> None:
    """Initialize positional encodings."""
    super().__init__()
    self.dropout = nn.Dropout(p=dropout)
    position = torch.arange(max_len).unsqueeze(1)
    div_term = torch.exp(
        torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model)
    )
    pe = torch.zeros(1, max_len, d_model)
    pe[0, :, 0::2] = torch.sin(position * div_term)
    pe[0, :, 1::2] = torch.cos(position * div_term)
    self.register_buffer("pe", pe)

forward

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

Return positional encodings matching the sequence length of x.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
62
63
64
65
66
def forward(
    self, x: Float[torch.Tensor, "batch tokens channels"]
) -> Float[torch.Tensor, "1 tokens channels"]:
    """Return positional encodings matching the sequence length of ``x``."""
    return self.dropout(self.pe[:, : x.shape[1]])

AdaLayerNorm

Bases: Module

Adaptive layer norm conditioned on the integration timestep.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
class AdaLayerNorm(nn.Module):
    """Adaptive layer norm conditioned on the integration timestep."""

    def __init__(self, n_embd: int) -> None:
        """Initialize timestep-conditioned normalization."""
        super().__init__()
        self.emb = nn.Sequential(
            nn.Unflatten(0, (-1, 1)),
            nn.Linear(1, n_embd // 2),
            nn.ReLU(),
            nn.Linear(n_embd // 2, n_embd),
        )
        self.silu = nn.SiLU()
        self.linear = nn.Linear(n_embd, n_embd * 2)
        self.layernorm = nn.LayerNorm(n_embd, elementwise_affine=False)

    def forward(
        self,
        x: Float[torch.Tensor, "batch tokens channels"],
        timestep: Float[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Normalize ``x`` with scale and shift predicted from ``timestep``."""
        emb = self.linear(self.silu(self.emb(timestep))).unsqueeze(1)
        scale, shift = torch.chunk(emb, 2, dim=2)
        return self.layernorm(x) * (1 + scale) + shift

__init__

__init__(n_embd: int) -> None

Initialize timestep-conditioned normalization.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
72
73
74
75
76
77
78
79
80
81
82
83
def __init__(self, n_embd: int) -> None:
    """Initialize timestep-conditioned normalization."""
    super().__init__()
    self.emb = nn.Sequential(
        nn.Unflatten(0, (-1, 1)),
        nn.Linear(1, n_embd // 2),
        nn.ReLU(),
        nn.Linear(n_embd // 2, n_embd),
    )
    self.silu = nn.SiLU()
    self.linear = nn.Linear(n_embd, n_embd * 2)
    self.layernorm = nn.LayerNorm(n_embd, elementwise_affine=False)

forward

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

Normalize x with scale and shift predicted from timestep.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
85
86
87
88
89
90
91
92
93
def forward(
    self,
    x: Float[torch.Tensor, "batch tokens channels"],
    timestep: Float[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Normalize ``x`` with scale and shift predicted from ``timestep``."""
    emb = self.linear(self.silu(self.emb(timestep))).unsqueeze(1)
    scale, shift = torch.chunk(emb, 2, dim=2)
    return self.layernorm(x) * (1 + scale) + shift

LayoutFlowBlock

Bases: Module

Transformer encoder block used by the LayoutFlow backbone.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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
class LayoutFlowBlock(nn.Module):
    """Transformer encoder block used by the LayoutFlow backbone."""

    def __init__(
        self,
        d_model: int = 1024,
        nhead: int = 16,
        dim_feedforward: int = 2048,
        dropout: float = 0.0,
        activation: str
        | Callable[[Shaped[torch.Tensor, "..."]], Shaped[torch.Tensor, "..."]] = F.relu,
        batch_first: bool = False,
        norm_first: bool = False,
    ) -> None:
        """Initialize one LayoutFlow transformer block."""
        super().__init__()
        if not norm_first:
            raise ValueError("LayoutFlow transformer expects prenorm blocks")

        self.norm_first = norm_first
        self.self_attn = nn.MultiheadAttention(
            d_model, nhead, dropout=dropout, batch_first=batch_first
        )
        self.linear1 = nn.Linear(d_model, dim_feedforward)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(dim_feedforward, d_model)
        self.norm1 = AdaLayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model, eps=1e-5)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)
        self.activation = _get_activation_fn(activation)

    def forward(
        self,
        src: Float[torch.Tensor, "batch tokens channels"],
        src_mask: Bool[torch.Tensor, "..."] | None = None,
        src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
        timestep: Float[torch.Tensor, "batch"] | None = None,
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Apply self-attention and feed-forward layers."""
        if timestep is None:
            raise ValueError("timestep is required")

        x = self.norm1(src, timestep)
        x = x + self._sa_block(x, src_mask, src_key_padding_mask)
        return x + self._ff_block(self.norm2(x))

    def _sa_block(
        self,
        x: Float[torch.Tensor, "batch tokens channels"],
        attn_mask: Shaped[torch.Tensor, "..."] | None,
        key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None,
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        x = self.self_attn(
            x,
            x,
            x,
            attn_mask=attn_mask,
            key_padding_mask=key_padding_mask,
            need_weights=False,
        )[0]
        return self.dropout1(x)

    def _ff_block(
        self, x: Float[torch.Tensor, "batch tokens channels"]
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        return self.dropout2(
            self.linear2(self.dropout(self.activation(self.linear1(x))))
        )

__init__

__init__(
    d_model: int = 1024,
    nhead: int = 16,
    dim_feedforward: int = 2048,
    dropout: float = 0.0,
    activation: str
    | Callable[
        [Shaped[Tensor, "..."]], Shaped[Tensor, "..."]
    ] = F.relu,
    batch_first: bool = False,
    norm_first: bool = False,
) -> None

Initialize one LayoutFlow transformer block.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
 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
def __init__(
    self,
    d_model: int = 1024,
    nhead: int = 16,
    dim_feedforward: int = 2048,
    dropout: float = 0.0,
    activation: str
    | Callable[[Shaped[torch.Tensor, "..."]], Shaped[torch.Tensor, "..."]] = F.relu,
    batch_first: bool = False,
    norm_first: bool = False,
) -> None:
    """Initialize one LayoutFlow transformer block."""
    super().__init__()
    if not norm_first:
        raise ValueError("LayoutFlow transformer expects prenorm blocks")

    self.norm_first = norm_first
    self.self_attn = nn.MultiheadAttention(
        d_model, nhead, dropout=dropout, batch_first=batch_first
    )
    self.linear1 = nn.Linear(d_model, dim_feedforward)
    self.dropout = nn.Dropout(dropout)
    self.linear2 = nn.Linear(dim_feedforward, d_model)
    self.norm1 = AdaLayerNorm(d_model)
    self.norm2 = nn.LayerNorm(d_model, eps=1e-5)
    self.dropout1 = nn.Dropout(dropout)
    self.dropout2 = nn.Dropout(dropout)
    self.activation = _get_activation_fn(activation)

forward

forward(
    src: Float[Tensor, "batch tokens channels"],
    src_mask: Bool[Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[Tensor, "batch tokens"]
    | None = None,
    timestep: Float[Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]

Apply self-attention and feed-forward layers.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def forward(
    self,
    src: Float[torch.Tensor, "batch tokens channels"],
    src_mask: Bool[torch.Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    timestep: Float[torch.Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Apply self-attention and feed-forward layers."""
    if timestep is None:
        raise ValueError("timestep is required")

    x = self.norm1(src, timestep)
    x = x + self._sa_block(x, src_mask, src_key_padding_mask)
    return x + self._ff_block(self.norm2(x))

LayoutFlowTransformerEncoder

Bases: Module

Stack of LayoutFlow transformer blocks.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
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
class LayoutFlowTransformerEncoder(nn.Module):
    """Stack of LayoutFlow transformer blocks."""

    def __init__(
        self, encoder_layer: nn.Module, num_layers: int, norm: nn.Module | None = None
    ) -> None:
        """Clone and stack ``encoder_layer`` ``num_layers`` times."""
        super().__init__()
        self.layers = _get_clones(encoder_layer, num_layers)
        self.num_layers = num_layers
        self.norm = norm

    def forward(
        self,
        src: Float[torch.Tensor, "batch tokens channels"],
        mask: Shaped[torch.Tensor, "..."] | None = None,
        src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
        timestep: Float[torch.Tensor, "batch"] | None = None,
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Run the stacked encoder blocks."""
        output = src
        for layer in self.layers:
            output = layer(
                output,
                src_mask=mask,
                src_key_padding_mask=src_key_padding_mask,
                timestep=timestep,
            )
        return self.norm(output) if self.norm is not None else output

__init__

__init__(
    encoder_layer: Module,
    num_layers: int,
    norm: Module | None = None,
) -> None

Clone and stack encoder_layer num_layers times.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
170
171
172
173
174
175
176
177
def __init__(
    self, encoder_layer: nn.Module, num_layers: int, norm: nn.Module | None = None
) -> None:
    """Clone and stack ``encoder_layer`` ``num_layers`` times."""
    super().__init__()
    self.layers = _get_clones(encoder_layer, num_layers)
    self.num_layers = num_layers
    self.norm = norm

forward

forward(
    src: Float[Tensor, "batch tokens channels"],
    mask: Shaped[Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[Tensor, "batch tokens"]
    | None = None,
    timestep: Float[Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]

Run the stacked encoder blocks.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def forward(
    self,
    src: Float[torch.Tensor, "batch tokens channels"],
    mask: Shaped[torch.Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    timestep: Float[torch.Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Run the stacked encoder blocks."""
    output = src
    for layer in self.layers:
        output = layer(
            output,
            src_mask=mask,
            src_key_padding_mask=src_key_padding_mask,
            timestep=timestep,
        )
    return self.norm(output) if self.norm is not None else output

LayoutDMBackbone

Bases: Module

LayoutFlow backbone module.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
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
class LayoutDMBackbone(nn.Module):
    """LayoutFlow backbone module."""

    def __init__(
        self,
        latent_dim: int = 128,
        tr_enc_only: bool = True,
        d_model: int = 256,
        nhead: int = 8,
        dim_feedforward: int = 2048,
        num_layers: int = 8,
        dropout: float = 0.1,
        use_pos_enc: bool = False,
        num_cat: int = 6,
        attr_encoding: AttrEncoding = AttrEncoding.continuous,
        seq_type: SeqType = SeqType.stacked,
    ) -> None:
        """Initialize the LayoutFlow backbone."""
        super().__init__()
        self.geom_dim = 4
        self.num_cat = num_cat
        self.attr_encoding = AttrEncoding(attr_encoding)
        self.seq_type = SeqType(seq_type)
        self.use_pose_enc = use_pos_enc
        self.pos_enc = PositionalEncoding(d_model, dropout, max_len=200)
        self.cond_enc = nn.Embedding(
            6,
            latent_dim if self.seq_type is SeqType.stacked else 2 * latent_dim,
        )
        attr_dim = (
            int(np.ceil(np.log2(num_cat)))
            if self.attr_encoding is AttrEncoding.analog_bit
            else 1
        )
        if self.attr_encoding is AttrEncoding.discrete:
            self.type_embed = nn.Embedding(num_cat, latent_dim)
            self.geom_embed = nn.Linear(self.geom_dim, latent_dim)
        else:
            self.type_embed = nn.Linear(
                attr_dim,
                latent_dim if self.seq_type is SeqType.stacked else 2 * latent_dim,
            )
            if self.seq_type is SeqType.seq:
                self.geom_enc = nn.ModuleList(
                    [nn.Linear(1, 2 * latent_dim) for _ in range(4)]
                )
            else:
                self.geom_embed = nn.Linear(
                    self.geom_dim,
                    latent_dim if self.seq_type is SeqType.stacked else 2 * latent_dim,
                )
        self.elem_embed = nn.Linear(2 * latent_dim, d_model)
        decoder_layer = LayoutFlowBlock(
            d_model=d_model,
            nhead=nhead,
            batch_first=True,
            norm_first=True,
            dropout=dropout,
            dim_feedforward=dim_feedforward,
        )
        self.tr_enc_only = tr_enc_only
        if tr_enc_only:
            self.transformer = LayoutFlowTransformerEncoder(
                decoder_layer, num_layers=num_layers, norm=nn.LayerNorm(d_model)
            )
        else:
            self.transformer = nn.Transformer(
                d_model=d_model, nhead=nhead, batch_first=True
            )
        self.linear = nn.Linear(d_model, self.geom_dim + attr_dim)
        if self.seq_type is not SeqType.stacked:
            k = 2 if self.seq_type is SeqType.seq_cond else 5
            self.to_attrdim = nn.Linear(
                k * (self.geom_dim + attr_dim), self.geom_dim + attr_dim
            )

    def forward(
        self,
        geom: Float[torch.Tensor, "batch elements 4"],
        attr: Float[torch.Tensor, "batch elements bits"],
        cond_flags: Int[torch.Tensor, "batch elements channels"],
        t: Float[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Predict vector-field values for geometry and attribute inputs."""
        ps = None
        if self.attr_encoding is AttrEncoding.discrete:
            geom = self.geom_embed(geom)
            attr = self.type_embed(attr.squeeze())
            x = torch.cat([geom, attr], dim=-1)
        elif self.seq_type is SeqType.stacked:
            geom = self.geom_embed(geom) + self.cond_enc(
                cond_flags[:, :, : self.geom_dim].sum(-1)
            )
            attr = self.type_embed(attr) + self.cond_enc(cond_flags[:, :, -1])
            x, _ = pack([geom, attr], "b s *")
        elif self.seq_type is SeqType.seq_cond:
            geom = self.geom_embed(geom) + self.cond_enc(
                cond_flags[:, :, : self.geom_dim].sum(-1)
            )
            attr = self.type_embed(attr) + self.cond_enc(cond_flags[:, :, -1])
            x, ps = pack([geom, attr], "b * d")
        elif self.seq_type is SeqType.seq:
            geom_parts = [
                self.geom_enc[i](geom[:, :, i, None])
                + self.cond_enc(cond_flags[:, :, i])
                for i in range(4)
            ]
            attr = self.type_embed(attr) + self.cond_enc(cond_flags[:, :, -1])
            x, ps = pack(geom_parts + [attr], "b * d")
        else:
            raise ValueError(f"Unsupported seq_type: {self.seq_type}")

        x = self.elem_embed(x)
        if self.use_pose_enc:
            x = x + self.pos_enc(x)
        x = (
            self.transformer(x, timestep=t)
            if self.tr_enc_only
            else self.transformer(x, x)
        )
        x = self.linear(x)
        if self.seq_type is not SeqType.stacked:
            if ps is None:
                raise ValueError(f"Unsupported seq_type: {self.seq_type}")

            x = unpack(x, ps, "b * d")
            x = rearrange(x, "k b s d -> b s (k d)")
            x = self.to_attrdim(x)
        return x

__init__

__init__(
    latent_dim: int = 128,
    tr_enc_only: bool = True,
    d_model: int = 256,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    num_layers: int = 8,
    dropout: float = 0.1,
    use_pos_enc: bool = False,
    num_cat: int = 6,
    attr_encoding: AttrEncoding = AttrEncoding.continuous,
    seq_type: SeqType = SeqType.stacked,
) -> None

Initialize the LayoutFlow backbone.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
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
def __init__(
    self,
    latent_dim: int = 128,
    tr_enc_only: bool = True,
    d_model: int = 256,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    num_layers: int = 8,
    dropout: float = 0.1,
    use_pos_enc: bool = False,
    num_cat: int = 6,
    attr_encoding: AttrEncoding = AttrEncoding.continuous,
    seq_type: SeqType = SeqType.stacked,
) -> None:
    """Initialize the LayoutFlow backbone."""
    super().__init__()
    self.geom_dim = 4
    self.num_cat = num_cat
    self.attr_encoding = AttrEncoding(attr_encoding)
    self.seq_type = SeqType(seq_type)
    self.use_pose_enc = use_pos_enc
    self.pos_enc = PositionalEncoding(d_model, dropout, max_len=200)
    self.cond_enc = nn.Embedding(
        6,
        latent_dim if self.seq_type is SeqType.stacked else 2 * latent_dim,
    )
    attr_dim = (
        int(np.ceil(np.log2(num_cat)))
        if self.attr_encoding is AttrEncoding.analog_bit
        else 1
    )
    if self.attr_encoding is AttrEncoding.discrete:
        self.type_embed = nn.Embedding(num_cat, latent_dim)
        self.geom_embed = nn.Linear(self.geom_dim, latent_dim)
    else:
        self.type_embed = nn.Linear(
            attr_dim,
            latent_dim if self.seq_type is SeqType.stacked else 2 * latent_dim,
        )
        if self.seq_type is SeqType.seq:
            self.geom_enc = nn.ModuleList(
                [nn.Linear(1, 2 * latent_dim) for _ in range(4)]
            )
        else:
            self.geom_embed = nn.Linear(
                self.geom_dim,
                latent_dim if self.seq_type is SeqType.stacked else 2 * latent_dim,
            )
    self.elem_embed = nn.Linear(2 * latent_dim, d_model)
    decoder_layer = LayoutFlowBlock(
        d_model=d_model,
        nhead=nhead,
        batch_first=True,
        norm_first=True,
        dropout=dropout,
        dim_feedforward=dim_feedforward,
    )
    self.tr_enc_only = tr_enc_only
    if tr_enc_only:
        self.transformer = LayoutFlowTransformerEncoder(
            decoder_layer, num_layers=num_layers, norm=nn.LayerNorm(d_model)
        )
    else:
        self.transformer = nn.Transformer(
            d_model=d_model, nhead=nhead, batch_first=True
        )
    self.linear = nn.Linear(d_model, self.geom_dim + attr_dim)
    if self.seq_type is not SeqType.stacked:
        k = 2 if self.seq_type is SeqType.seq_cond else 5
        self.to_attrdim = nn.Linear(
            k * (self.geom_dim + attr_dim), self.geom_dim + attr_dim
        )

forward

forward(
    geom: Float[Tensor, "batch elements 4"],
    attr: Float[Tensor, "batch elements bits"],
    cond_flags: Int[Tensor, "batch elements channels"],
    t: Float[Tensor, "batch"],
) -> Float[torch.Tensor, "batch elements channels"]

Predict vector-field values for geometry and attribute inputs.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
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
def forward(
    self,
    geom: Float[torch.Tensor, "batch elements 4"],
    attr: Float[torch.Tensor, "batch elements bits"],
    cond_flags: Int[torch.Tensor, "batch elements channels"],
    t: Float[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch elements channels"]:
    """Predict vector-field values for geometry and attribute inputs."""
    ps = None
    if self.attr_encoding is AttrEncoding.discrete:
        geom = self.geom_embed(geom)
        attr = self.type_embed(attr.squeeze())
        x = torch.cat([geom, attr], dim=-1)
    elif self.seq_type is SeqType.stacked:
        geom = self.geom_embed(geom) + self.cond_enc(
            cond_flags[:, :, : self.geom_dim].sum(-1)
        )
        attr = self.type_embed(attr) + self.cond_enc(cond_flags[:, :, -1])
        x, _ = pack([geom, attr], "b s *")
    elif self.seq_type is SeqType.seq_cond:
        geom = self.geom_embed(geom) + self.cond_enc(
            cond_flags[:, :, : self.geom_dim].sum(-1)
        )
        attr = self.type_embed(attr) + self.cond_enc(cond_flags[:, :, -1])
        x, ps = pack([geom, attr], "b * d")
    elif self.seq_type is SeqType.seq:
        geom_parts = [
            self.geom_enc[i](geom[:, :, i, None])
            + self.cond_enc(cond_flags[:, :, i])
            for i in range(4)
        ]
        attr = self.type_embed(attr) + self.cond_enc(cond_flags[:, :, -1])
        x, ps = pack(geom_parts + [attr], "b * d")
    else:
        raise ValueError(f"Unsupported seq_type: {self.seq_type}")

    x = self.elem_embed(x)
    if self.use_pose_enc:
        x = x + self.pos_enc(x)
    x = (
        self.transformer(x, timestep=t)
        if self.tr_enc_only
        else self.transformer(x, x)
    )
    x = self.linear(x)
    if self.seq_type is not SeqType.stacked:
        if ps is None:
            raise ValueError(f"Unsupported seq_type: {self.seq_type}")

        x = unpack(x, ps, "b * d")
        x = rearrange(x, "k b s d -> b s (k d)")
        x = self.to_attrdim(x)
    return x

LayoutFlowModelOutput dataclass

Bases: BaseOutput

Output of LayoutFlowTransformerModel.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
329
330
331
332
333
@dataclass
class LayoutFlowModelOutput(BaseOutput):
    """Output of ``LayoutFlowTransformerModel``."""

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

LayoutFlowTransformerModel

Bases: ModelMixin, ConfigMixin

Diffusers model wrapper around the LayoutFlow backbone.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
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
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
class LayoutFlowTransformerModel(ModelMixin, ConfigMixin):
    """Diffusers model wrapper around the LayoutFlow backbone."""

    config_name: str = "layout_flow_model_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        num_labels: int = 6,
        latent_dim: int = 128,
        tr_enc_only: bool = True,
        d_model: int = 512,
        nhead: int = 8,
        dim_feedforward: int = 2048,
        num_layers: int = 4,
        dropout: float = 0.1,
        use_pos_enc: bool = False,
        attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
        seq_type: SeqType = SeqType.stacked,
    ) -> None:
        """Initialize the converted LayoutFlow transformer model.

        Args:
            num_labels: Number of dataset labels.
            latent_dim: Latent dimension.
            tr_enc_only: Whether to use the encoder-only path.
            d_model: Transformer hidden size.
            nhead: Number of attention heads.
            dim_feedforward: Feed-forward hidden size.
            num_layers: Number of transformer layers.
            dropout: Dropout probability.
            use_pos_enc: Whether to add positional encodings.
            attr_encoding: Attribute encoding.
            seq_type: Sequence type.
        """
        super().__init__()
        self.geom_dim = 4
        self.attr_dim = (
            int(np.ceil(np.log2(num_labels)))
            if AttrEncoding(attr_encoding) is AttrEncoding.analog_bit
            else 1
        )
        self.backbone = LayoutDMBackbone(
            latent_dim=latent_dim,
            tr_enc_only=tr_enc_only,
            d_model=d_model,
            nhead=nhead,
            dim_feedforward=dim_feedforward,
            num_layers=num_layers,
            dropout=dropout,
            use_pos_enc=use_pos_enc,
            num_cat=num_labels,
            attr_encoding=attr_encoding,
            seq_type=seq_type,
        )

    def forward(
        self,
        sample: Float[torch.Tensor, "batch elements channels"],
        timestep: Float[torch.Tensor, ""],
        cond_mask: Bool[torch.Tensor, "batch elements channels"],
        return_dict: bool = True,
    ) -> LayoutFlowModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
        """Predict the vector field for a model state.

        Args:
            sample: Current model state.
            timestep: Current integration timestep.
            cond_mask: Condition mask.
            return_dict: Whether to return a dataclass output.

        Returns:
            Model output dataclass or single-item tuple.
        """
        timestep = timestep.to(device=sample.device, dtype=sample.dtype)
        if timestep.ndim == 0:
            timestep = timestep.repeat(sample.shape[0])
        geom = sample[:, :, : self.geom_dim]
        attr = sample[:, :, self.geom_dim :]
        out = self.backbone(geom, attr, cond_mask.to(torch.long), timestep)
        if not return_dict:
            return (out,)
        return LayoutFlowModelOutput(sample=out)

__init__

__init__(
    *,
    num_labels: int = 6,
    latent_dim: int = 128,
    tr_enc_only: bool = True,
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    num_layers: int = 4,
    dropout: float = 0.1,
    use_pos_enc: bool = False,
    attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
    seq_type: SeqType = SeqType.stacked,
) -> None

Initialize the converted LayoutFlow transformer model.

Parameters:

Name Type Description Default
num_labels int

Number of dataset labels.

6
latent_dim int

Latent dimension.

128
tr_enc_only bool

Whether to use the encoder-only path.

True
d_model int

Transformer hidden size.

512
nhead int

Number of attention heads.

8
dim_feedforward int

Feed-forward hidden size.

2048
num_layers int

Number of transformer layers.

4
dropout float

Dropout probability.

0.1
use_pos_enc bool

Whether to add positional encodings.

False
attr_encoding AttrEncoding

Attribute encoding.

analog_bit
seq_type SeqType

Sequence type.

stacked
Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
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
390
391
@register_to_config
def __init__(
    self,
    *,
    num_labels: int = 6,
    latent_dim: int = 128,
    tr_enc_only: bool = True,
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    num_layers: int = 4,
    dropout: float = 0.1,
    use_pos_enc: bool = False,
    attr_encoding: AttrEncoding = AttrEncoding.analog_bit,
    seq_type: SeqType = SeqType.stacked,
) -> None:
    """Initialize the converted LayoutFlow transformer model.

    Args:
        num_labels: Number of dataset labels.
        latent_dim: Latent dimension.
        tr_enc_only: Whether to use the encoder-only path.
        d_model: Transformer hidden size.
        nhead: Number of attention heads.
        dim_feedforward: Feed-forward hidden size.
        num_layers: Number of transformer layers.
        dropout: Dropout probability.
        use_pos_enc: Whether to add positional encodings.
        attr_encoding: Attribute encoding.
        seq_type: Sequence type.
    """
    super().__init__()
    self.geom_dim = 4
    self.attr_dim = (
        int(np.ceil(np.log2(num_labels)))
        if AttrEncoding(attr_encoding) is AttrEncoding.analog_bit
        else 1
    )
    self.backbone = LayoutDMBackbone(
        latent_dim=latent_dim,
        tr_enc_only=tr_enc_only,
        d_model=d_model,
        nhead=nhead,
        dim_feedforward=dim_feedforward,
        num_layers=num_layers,
        dropout=dropout,
        use_pos_enc=use_pos_enc,
        num_cat=num_labels,
        attr_encoding=attr_encoding,
        seq_type=seq_type,
    )

forward

forward(
    sample: Float[Tensor, "batch elements channels"],
    timestep: Float[Tensor, ""],
    cond_mask: Bool[Tensor, "batch elements channels"],
    return_dict: bool = True,
) -> (
    LayoutFlowModelOutput
    | tuple[Float[torch.Tensor, "batch elements channels"]]
)

Predict the vector field for a model state.

Parameters:

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

Current model state.

required
timestep Float[Tensor, '']

Current integration timestep.

required
cond_mask Bool[Tensor, 'batch elements channels']

Condition mask.

required
return_dict bool

Whether to return a dataclass output.

True

Returns:

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

Model output dataclass or single-item tuple.

Source code in models/layout-flow/src/layout_flow/modeling_layout_flow.py
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
def forward(
    self,
    sample: Float[torch.Tensor, "batch elements channels"],
    timestep: Float[torch.Tensor, ""],
    cond_mask: Bool[torch.Tensor, "batch elements channels"],
    return_dict: bool = True,
) -> LayoutFlowModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
    """Predict the vector field for a model state.

    Args:
        sample: Current model state.
        timestep: Current integration timestep.
        cond_mask: Condition mask.
        return_dict: Whether to return a dataclass output.

    Returns:
        Model output dataclass or single-item tuple.
    """
    timestep = timestep.to(device=sample.device, dtype=sample.dtype)
    if timestep.ndim == 0:
        timestep = timestep.repeat(sample.shape[0])
    geom = sample[:, :, : self.geom_dim]
    attr = sample[:, :, self.geom_dim :]
    out = self.backbone(geom, attr, cond_mask.to(torch.long), timestep)
    if not return_dict:
        return (out,)
    return LayoutFlowModelOutput(sample=out)

pipeline_layout_flow

Diffusers pipeline for LayoutFlow inference.

OutputType

Bases: StrEnum

Pipeline output containers supported by LayoutFlow.

Source code in models/layout-flow/src/layout_flow/pipeline_layout_flow.py
29
30
31
32
33
class OutputType(StrEnum):
    """Pipeline output containers supported by LayoutFlow."""

    dataclass = "dataclass"
    dict = "dict"

LayoutFlowPipeline

Bases: DiffusionPipeline

Generate layouts with a converted LayoutFlow checkpoint.

Source code in models/layout-flow/src/layout_flow/pipeline_layout_flow.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
class LayoutFlowPipeline(DiffusionPipeline):
    """Generate layouts with a converted LayoutFlow checkpoint."""

    model_cpu_offload_seq: str = "model"

    def __init__(
        self,
        model: LayoutFlowTransformerModel,
        scheduler: LayoutFlowEulerScheduler,
        config: LayoutFlowConfig,
        processor: LayoutFlowProcessor | None = None,
    ) -> None:
        """Create a LayoutFlow pipeline.

        Args:
            model: Converted LayoutFlow transformer model.
            scheduler: Increasing-time Euler scheduler.
            config: Pipeline configuration.
            processor: Optional input/output processor.
        """
        super().__init__()
        self.register_modules(model=model, scheduler=scheduler)
        self.layout_flow_config = config
        self.processor = processor or LayoutFlowProcessor(self.layout_flow_config)
        self.model.eval()

    @torch.no_grad()
    def __call__(
        self,
        *,
        batch_size: int = 1,
        seed: int | None = None,
        generator: torch.Generator | None = None,
        condition_type: ConditionType | str = ConditionType.unconditional,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = "xywh",
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        guidance_scale: float = 0.0,
        output_type: OutputType | str = "dataclass",
        return_intermediates: bool = False,
    ) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
        """Generate layout boxes and labels.

        Args:
            batch_size: Number of layouts to generate.
            seed: Optional seed used when ``generator`` is omitted.
            generator: Optional torch random generator.
            condition_type: Public condition name or supported alias.
            labels: Optional condition labels.
            bbox: Optional condition boxes.
            mask: Optional valid-element mask.
            num_elements: Optional element counts for unconditional masks.
            box_format: Input and output box format.
            normalized: Whether coordinates are normalized.
            canvas_size: Pixel canvas size for denormalized coordinates.
            num_inference_steps: Number of Euler steps.
            guidance_scale: Classifier-free guidance scale.
            output_type: ``"dataclass"`` or ``"dict"``.
            return_intermediates: Whether to include intermediate samples.

        Returns:
            Layout generation output dataclass, or a dictionary when requested.

        Raises:
            ValueError: If ``condition_type``, ``box_format``, or ``output_type``
                is unsupported.

        Examples:
            >>> pipe = LayoutFlowPipeline(
            ...     model=LayoutFlowTransformerModel(
            ...         num_labels=6, latent_dim=8, d_model=16, nhead=4,
            ...         dim_feedforward=32, num_layers=1
            ...     ),
            ...     scheduler=LayoutFlowEulerScheduler(num_inference_steps=2),
            ...     config=LayoutFlowConfig(max_length=2, latent_dim=8, d_model=16),
            ... )
            >>> out = pipe(batch_size=1, num_elements=1, seed=0, num_inference_steps=2)
            >>> out.bbox.shape[-1]
            4
        """
        if generator is None and seed is not None:
            generator = torch.Generator(device=self.device).manual_seed(seed)
        canonical = normalize_condition_type(condition_type)
        output_kind = OutputType(output_type)
        processed = self.processor(
            bbox=bbox,
            labels=labels,
            mask=mask,
            num_elements=num_elements,
            batch_size=batch_size,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
            device=self.device,
        )
        batch_size = processed["bbox"].shape[0]
        cond_mask = self.processor.make_condition_mask(
            canonical, mask=processed["mask"], generator=generator
        )
        cond_state = self.processor.preprocess_state(
            self.processor.model_state(processed["bbox"], processed["labels"])
        )
        sample = sample_initial_state(
            batch_size=batch_size,
            max_length=self.layout_flow_config.max_length,
            lengths=processed["length"],
            dim=self.layout_flow_config.sample_dim,
            distribution=self.layout_flow_config.distribution,
            generator=generator,
            device=self.device,
            dtype=cond_state.dtype,
        )
        if canonical is ConditionType.refinement:
            sample = cond_state
            self.scheduler.set_timesteps(
                num_inference_steps, device=self.device, start=0.97, end=1.0
            )
        else:
            self.scheduler.set_timesteps(num_inference_steps, device=self.device)
        trajectory = [] if return_intermediates else None
        for i, timestep in enumerate(self.scheduler.timesteps[:-1]):
            x_in = (1 - cond_mask) * cond_state + cond_mask * sample
            t_batch = timestep.repeat(batch_size)
            vector = self.model(
                sample=x_in, timestep=t_batch, cond_mask=cond_mask
            ).sample
            if guidance_scale:
                uncond_mask = torch.ones_like(cond_mask)
                uncond = self.model(
                    sample=x_in, timestep=t_batch, cond_mask=uncond_mask
                ).sample
                vector = (1 + guidance_scale) * vector - guidance_scale * uncond
            sample = self.scheduler.step(
                vector,
                timestep,
                sample,
                next_timestep=self.scheduler.timesteps[i + 1],
            ).prev_sample
            if trajectory is not None:
                trajectory.append(sample.detach().cpu())
        final_state = (1 - cond_mask) * cond_state + cond_mask * sample
        decoded = self.processor.postprocess(
            final_state,
            mask=processed["mask"],
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        output = LayoutGenerationOutput(
            bbox=decoded["bbox"].detach().cpu(),
            labels=decoded["labels"].detach().cpu(),
            mask=decoded["mask"].detach().cpu(),
            id2label=self.layout_flow_config.id2label,
            trajectory=trajectory,
            intermediates={"condition_type": str(canonical)}
            if return_intermediates
            else None,
        )
        if output_kind is OutputType.dict:
            return dict(output)
        if output_kind is OutputType.dataclass:
            return output
        assert_never(output_kind)

    generate = __call__

    def save_pretrained(self, save_directory: str | Path) -> None:
        """Save pipeline components and LayoutFlow config.

        Args:
            save_directory: Output directory.
        """
        super().save_pretrained(save_directory)
        self.layout_flow_config.save_config(save_directory)

    @classmethod
    def from_pretrained(
        cls, pretrained_model_name_or_path: str | Path
    ) -> LayoutFlowPipeline:
        """Load a saved LayoutFlow pipeline.

        Args:
            pretrained_model_name_or_path: Local directory or Hub id.

        Returns:
            Loaded LayoutFlow pipeline.
        """
        config_dict, _ = LayoutFlowConfig.load_config(
            pretrained_model_name_or_path,
            return_unused_kwargs=True,
        )
        config = cast(LayoutFlowConfig, LayoutFlowConfig.from_config(config_dict))
        pipe = super().from_pretrained(pretrained_model_name_or_path, config=config)
        pipe.layout_flow_config = config
        pipe.processor = LayoutFlowProcessor(config)
        return pipe

__init__

__init__(
    model: LayoutFlowTransformerModel,
    scheduler: LayoutFlowEulerScheduler,
    config: LayoutFlowConfig,
    processor: LayoutFlowProcessor | None = None,
) -> None

Create a LayoutFlow pipeline.

Parameters:

Name Type Description Default
model LayoutFlowTransformerModel

Converted LayoutFlow transformer model.

required
scheduler LayoutFlowEulerScheduler

Increasing-time Euler scheduler.

required
config LayoutFlowConfig

Pipeline configuration.

required
processor LayoutFlowProcessor | None

Optional input/output processor.

None
Source code in models/layout-flow/src/layout_flow/pipeline_layout_flow.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def __init__(
    self,
    model: LayoutFlowTransformerModel,
    scheduler: LayoutFlowEulerScheduler,
    config: LayoutFlowConfig,
    processor: LayoutFlowProcessor | None = None,
) -> None:
    """Create a LayoutFlow pipeline.

    Args:
        model: Converted LayoutFlow transformer model.
        scheduler: Increasing-time Euler scheduler.
        config: Pipeline configuration.
        processor: Optional input/output processor.
    """
    super().__init__()
    self.register_modules(model=model, scheduler=scheduler)
    self.layout_flow_config = config
    self.processor = processor or LayoutFlowProcessor(self.layout_flow_config)
    self.model.eval()

__call__

__call__(
    *,
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.unconditional,
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = "xywh",
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    guidance_scale: float = 0.0,
    output_type: OutputType | str = "dataclass",
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[str, Shaped[torch.Tensor, "..."]]
)

Generate layout boxes and labels.

Parameters:

Name Type Description Default
batch_size int

Number of layouts to generate.

1
seed int | None

Optional seed used when generator is omitted.

None
generator Generator | None

Optional torch random generator.

None
condition_type ConditionType | str

Public condition name or supported alias.

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

Optional condition labels.

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

Optional condition boxes.

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

Optional valid-element mask.

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

Optional element counts for unconditional masks.

None
box_format BoxFormat | str

Input and output box format.

'xywh'
normalized bool

Whether coordinates are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size for denormalized coordinates.

None
num_inference_steps int | None

Number of Euler steps.

None
guidance_scale float

Classifier-free guidance scale.

0.0
output_type OutputType | str

"dataclass" or "dict".

'dataclass'
return_intermediates bool

Whether to include intermediate samples.

False

Returns:

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

Layout generation output dataclass, or a dictionary when requested.

Raises:

Type Description
ValueError

If condition_type, box_format, or output_type is unsupported.

Examples:

>>> pipe = LayoutFlowPipeline(
...     model=LayoutFlowTransformerModel(
...         num_labels=6, latent_dim=8, d_model=16, nhead=4,
...         dim_feedforward=32, num_layers=1
...     ),
...     scheduler=LayoutFlowEulerScheduler(num_inference_steps=2),
...     config=LayoutFlowConfig(max_length=2, latent_dim=8, d_model=16),
... )
>>> out = pipe(batch_size=1, num_elements=1, seed=0, num_inference_steps=2)
>>> out.bbox.shape[-1]
4
Source code in models/layout-flow/src/layout_flow/pipeline_layout_flow.py
 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
@torch.no_grad()
def __call__(
    self,
    *,
    batch_size: int = 1,
    seed: int | None = None,
    generator: torch.Generator | None = None,
    condition_type: ConditionType | str = ConditionType.unconditional,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = "xywh",
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    guidance_scale: float = 0.0,
    output_type: OutputType | str = "dataclass",
    return_intermediates: bool = False,
) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
    """Generate layout boxes and labels.

    Args:
        batch_size: Number of layouts to generate.
        seed: Optional seed used when ``generator`` is omitted.
        generator: Optional torch random generator.
        condition_type: Public condition name or supported alias.
        labels: Optional condition labels.
        bbox: Optional condition boxes.
        mask: Optional valid-element mask.
        num_elements: Optional element counts for unconditional masks.
        box_format: Input and output box format.
        normalized: Whether coordinates are normalized.
        canvas_size: Pixel canvas size for denormalized coordinates.
        num_inference_steps: Number of Euler steps.
        guidance_scale: Classifier-free guidance scale.
        output_type: ``"dataclass"`` or ``"dict"``.
        return_intermediates: Whether to include intermediate samples.

    Returns:
        Layout generation output dataclass, or a dictionary when requested.

    Raises:
        ValueError: If ``condition_type``, ``box_format``, or ``output_type``
            is unsupported.

    Examples:
        >>> pipe = LayoutFlowPipeline(
        ...     model=LayoutFlowTransformerModel(
        ...         num_labels=6, latent_dim=8, d_model=16, nhead=4,
        ...         dim_feedforward=32, num_layers=1
        ...     ),
        ...     scheduler=LayoutFlowEulerScheduler(num_inference_steps=2),
        ...     config=LayoutFlowConfig(max_length=2, latent_dim=8, d_model=16),
        ... )
        >>> out = pipe(batch_size=1, num_elements=1, seed=0, num_inference_steps=2)
        >>> out.bbox.shape[-1]
        4
    """
    if generator is None and seed is not None:
        generator = torch.Generator(device=self.device).manual_seed(seed)
    canonical = normalize_condition_type(condition_type)
    output_kind = OutputType(output_type)
    processed = self.processor(
        bbox=bbox,
        labels=labels,
        mask=mask,
        num_elements=num_elements,
        batch_size=batch_size,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
        device=self.device,
    )
    batch_size = processed["bbox"].shape[0]
    cond_mask = self.processor.make_condition_mask(
        canonical, mask=processed["mask"], generator=generator
    )
    cond_state = self.processor.preprocess_state(
        self.processor.model_state(processed["bbox"], processed["labels"])
    )
    sample = sample_initial_state(
        batch_size=batch_size,
        max_length=self.layout_flow_config.max_length,
        lengths=processed["length"],
        dim=self.layout_flow_config.sample_dim,
        distribution=self.layout_flow_config.distribution,
        generator=generator,
        device=self.device,
        dtype=cond_state.dtype,
    )
    if canonical is ConditionType.refinement:
        sample = cond_state
        self.scheduler.set_timesteps(
            num_inference_steps, device=self.device, start=0.97, end=1.0
        )
    else:
        self.scheduler.set_timesteps(num_inference_steps, device=self.device)
    trajectory = [] if return_intermediates else None
    for i, timestep in enumerate(self.scheduler.timesteps[:-1]):
        x_in = (1 - cond_mask) * cond_state + cond_mask * sample
        t_batch = timestep.repeat(batch_size)
        vector = self.model(
            sample=x_in, timestep=t_batch, cond_mask=cond_mask
        ).sample
        if guidance_scale:
            uncond_mask = torch.ones_like(cond_mask)
            uncond = self.model(
                sample=x_in, timestep=t_batch, cond_mask=uncond_mask
            ).sample
            vector = (1 + guidance_scale) * vector - guidance_scale * uncond
        sample = self.scheduler.step(
            vector,
            timestep,
            sample,
            next_timestep=self.scheduler.timesteps[i + 1],
        ).prev_sample
        if trajectory is not None:
            trajectory.append(sample.detach().cpu())
    final_state = (1 - cond_mask) * cond_state + cond_mask * sample
    decoded = self.processor.postprocess(
        final_state,
        mask=processed["mask"],
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    output = LayoutGenerationOutput(
        bbox=decoded["bbox"].detach().cpu(),
        labels=decoded["labels"].detach().cpu(),
        mask=decoded["mask"].detach().cpu(),
        id2label=self.layout_flow_config.id2label,
        trajectory=trajectory,
        intermediates={"condition_type": str(canonical)}
        if return_intermediates
        else None,
    )
    if output_kind is OutputType.dict:
        return dict(output)
    if output_kind is OutputType.dataclass:
        return output
    assert_never(output_kind)

save_pretrained

save_pretrained(save_directory: str | Path) -> None

Save pipeline components and LayoutFlow config.

Parameters:

Name Type Description Default
save_directory str | Path

Output directory.

required
Source code in models/layout-flow/src/layout_flow/pipeline_layout_flow.py
216
217
218
219
220
221
222
223
def save_pretrained(self, save_directory: str | Path) -> None:
    """Save pipeline components and LayoutFlow config.

    Args:
        save_directory: Output directory.
    """
    super().save_pretrained(save_directory)
    self.layout_flow_config.save_config(save_directory)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
) -> LayoutFlowPipeline

Load a saved LayoutFlow pipeline.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Local directory or Hub id.

required

Returns:

Type Description
LayoutFlowPipeline

Loaded LayoutFlow pipeline.

Source code in models/layout-flow/src/layout_flow/pipeline_layout_flow.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
@classmethod
def from_pretrained(
    cls, pretrained_model_name_or_path: str | Path
) -> LayoutFlowPipeline:
    """Load a saved LayoutFlow pipeline.

    Args:
        pretrained_model_name_or_path: Local directory or Hub id.

    Returns:
        Loaded LayoutFlow pipeline.
    """
    config_dict, _ = LayoutFlowConfig.load_config(
        pretrained_model_name_or_path,
        return_unused_kwargs=True,
    )
    config = cast(LayoutFlowConfig, LayoutFlowConfig.from_config(config_dict))
    pipe = super().from_pretrained(pretrained_model_name_or_path, config=config)
    pipe.layout_flow_config = config
    pipe.processor = LayoutFlowProcessor(config)
    return pipe

processing_layout_flow

Input and output processing for LayoutFlow pipelines.

ConditionType

Bases: StrEnum

Canonical condition names used by layout generation interfaces.

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

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

LayoutFlowProcessor

Bases: ProcessorMixin

Prepare public layout tensors for the LayoutFlow model.

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

    config_name = "processor_config.json"

    def __init__(self, config: LayoutFlowConfig) -> None:
        """Create a processor for a LayoutFlow configuration.

        Args:
            config: LayoutFlow pipeline configuration.
        """
        super().__init__()
        self.config = config
        bit_mask = [1 << k for k in range(config.attr_dim)]
        self.bit_mask = torch.tensor(bit_mask, dtype=torch.long)

    def __call__(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        batch_size: int = 1,
        box_format: BoxFormat | str = "xywh",
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        device: torch.device | str | None = None,
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Convert public inputs into padded model-ready tensors.

        Args:
            bbox: Optional boxes in the requested ``box_format``.
            labels: Optional dataset-local label ids.
            mask: Optional valid-element mask.
            num_elements: Optional element counts used when ``mask`` is omitted.
            batch_size: Batch size used when tensors are omitted.
            box_format: Format of ``bbox``.
            normalized: Whether ``bbox`` is already normalized to ``[0, 1]``.
            canvas_size: Pixel canvas size required for denormalized boxes.
            device: Target torch device.

        Returns:
            Dictionary with ``bbox``, ``labels``, ``mask``, and ``length`` tensors.

        Raises:
            ValueError: If denormalized boxes are missing ``canvas_size`` or the
                box format is unsupported.
        """
        device = torch.device(device) if device is not None else torch.device("cpu")
        max_length = self.config.max_length
        if bbox is None:
            bbox_t = torch.zeros(
                batch_size, max_length, 4, dtype=torch.float32, device=device
            )
        else:
            bbox_t = torch.as_tensor(bbox, dtype=torch.float32, device=device)
            if bbox_t.ndim == 2:
                bbox_t = bbox_t.unsqueeze(0)
            batch_size = bbox_t.shape[0]
            if not normalized:
                if canvas_size is None:
                    raise ValueError("canvas_size is required when normalized=False")

                bbox_t = normalize_boxes(
                    bbox_t, canvas_size=canvas_size, box_format=box_format
                )
            else:
                fmt = BoxFormat(box_format)
                if fmt is BoxFormat.ltwh:
                    bbox_t = ltwh_to_xywh(bbox_t)
                elif fmt is BoxFormat.ltrb:
                    bbox_t = ltrb_to_xywh(bbox_t)
                elif fmt is not BoxFormat.xywh:
                    assert_never(fmt)
            bbox_t = self._pad_tensor(bbox_t, max_length, 0.0)
        if labels is None:
            labels_t = torch.zeros(
                batch_size, max_length, dtype=torch.long, device=device
            )
        else:
            labels_t = torch.as_tensor(labels, dtype=torch.long, device=device)
            if labels_t.ndim == 1:
                labels_t = labels_t.unsqueeze(0)
            labels_t = self._pad_tensor(labels_t, max_length, 0)
        if mask is None:
            lengths = self._num_elements_to_lengths(
                num_elements, batch_size, max_length, device
            )
            mask_t = torch.arange(max_length, device=device)[None, :] < lengths[:, None]
        else:
            mask_t = torch.as_tensor(mask, dtype=torch.bool, device=device)
            if mask_t.ndim == 1:
                mask_t = mask_t.unsqueeze(0)
            mask_t = self._pad_tensor(mask_t, max_length, False)
            lengths = mask_t.sum(dim=1).long()
        bbox_t = bbox_t * mask_t.unsqueeze(-1)
        labels_t = labels_t * mask_t.long()
        return {"bbox": bbox_t, "labels": labels_t, "mask": mask_t, "length": lengths}

    def encode_labels(
        self, labels: Int[torch.Tensor, "batch elements"]
    ) -> Float[torch.Tensor, "batch elements bits"]:
        """Encode integer labels as analog-bit vectors."""
        bit_mask = self.bit_mask.to(labels.device)
        return (
            torch.bitwise_and(labels.unsqueeze(-1), bit_mask).float() / bit_mask.float()
        )

    def decode_labels(
        self, bits: Float[torch.Tensor, "batch elements bits"]
    ) -> Int[torch.Tensor, "batch elements"]:
        """Decode analog-bit vectors into integer labels."""
        bit_mask = self.bit_mask.to(bits.device)
        active = (bits - 0.5 >= 0).long()
        return (
            (active * bit_mask).sum(dim=-1).clamp(0, self.config.num_labels - 1).long()
        )

    def model_state(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Concatenate normalized boxes and analog-bit labels."""
        return torch.cat([bbox, self.encode_labels(labels)], dim=-1)

    def preprocess_state(
        self,
        state: Float[torch.Tensor, "batch elements channels"],
        *,
        reverse: bool = False,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Map between public ``[0, 1]`` state and model distribution range."""
        if self.config.distribution in {"gaussian", "gmm", "uniform", "gauss_uniform"}:
            return (state + 1) / 2 if reverse else 2 * state - 1
        return state

    def make_condition_mask(
        self,
        condition_type: ConditionType,
        *,
        mask: Bool[torch.Tensor, "batch elements"],
        generator: torch.Generator | None = None,
    ) -> Int[torch.Tensor, "batch elements channels"]:
        """Create the condition mask for a conditioning mode.

        Args:
            condition_type: Canonical condition or alias.
            mask: Valid-element mask.
            generator: Optional generator used by completion masking.

        Returns:
            Long tensor where ``1`` means generated and ``0`` means conditioned.

        Raises:
            ValueError: If the condition type is unsupported.
        """
        batch, seq = mask.shape
        cond_mask = torch.ones(
            batch,
            seq,
            self.config.sample_dim,
            dtype=torch.long,
            device=mask.device,
        )
        if condition_type in {ConditionType.label, ConditionType.refinement}:
            cond_mask[:, :, 4:] = 0
        elif condition_type is ConditionType.label_size:
            cond_mask[:, :, 2:] = 0
        elif condition_type is ConditionType.completion:
            cond_mask = self._completion_mask(cond_mask, mask, generator)
        elif condition_type is ConditionType.unconditional:
            pass
        else:
            raise ValueError(f"Unsupported LayoutFlow condition_type: {condition_type}")

        return cond_mask

    def postprocess(
        self,
        state: Float[torch.Tensor, "batch elements channels"],
        *,
        mask: Bool[torch.Tensor, "batch elements"],
        box_format: BoxFormat | str = "xywh",
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Convert model state back to public layout tensors.

        Args:
            state: Model state tensor.
            mask: Valid-element mask.
            box_format: Requested output box format.
            normalized: Whether to return normalized coordinates.
            canvas_size: Pixel canvas size for denormalized coordinates.

        Returns:
            Dictionary with ``bbox``, ``labels``, and ``mask``.

        Raises:
            ValueError: If denormalized output is requested without
                ``canvas_size``.
        """
        restored = self.preprocess_state(state, reverse=True)
        bbox = clamp_boxes(restored[:, :, :4]) * mask.unsqueeze(-1)
        labels = self.decode_labels(restored[:, :, 4:]) * mask.long()
        fmt = BoxFormat(box_format)
        if fmt is not BoxFormat.xywh:
            if not normalized and canvas_size is None:
                raise ValueError("canvas_size is required for denormalized output")

            if canvas_size is None:
                canvas_size = (1, 1)
            bbox = denormalize_boxes(bbox, canvas_size=canvas_size, box_format=fmt)
            if normalized:
                scale = torch.tensor(
                    (*canvas_size, *canvas_size), dtype=bbox.dtype, device=bbox.device
                )
                bbox = bbox / scale
        elif not normalized:
            if canvas_size is None:
                raise ValueError("canvas_size is required for denormalized output")

            bbox = denormalize_boxes(
                bbox, canvas_size=canvas_size, box_format=BoxFormat.xywh
            )
        return {"bbox": bbox, "labels": labels, "mask": mask}

    def _completion_mask(
        self,
        cond_mask: Int[torch.Tensor, "batch elements channels"],
        mask: Bool[torch.Tensor, "batch elements"],
        generator: torch.Generator | None,
    ) -> Int[torch.Tensor, "batch elements channels"]:
        for i, length in enumerate(mask.sum(dim=1).tolist()):
            if length <= 1:
                continue
            keep = max(1, int(length * 0.2))
            scores = torch.rand(length, device=mask.device, generator=generator)
            idx = scores.topk(keep).indices
            cond_mask[i, idx] = 0
        return cond_mask

    @staticmethod
    def _pad_tensor(
        tensor: Shaped[torch.Tensor, "..."], max_length: int, value: float | int | bool
    ) -> Shaped[torch.Tensor, "..."]:
        if tensor.shape[1] > max_length:
            return tensor[:, :max_length]
        if tensor.shape[1] == max_length:
            return tensor
        pad_shape = (tensor.shape[0], max_length - tensor.shape[1], *tensor.shape[2:])
        pad = torch.full(pad_shape, value, dtype=tensor.dtype, device=tensor.device)
        return torch.cat([tensor, pad], dim=1)

    @staticmethod
    def _num_elements_to_lengths(
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None,
        batch_size: int,
        max_length: int,
        device: torch.device,
    ) -> Int[torch.Tensor, "batch"]:
        if num_elements is None:
            return torch.full(
                (batch_size,), max_length, dtype=torch.long, device=device
            )
        lengths = torch.as_tensor(num_elements, dtype=torch.long, device=device)
        if lengths.ndim == 0:
            lengths = lengths.repeat(batch_size)
        return lengths.clamp(0, max_length)

__init__

__init__(config: LayoutFlowConfig) -> None

Create a processor for a LayoutFlow configuration.

Parameters:

Name Type Description Default
config LayoutFlowConfig

LayoutFlow pipeline configuration.

required
Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
34
35
36
37
38
39
40
41
42
43
def __init__(self, config: LayoutFlowConfig) -> None:
    """Create a processor for a LayoutFlow configuration.

    Args:
        config: LayoutFlow pipeline configuration.
    """
    super().__init__()
    self.config = config
    bit_mask = [1 << k for k in range(config.attr_dim)]
    self.bit_mask = torch.tensor(bit_mask, dtype=torch.long)

__call__

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

Convert public inputs into padded model-ready tensors.

Parameters:

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

Optional boxes in the requested box_format.

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

Optional dataset-local label ids.

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

Optional valid-element mask.

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

Optional element counts used when mask is omitted.

None
batch_size int

Batch size used when tensors are omitted.

1
box_format BoxFormat | str

Format of bbox.

'xywh'
normalized bool

Whether bbox is already normalized to [0, 1].

True
canvas_size tuple[int, int] | None

Pixel canvas size required for denormalized boxes.

None
device device | str | None

Target torch device.

None

Returns:

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

Dictionary with bbox, labels, mask, and length tensors.

Raises:

Type Description
ValueError

If denormalized boxes are missing canvas_size or the box format is unsupported.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
 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
def __call__(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    batch_size: int = 1,
    box_format: BoxFormat | str = "xywh",
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    device: torch.device | str | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Convert public inputs into padded model-ready tensors.

    Args:
        bbox: Optional boxes in the requested ``box_format``.
        labels: Optional dataset-local label ids.
        mask: Optional valid-element mask.
        num_elements: Optional element counts used when ``mask`` is omitted.
        batch_size: Batch size used when tensors are omitted.
        box_format: Format of ``bbox``.
        normalized: Whether ``bbox`` is already normalized to ``[0, 1]``.
        canvas_size: Pixel canvas size required for denormalized boxes.
        device: Target torch device.

    Returns:
        Dictionary with ``bbox``, ``labels``, ``mask``, and ``length`` tensors.

    Raises:
        ValueError: If denormalized boxes are missing ``canvas_size`` or the
            box format is unsupported.
    """
    device = torch.device(device) if device is not None else torch.device("cpu")
    max_length = self.config.max_length
    if bbox is None:
        bbox_t = torch.zeros(
            batch_size, max_length, 4, dtype=torch.float32, device=device
        )
    else:
        bbox_t = torch.as_tensor(bbox, dtype=torch.float32, device=device)
        if bbox_t.ndim == 2:
            bbox_t = bbox_t.unsqueeze(0)
        batch_size = bbox_t.shape[0]
        if not normalized:
            if canvas_size is None:
                raise ValueError("canvas_size is required when normalized=False")

            bbox_t = normalize_boxes(
                bbox_t, canvas_size=canvas_size, box_format=box_format
            )
        else:
            fmt = BoxFormat(box_format)
            if fmt is BoxFormat.ltwh:
                bbox_t = ltwh_to_xywh(bbox_t)
            elif fmt is BoxFormat.ltrb:
                bbox_t = ltrb_to_xywh(bbox_t)
            elif fmt is not BoxFormat.xywh:
                assert_never(fmt)
        bbox_t = self._pad_tensor(bbox_t, max_length, 0.0)
    if labels is None:
        labels_t = torch.zeros(
            batch_size, max_length, dtype=torch.long, device=device
        )
    else:
        labels_t = torch.as_tensor(labels, dtype=torch.long, device=device)
        if labels_t.ndim == 1:
            labels_t = labels_t.unsqueeze(0)
        labels_t = self._pad_tensor(labels_t, max_length, 0)
    if mask is None:
        lengths = self._num_elements_to_lengths(
            num_elements, batch_size, max_length, device
        )
        mask_t = torch.arange(max_length, device=device)[None, :] < lengths[:, None]
    else:
        mask_t = torch.as_tensor(mask, dtype=torch.bool, device=device)
        if mask_t.ndim == 1:
            mask_t = mask_t.unsqueeze(0)
        mask_t = self._pad_tensor(mask_t, max_length, False)
        lengths = mask_t.sum(dim=1).long()
    bbox_t = bbox_t * mask_t.unsqueeze(-1)
    labels_t = labels_t * mask_t.long()
    return {"bbox": bbox_t, "labels": labels_t, "mask": mask_t, "length": lengths}

encode_labels

encode_labels(
    labels: Int[Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements bits"]

Encode integer labels as analog-bit vectors.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
138
139
140
141
142
143
144
145
def encode_labels(
    self, labels: Int[torch.Tensor, "batch elements"]
) -> Float[torch.Tensor, "batch elements bits"]:
    """Encode integer labels as analog-bit vectors."""
    bit_mask = self.bit_mask.to(labels.device)
    return (
        torch.bitwise_and(labels.unsqueeze(-1), bit_mask).float() / bit_mask.float()
    )

decode_labels

decode_labels(
    bits: Float[Tensor, "batch elements bits"],
) -> Int[torch.Tensor, "batch elements"]

Decode analog-bit vectors into integer labels.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
147
148
149
150
151
152
153
154
155
def decode_labels(
    self, bits: Float[torch.Tensor, "batch elements bits"]
) -> Int[torch.Tensor, "batch elements"]:
    """Decode analog-bit vectors into integer labels."""
    bit_mask = self.bit_mask.to(bits.device)
    active = (bits - 0.5 >= 0).long()
    return (
        (active * bit_mask).sum(dim=-1).clamp(0, self.config.num_labels - 1).long()
    )

model_state

model_state(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements channels"]

Concatenate normalized boxes and analog-bit labels.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
157
158
159
160
161
162
163
def model_state(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
) -> Float[torch.Tensor, "batch elements channels"]:
    """Concatenate normalized boxes and analog-bit labels."""
    return torch.cat([bbox, self.encode_labels(labels)], dim=-1)

preprocess_state

preprocess_state(
    state: Float[Tensor, "batch elements channels"],
    *,
    reverse: bool = False,
) -> Float[torch.Tensor, "batch elements channels"]

Map between public [0, 1] state and model distribution range.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
165
166
167
168
169
170
171
172
173
174
def preprocess_state(
    self,
    state: Float[torch.Tensor, "batch elements channels"],
    *,
    reverse: bool = False,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Map between public ``[0, 1]`` state and model distribution range."""
    if self.config.distribution in {"gaussian", "gmm", "uniform", "gauss_uniform"}:
        return (state + 1) / 2 if reverse else 2 * state - 1
    return state

make_condition_mask

make_condition_mask(
    condition_type: ConditionType,
    *,
    mask: Bool[Tensor, "batch elements"],
    generator: Generator | None = None,
) -> Int[torch.Tensor, "batch elements channels"]

Create the condition mask for a conditioning mode.

Parameters:

Name Type Description Default
condition_type ConditionType

Canonical condition or alias.

required
mask Bool[Tensor, 'batch elements']

Valid-element mask.

required
generator Generator | None

Optional generator used by completion masking.

None

Returns:

Type Description
Int[Tensor, 'batch elements channels']

Long tensor where 1 means generated and 0 means conditioned.

Raises:

Type Description
ValueError

If the condition type is unsupported.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
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
def make_condition_mask(
    self,
    condition_type: ConditionType,
    *,
    mask: Bool[torch.Tensor, "batch elements"],
    generator: torch.Generator | None = None,
) -> Int[torch.Tensor, "batch elements channels"]:
    """Create the condition mask for a conditioning mode.

    Args:
        condition_type: Canonical condition or alias.
        mask: Valid-element mask.
        generator: Optional generator used by completion masking.

    Returns:
        Long tensor where ``1`` means generated and ``0`` means conditioned.

    Raises:
        ValueError: If the condition type is unsupported.
    """
    batch, seq = mask.shape
    cond_mask = torch.ones(
        batch,
        seq,
        self.config.sample_dim,
        dtype=torch.long,
        device=mask.device,
    )
    if condition_type in {ConditionType.label, ConditionType.refinement}:
        cond_mask[:, :, 4:] = 0
    elif condition_type is ConditionType.label_size:
        cond_mask[:, :, 2:] = 0
    elif condition_type is ConditionType.completion:
        cond_mask = self._completion_mask(cond_mask, mask, generator)
    elif condition_type is ConditionType.unconditional:
        pass
    else:
        raise ValueError(f"Unsupported LayoutFlow condition_type: {condition_type}")

    return cond_mask

postprocess

postprocess(
    state: Float[Tensor, "batch elements channels"],
    *,
    mask: Bool[Tensor, "batch elements"],
    box_format: BoxFormat | str = "xywh",
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]

Convert model state back to public layout tensors.

Parameters:

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

Model state tensor.

required
mask Bool[Tensor, 'batch elements']

Valid-element mask.

required
box_format BoxFormat | str

Requested output box format.

'xywh'
normalized bool

Whether to return normalized coordinates.

True
canvas_size tuple[int, int] | None

Pixel canvas size for denormalized coordinates.

None

Returns:

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

Dictionary with bbox, labels, and mask.

Raises:

Type Description
ValueError

If denormalized output is requested without canvas_size.

Source code in models/layout-flow/src/layout_flow/processing_layout_flow.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def postprocess(
    self,
    state: Float[torch.Tensor, "batch elements channels"],
    *,
    mask: Bool[torch.Tensor, "batch elements"],
    box_format: BoxFormat | str = "xywh",
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Convert model state back to public layout tensors.

    Args:
        state: Model state tensor.
        mask: Valid-element mask.
        box_format: Requested output box format.
        normalized: Whether to return normalized coordinates.
        canvas_size: Pixel canvas size for denormalized coordinates.

    Returns:
        Dictionary with ``bbox``, ``labels``, and ``mask``.

    Raises:
        ValueError: If denormalized output is requested without
            ``canvas_size``.
    """
    restored = self.preprocess_state(state, reverse=True)
    bbox = clamp_boxes(restored[:, :, :4]) * mask.unsqueeze(-1)
    labels = self.decode_labels(restored[:, :, 4:]) * mask.long()
    fmt = BoxFormat(box_format)
    if fmt is not BoxFormat.xywh:
        if not normalized and canvas_size is None:
            raise ValueError("canvas_size is required for denormalized output")

        if canvas_size is None:
            canvas_size = (1, 1)
        bbox = denormalize_boxes(bbox, canvas_size=canvas_size, box_format=fmt)
        if normalized:
            scale = torch.tensor(
                (*canvas_size, *canvas_size), dtype=bbox.dtype, device=bbox.device
            )
            bbox = bbox / scale
    elif not normalized:
        if canvas_size is None:
            raise ValueError("canvas_size is required for denormalized output")

        bbox = denormalize_boxes(
            bbox, canvas_size=canvas_size, box_format=BoxFormat.xywh
        )
    return {"bbox": bbox, "labels": labels, "mask": mask}

normalize_condition_type

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

Normalize condition aliases to a canonical ConditionType.

Parameters:

Name Type Description Default
condition_type ConditionType | str

Canonical condition enum or a public/release alias.

required

Returns:

Type Description
ConditionType

Canonical condition enum.

Raises:

Type Description
ValueError

If the condition type is unknown.

Examples:

>>> str(normalize_condition_type("gen_t"))
'label'
>>> str(normalize_condition_type("gen_r"))
'relation'
Source code in lib/laygen/src/laygen/common/conditions.py
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 normalize_condition_type(condition_type: ConditionType | str) -> ConditionType:
    """Normalize condition aliases to a canonical ``ConditionType``.

    Args:
        condition_type: Canonical condition enum or a public/release alias.

    Returns:
        Canonical condition enum.

    Raises:
        ValueError: If the condition type is unknown.

    Examples:
        >>> str(normalize_condition_type("gen_t"))
        'label'
        >>> str(normalize_condition_type("gen_r"))
        'relation'
    """
    if isinstance(condition_type, ConditionType):
        return condition_type
    try:
        return _CONDITION_ALIASES[
            ConditionAlias(condition_type.lower().replace("-", "_"))
        ]
    except ValueError as exc:
        raise ValueError(f"Unknown condition_type: {condition_type}") from exc

sampling

Initial-state sampling helpers for LayoutFlow.

InitialDistribution

Bases: StrEnum

Supported initial-state distributions.

Source code in models/layout-flow/src/layout_flow/sampling.py
12
13
14
15
16
class InitialDistribution(StrEnum):
    """Supported initial-state distributions."""

    gaussian = "gaussian"
    uniform = "uniform"

sample_initial_state

sample_initial_state(
    *,
    batch_size: int,
    max_length: int,
    lengths: Int[Tensor, "batch"],
    dim: int,
    distribution: InitialDistribution | str = "gaussian",
    generator: Generator | None = None,
    device: device | str | None = None,
    dtype: dtype = torch.float32,
) -> Float[torch.Tensor, "batch elements channels"]

Sample a padded initial LayoutFlow state.

Parameters:

Name Type Description Default
batch_size int

Number of layouts.

required
max_length int

Maximum number of elements per layout.

required
lengths Int[Tensor, 'batch']

Valid element counts.

required
dim int

Per-element state dimension.

required
distribution InitialDistribution | str

Initial sampling distribution.

'gaussian'
generator Generator | None

Optional torch random generator.

None
device device | str | None

Target torch device.

None
dtype dtype

Target tensor dtype.

float32

Returns:

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

Initial state tensor with padded elements zeroed.

Raises:

Type Description
ValueError

If distribution is unsupported.

Examples:

>>> lengths = torch.tensor([1])
>>> sample_initial_state(batch_size=1, max_length=2, lengths=lengths, dim=3).shape
torch.Size([1, 2, 3])
Source code in models/layout-flow/src/layout_flow/sampling.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def sample_initial_state(
    *,
    batch_size: int,
    max_length: int,
    lengths: Int[torch.Tensor, "batch"],
    dim: int,
    distribution: InitialDistribution | str = "gaussian",
    generator: torch.Generator | None = None,
    device: torch.device | str | None = None,
    dtype: torch.dtype = torch.float32,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Sample a padded initial LayoutFlow state.

    Args:
        batch_size: Number of layouts.
        max_length: Maximum number of elements per layout.
        lengths: Valid element counts.
        dim: Per-element state dimension.
        distribution: Initial sampling distribution.
        generator: Optional torch random generator.
        device: Target torch device.
        dtype: Target tensor dtype.

    Returns:
        Initial state tensor with padded elements zeroed.

    Raises:
        ValueError: If ``distribution`` is unsupported.

    Examples:
        >>> lengths = torch.tensor([1])
        >>> sample_initial_state(batch_size=1, max_length=2, lengths=lengths, dim=3).shape
        torch.Size([1, 2, 3])
    """
    device = torch.device(device) if device is not None else lengths.device
    dist = InitialDistribution(distribution)
    if dist is InitialDistribution.gaussian:
        sample = torch.randn(
            batch_size,
            max_length,
            dim,
            generator=generator,
            device=device,
            dtype=dtype,
        )
    elif dist is InitialDistribution.uniform:
        sample = (
            2
            * torch.rand(
                batch_size,
                max_length,
                dim,
                generator=generator,
                device=device,
                dtype=dtype,
            )
            - 1
        )
    else:
        assert_never(dist)
    mask = torch.arange(max_length, device=device)[None, :] < lengths[:, None].to(
        device
    )
    return sample * mask.unsqueeze(-1)

scheduling_layout_flow

Euler scheduler for LayoutFlow flow-matching inference.

LayoutFlowSchedulerOutput dataclass

Bases: BaseOutput

Output of one LayoutFlow Euler scheduler step.

Source code in models/layout-flow/src/layout_flow/scheduling_layout_flow.py
15
16
17
18
19
@dataclass
class LayoutFlowSchedulerOutput(BaseOutput):
    """Output of one LayoutFlow Euler scheduler step."""

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

LayoutFlowEulerScheduler

Bases: SchedulerMixin, ConfigMixin

Increasing-time Euler scheduler used by LayoutFlow.

Source code in models/layout-flow/src/layout_flow/scheduling_layout_flow.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 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
class LayoutFlowEulerScheduler(SchedulerMixin, ConfigMixin):
    """Increasing-time Euler scheduler used by LayoutFlow."""

    config_name: str = "scheduler_config.json"
    order: int = 1

    @register_to_config
    def __init__(
        self, num_inference_steps: int = 100, start: float = 0.0, end: float = 1.0
    ) -> None:
        """Initialize the scheduler.

        Args:
            num_inference_steps: Number of Euler steps.
            start: Initial integration time.
            end: Final integration time.
        """
        self.num_inference_steps = num_inference_steps
        self.start = start
        self.end = end
        self.timesteps = torch.linspace(start, end, num_inference_steps)

    def set_timesteps(
        self,
        num_inference_steps: int | None = None,
        *,
        device: torch.device | str | None = None,
        start: float | None = None,
        end: float | None = None,
    ) -> None:
        """Set the integration timesteps.

        Args:
            num_inference_steps: Optional number of inference steps.
            device: Optional target device.
            start: Optional start time.
            end: Optional end time.
        """
        steps = num_inference_steps or self.config.num_inference_steps
        start = self.config.start if start is None else start
        end = self.config.end if end is None else end
        self.timesteps = torch.linspace(start, end, steps, device=device)

    def scale_model_input(
        self,
        sample: Float[torch.Tensor, "batch elements channels"],
        timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Return the sample unchanged for Diffusers scheduler compatibility."""
        del timestep
        return sample

    @overload
    def step(
        self,
        model_output: Float[torch.Tensor, "batch elements channels"],
        timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
        sample: Float[torch.Tensor, "batch elements channels"],
        *,
        next_timestep: Float[torch.Tensor, ""]
        | Float[torch.Tensor, "batch"]
        | float
        | None = None,
        return_dict: Literal[True] = True,
    ) -> LayoutFlowSchedulerOutput: ...

    @overload
    def step(
        self,
        model_output: Float[torch.Tensor, "batch elements channels"],
        timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
        sample: Float[torch.Tensor, "batch elements channels"],
        *,
        next_timestep: Float[torch.Tensor, ""]
        | Float[torch.Tensor, "batch"]
        | float
        | None = None,
        return_dict: Literal[False],
    ) -> tuple[Float[torch.Tensor, "batch elements channels"]]: ...

    def step(
        self,
        model_output: Float[torch.Tensor, "batch elements channels"],
        timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
        sample: Float[torch.Tensor, "batch elements channels"],
        *,
        next_timestep: Float[torch.Tensor, ""]
        | Float[torch.Tensor, "batch"]
        | float
        | None = None,
        return_dict: bool = True,
    ) -> (
        LayoutFlowSchedulerOutput
        | tuple[Float[torch.Tensor, "batch elements channels"]]
    ):
        """Advance the sample with one Euler step.

        Args:
            model_output: Predicted vector field.
            timestep: Current integration time.
            sample: Current sample state.
            next_timestep: Optional next integration time.
            return_dict: Whether to return a scheduler output dataclass.

        Returns:
            Scheduler output dataclass or single-item tuple.
        """
        t = torch.as_tensor(timestep, device=sample.device, dtype=sample.dtype)
        if next_timestep is None:
            matches = torch.isclose(self.timesteps.to(sample.device, sample.dtype), t)
            idx = int(matches.nonzero()[0].item())
            if idx >= len(self.timesteps) - 1:
                next_timestep = t
            else:
                next_timestep = self.timesteps[idx + 1]
        t_next = torch.as_tensor(
            next_timestep, device=sample.device, dtype=sample.dtype
        )
        prev_sample = sample + (t_next - t) * model_output
        if not return_dict:
            return (prev_sample,)
        return LayoutFlowSchedulerOutput(prev_sample=prev_sample)

__init__

__init__(
    num_inference_steps: int = 100,
    start: float = 0.0,
    end: float = 1.0,
) -> None

Initialize the scheduler.

Parameters:

Name Type Description Default
num_inference_steps int

Number of Euler steps.

100
start float

Initial integration time.

0.0
end float

Final integration time.

1.0
Source code in models/layout-flow/src/layout_flow/scheduling_layout_flow.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@register_to_config
def __init__(
    self, num_inference_steps: int = 100, start: float = 0.0, end: float = 1.0
) -> None:
    """Initialize the scheduler.

    Args:
        num_inference_steps: Number of Euler steps.
        start: Initial integration time.
        end: Final integration time.
    """
    self.num_inference_steps = num_inference_steps
    self.start = start
    self.end = end
    self.timesteps = torch.linspace(start, end, num_inference_steps)

set_timesteps

set_timesteps(
    num_inference_steps: int | None = None,
    *,
    device: device | str | None = None,
    start: float | None = None,
    end: float | None = None,
) -> None

Set the integration timesteps.

Parameters:

Name Type Description Default
num_inference_steps int | None

Optional number of inference steps.

None
device device | str | None

Optional target device.

None
start float | None

Optional start time.

None
end float | None

Optional end time.

None
Source code in models/layout-flow/src/layout_flow/scheduling_layout_flow.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def set_timesteps(
    self,
    num_inference_steps: int | None = None,
    *,
    device: torch.device | str | None = None,
    start: float | None = None,
    end: float | None = None,
) -> None:
    """Set the integration timesteps.

    Args:
        num_inference_steps: Optional number of inference steps.
        device: Optional target device.
        start: Optional start time.
        end: Optional end time.
    """
    steps = num_inference_steps or self.config.num_inference_steps
    start = self.config.start if start is None else start
    end = self.config.end if end is None else end
    self.timesteps = torch.linspace(start, end, steps, device=device)

scale_model_input

scale_model_input(
    sample: Float[Tensor, "batch elements channels"],
    timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float,
) -> Float[torch.Tensor, "batch elements channels"]

Return the sample unchanged for Diffusers scheduler compatibility.

Source code in models/layout-flow/src/layout_flow/scheduling_layout_flow.py
65
66
67
68
69
70
71
72
def scale_model_input(
    self,
    sample: Float[torch.Tensor, "batch elements channels"],
    timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Return the sample unchanged for Diffusers scheduler compatibility."""
    del timestep
    return sample

step

step(
    model_output: Float[Tensor, "batch elements channels"],
    timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float,
    sample: Float[Tensor, "batch elements channels"],
    *,
    next_timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float
    | None = None,
    return_dict: Literal[True] = True,
) -> LayoutFlowSchedulerOutput
step(
    model_output: Float[Tensor, "batch elements channels"],
    timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float,
    sample: Float[Tensor, "batch elements channels"],
    *,
    next_timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float
    | None = None,
    return_dict: Literal[False],
) -> tuple[Float[torch.Tensor, "batch elements channels"]]
step(
    model_output: Float[Tensor, "batch elements channels"],
    timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float,
    sample: Float[Tensor, "batch elements channels"],
    *,
    next_timestep: Float[Tensor, ""]
    | Float[Tensor, "batch"]
    | float
    | None = None,
    return_dict: bool = True,
) -> (
    LayoutFlowSchedulerOutput
    | tuple[Float[torch.Tensor, "batch elements channels"]]
)

Advance the sample with one Euler step.

Parameters:

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

Predicted vector field.

required
timestep Float[Tensor, ''] | Float[Tensor, 'batch'] | float

Current integration time.

required
sample Float[Tensor, 'batch elements channels']

Current sample state.

required
next_timestep Float[Tensor, ''] | Float[Tensor, 'batch'] | float | None

Optional next integration time.

None
return_dict bool

Whether to return a scheduler output dataclass.

True

Returns:

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

Scheduler output dataclass or single-item tuple.

Source code in models/layout-flow/src/layout_flow/scheduling_layout_flow.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def step(
    self,
    model_output: Float[torch.Tensor, "batch elements channels"],
    timestep: Float[torch.Tensor, ""] | Float[torch.Tensor, "batch"] | float,
    sample: Float[torch.Tensor, "batch elements channels"],
    *,
    next_timestep: Float[torch.Tensor, ""]
    | Float[torch.Tensor, "batch"]
    | float
    | None = None,
    return_dict: bool = True,
) -> (
    LayoutFlowSchedulerOutput
    | tuple[Float[torch.Tensor, "batch elements channels"]]
):
    """Advance the sample with one Euler step.

    Args:
        model_output: Predicted vector field.
        timestep: Current integration time.
        sample: Current sample state.
        next_timestep: Optional next integration time.
        return_dict: Whether to return a scheduler output dataclass.

    Returns:
        Scheduler output dataclass or single-item tuple.
    """
    t = torch.as_tensor(timestep, device=sample.device, dtype=sample.dtype)
    if next_timestep is None:
        matches = torch.isclose(self.timesteps.to(sample.device, sample.dtype), t)
        idx = int(matches.nonzero()[0].item())
        if idx >= len(self.timesteps) - 1:
            next_timestep = t
        else:
            next_timestep = self.timesteps[idx + 1]
    t_next = torch.as_tensor(
        next_timestep, device=sample.device, dtype=sample.dtype
    )
    prev_sample = sample + (t_next - t) * model_output
    if not return_dict:
        return (prev_sample,)
    return LayoutFlowSchedulerOutput(prev_sample=prev_sample)

training

Training entry points for LayoutFlow.

config

Configuration enums for LayoutFlow training.

LayoutFlowTrainingDatasetName module-attribute

LayoutFlowTrainingDatasetName: TypeAlias = Literal[
    "rico25", "publaynet"
]

Dataset names supported by package-local LayoutFlow training data.

LayoutFlowTrainingSplit module-attribute

LayoutFlowTrainingSplit: TypeAlias = Literal[
    "train", "validation", "test"
]

HDF5 split names supported by package-local LayoutFlow training data.

LayoutFlowTrainingScheduler module-attribute

LayoutFlowTrainingScheduler: TypeAlias = Literal[
    "reduce_on_plateau"
]

Scheduler names supported by package-local LayoutFlow training.

LayoutFlowConditionPolicy module-attribute

LayoutFlowConditionPolicy: TypeAlias = Literal['random4']

Condition-mask policy names supported by package-local LayoutFlow training.

LayoutFlowSeedMode

Bases: StrEnum

Seed modes for regular and deterministic LayoutFlow training.

Source code in models/layout-flow/src/layout_flow/training/config.py
22
23
24
25
26
class LayoutFlowSeedMode(StrEnum):
    """Seed modes for regular and deterministic LayoutFlow training."""

    default = auto()
    deterministic = auto()

datamodule

LightningDataModule for LayoutFlow training.

LayoutFlowDataModule

Bases: LightningDataModule

Package-local LightningDataModule for LayoutFlow HDF5 data.

Source code in models/layout-flow/src/layout_flow/training/datamodule.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 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
class LayoutFlowDataModule(LightningDataModule):
    """Package-local LightningDataModule for LayoutFlow HDF5 data."""

    def __init__(
        self,
        *,
        data_path: str | Path,
        dataset_name: LayoutFlowTrainingDatasetName = "publaynet",
        batch_size: int = 256,
        max_length: int = 20,
        num_workers: int = 4,
        box_format: BoxFormat | str = BoxFormat.xywh,
        lex_order: bool = False,
        permute_elements: bool = False,
        inoue_split: bool = False,
    ) -> None:
        """Initialize datamodule settings."""
        super().__init__()
        self.data_path = Path(data_path)
        self.dataset_name = dataset_name
        self.batch_size = batch_size
        self.max_length = max_length
        self.num_workers = num_workers
        self.box_format = box_format
        self.lex_order = lex_order
        self.permute_elements = permute_elements
        self.inoue_split = inoue_split
        self.train_dataset: LayoutFlowH5Dataset | None = None
        self.val_dataset: LayoutFlowH5Dataset | None = None
        self.test_dataset: LayoutFlowH5Dataset | None = None

    def setup(self, stage: str | None = None) -> None:
        """Open datasets for the requested stage."""
        if stage in {None, "fit"}:
            self.train_dataset = self._dataset("train")
            self.val_dataset = self._dataset("validation")
        if stage in {None, "test"}:
            self.test_dataset = self._dataset("test")

    def train_dataloader(
        self,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, ...] | str]]:
        """Return the training dataloader."""
        if self.train_dataset is None:
            self.setup("fit")
        return self._loader(self.train_dataset, shuffle=True)

    def val_dataloader(
        self,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, ...] | str]]:
        """Return the validation dataloader."""
        if self.val_dataset is None:
            self.setup("fit")
        return self._loader(self.val_dataset, shuffle=False)

    def test_dataloader(
        self,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, ...] | str]]:
        """Return the test dataloader."""
        if self.test_dataset is None:
            self.setup("test")
        return self._loader(self.test_dataset, shuffle=False)

    def _dataset(self, split: LayoutFlowTrainingSplit) -> LayoutFlowH5Dataset:
        return LayoutFlowH5Dataset(
            data_path=self.data_path,
            dataset_name=self.dataset_name,
            split=split,
            lex_order=self.lex_order,
            permute_elements=self.permute_elements,
            inoue_split=self.inoue_split,
        )

    def _loader(
        self, dataset: LayoutFlowH5Dataset | None, *, shuffle: bool
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, ...] | str]]:
        if dataset is None:
            raise RuntimeError("Dataset has not been initialized")

        return DataLoader(
            dataset,
            batch_size=self.batch_size,
            shuffle=shuffle,
            num_workers=self.num_workers,
            collate_fn=partial(
                collate_layout_flow_batch,
                max_length=self.max_length,
                box_format=self.box_format,
            ),
        )
__init__
__init__(
    *,
    data_path: str | Path,
    dataset_name: LayoutFlowTrainingDatasetName = "publaynet",
    batch_size: int = 256,
    max_length: int = 20,
    num_workers: int = 4,
    box_format: BoxFormat | str = BoxFormat.xywh,
    lex_order: bool = False,
    permute_elements: bool = False,
    inoue_split: bool = False,
) -> None

Initialize datamodule settings.

Source code in models/layout-flow/src/layout_flow/training/datamodule.py
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
def __init__(
    self,
    *,
    data_path: str | Path,
    dataset_name: LayoutFlowTrainingDatasetName = "publaynet",
    batch_size: int = 256,
    max_length: int = 20,
    num_workers: int = 4,
    box_format: BoxFormat | str = BoxFormat.xywh,
    lex_order: bool = False,
    permute_elements: bool = False,
    inoue_split: bool = False,
) -> None:
    """Initialize datamodule settings."""
    super().__init__()
    self.data_path = Path(data_path)
    self.dataset_name = dataset_name
    self.batch_size = batch_size
    self.max_length = max_length
    self.num_workers = num_workers
    self.box_format = box_format
    self.lex_order = lex_order
    self.permute_elements = permute_elements
    self.inoue_split = inoue_split
    self.train_dataset: LayoutFlowH5Dataset | None = None
    self.val_dataset: LayoutFlowH5Dataset | None = None
    self.test_dataset: LayoutFlowH5Dataset | None = None
setup
setup(stage: str | None = None) -> None

Open datasets for the requested stage.

Source code in models/layout-flow/src/layout_flow/training/datamodule.py
49
50
51
52
53
54
55
def setup(self, stage: str | None = None) -> None:
    """Open datasets for the requested stage."""
    if stage in {None, "fit"}:
        self.train_dataset = self._dataset("train")
        self.val_dataset = self._dataset("validation")
    if stage in {None, "test"}:
        self.test_dataset = self._dataset("test")
train_dataloader
train_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, ...] | str]
]

Return the training dataloader.

Source code in models/layout-flow/src/layout_flow/training/datamodule.py
57
58
59
60
61
62
63
def train_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, ...] | str]]:
    """Return the training dataloader."""
    if self.train_dataset is None:
        self.setup("fit")
    return self._loader(self.train_dataset, shuffle=True)
val_dataloader
val_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, ...] | str]
]

Return the validation dataloader.

Source code in models/layout-flow/src/layout_flow/training/datamodule.py
65
66
67
68
69
70
71
def val_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, ...] | str]]:
    """Return the validation dataloader."""
    if self.val_dataset is None:
        self.setup("fit")
    return self._loader(self.val_dataset, shuffle=False)
test_dataloader
test_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, ...] | str]
]

Return the test dataloader.

Source code in models/layout-flow/src/layout_flow/training/datamodule.py
73
74
75
76
77
78
79
def test_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, ...] | str]]:
    """Return the test dataloader."""
    if self.test_dataset is None:
        self.setup("test")
    return self._loader(self.test_dataset, shuffle=False)

dataset

HDF5 dataset and collation helpers for LayoutFlow training.

LayoutFlowH5Dataset

Bases: Dataset[dict[str, Shaped[Tensor, '...'] | str]]

HDF5 dataset for LayoutFlow training.

Source code in models/layout-flow/src/layout_flow/training/dataset.py
 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
class LayoutFlowH5Dataset(Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]]):
    """HDF5 dataset for LayoutFlow training."""

    def __init__(
        self,
        *,
        data_path: str | Path,
        dataset_name: LayoutFlowTrainingDatasetName,
        split: LayoutFlowTrainingSplit = "train",
        lex_order: bool = False,
        permute_elements: bool = False,
        inoue_split: bool = False,
    ) -> None:
        """Open one LayoutFlow HDF5 split.

        Args:
            data_path: Directory containing LayoutFlow HDF5 files.
            dataset_name: ``rico25`` or ``publaynet``.
            split: ``train``, ``validation``, or ``test``.
            lex_order: Whether to use lexical-order files.
            permute_elements: Whether to permute elements at access time.
            inoue_split: Whether PubLayNet uses Inoue split filenames.

        Raises:
            ValueError: If the dataset or split is unsupported.
            FileNotFoundError: If the expected HDF5 file is absent.
        """
        super().__init__()
        self.data_path = Path(data_path)
        self.dataset_name = dataset_name
        self.split = split
        self.permute_elements = permute_elements
        file_name = self._file_name(dataset_name, split, lex_order, inoue_split)
        path = self.data_path / file_name
        if not path.exists():
            raise FileNotFoundError(path)

        import h5pickle as h5py

        self.data = h5py.File(str(path))
        self.keys = list(self.data.keys())

    def __len__(self) -> int:
        """Return dataset size."""
        return len(self.keys)

    def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
        """Return a raw sample from the HDF5 file."""
        key = self.keys[index]
        sample = self.data[key]
        sample_dict: dict[str, Shaped[torch.Tensor, "..."] | str] = {"id": str(key)}
        for feature in sample.keys():
            value = torch.from_numpy(np.array(sample[feature]))
            sample_dict["type" if feature == "categories" else feature] = value
        if self.permute_elements:
            length = int(torch.as_tensor(sample_dict["length"]).item())
            randperm = torch.randperm(length)
            sample_dict["type"] = torch.as_tensor(sample_dict["type"])[randperm]
            sample_dict["bbox"] = torch.as_tensor(sample_dict["bbox"])[randperm]
        return sample_dict

    @staticmethod
    def _file_name(
        dataset_name: LayoutFlowTrainingDatasetName,
        split: LayoutFlowTrainingSplit,
        lex_order: bool,
        inoue_split: bool,
    ) -> str:
        if dataset_name == "rico25":
            prefix = "ldm_lex_rico" if lex_order else "ldm_rico"
            return f"{prefix}_{split if split != 'validation' else 'val'}.h5"
        if dataset_name == "publaynet":
            split_key = "val" if split == "validation" else split
            if inoue_split:
                return f"publaynet_{split_key}_inoue.h5"
            if lex_order:
                return f"ldm_lex_publaynet_{split_key}.h5"
            return _SPLIT_FILES[dataset_name][split]
        raise ValueError(f"Unsupported LayoutFlow dataset_name: {dataset_name}")
__init__
__init__(
    *,
    data_path: str | Path,
    dataset_name: LayoutFlowTrainingDatasetName,
    split: LayoutFlowTrainingSplit = "train",
    lex_order: bool = False,
    permute_elements: bool = False,
    inoue_split: bool = False,
) -> None

Open one LayoutFlow HDF5 split.

Parameters:

Name Type Description Default
data_path str | Path

Directory containing LayoutFlow HDF5 files.

required
dataset_name LayoutFlowTrainingDatasetName

rico25 or publaynet.

required
split LayoutFlowTrainingSplit

train, validation, or test.

'train'
lex_order bool

Whether to use lexical-order files.

False
permute_elements bool

Whether to permute elements at access time.

False
inoue_split bool

Whether PubLayNet uses Inoue split filenames.

False

Raises:

Type Description
ValueError

If the dataset or split is unsupported.

FileNotFoundError

If the expected HDF5 file is absent.

Source code in models/layout-flow/src/layout_flow/training/dataset.py
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
def __init__(
    self,
    *,
    data_path: str | Path,
    dataset_name: LayoutFlowTrainingDatasetName,
    split: LayoutFlowTrainingSplit = "train",
    lex_order: bool = False,
    permute_elements: bool = False,
    inoue_split: bool = False,
) -> None:
    """Open one LayoutFlow HDF5 split.

    Args:
        data_path: Directory containing LayoutFlow HDF5 files.
        dataset_name: ``rico25`` or ``publaynet``.
        split: ``train``, ``validation``, or ``test``.
        lex_order: Whether to use lexical-order files.
        permute_elements: Whether to permute elements at access time.
        inoue_split: Whether PubLayNet uses Inoue split filenames.

    Raises:
        ValueError: If the dataset or split is unsupported.
        FileNotFoundError: If the expected HDF5 file is absent.
    """
    super().__init__()
    self.data_path = Path(data_path)
    self.dataset_name = dataset_name
    self.split = split
    self.permute_elements = permute_elements
    file_name = self._file_name(dataset_name, split, lex_order, inoue_split)
    path = self.data_path / file_name
    if not path.exists():
        raise FileNotFoundError(path)

    import h5pickle as h5py

    self.data = h5py.File(str(path))
    self.keys = list(self.data.keys())
__len__
__len__() -> int

Return dataset size.

Source code in models/layout-flow/src/layout_flow/training/dataset.py
74
75
76
def __len__(self) -> int:
    """Return dataset size."""
    return len(self.keys)
__getitem__
__getitem__(
    index: int,
) -> dict[str, Shaped[torch.Tensor, "..."] | str]

Return a raw sample from the HDF5 file.

Source code in models/layout-flow/src/layout_flow/training/dataset.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
    """Return a raw sample from the HDF5 file."""
    key = self.keys[index]
    sample = self.data[key]
    sample_dict: dict[str, Shaped[torch.Tensor, "..."] | str] = {"id": str(key)}
    for feature in sample.keys():
        value = torch.from_numpy(np.array(sample[feature]))
        sample_dict["type" if feature == "categories" else feature] = value
    if self.permute_elements:
        length = int(torch.as_tensor(sample_dict["length"]).item())
        randperm = torch.randperm(length)
        sample_dict["type"] = torch.as_tensor(sample_dict["type"])[randperm]
        sample_dict["bbox"] = torch.as_tensor(sample_dict["bbox"])[randperm]
    return sample_dict

collate_layout_flow_batch

collate_layout_flow_batch(
    batch: Sequence[dict[str, Shaped[Tensor, "..."] | str]],
    *,
    max_length: int | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
) -> dict[str, Shaped[torch.Tensor, "..."] | list[str]]

Collate LayoutFlow samples with fixed-length padding.

Parameters:

Name Type Description Default
batch Sequence[dict[str, Shaped[Tensor, '...'] | str]]

Raw sample dictionaries.

required
max_length int | None

Optional fixed maximum sequence length.

None
box_format BoxFormat | str

Output box format. xywh converts source ltwh boxes to center coordinates.

xywh

Returns:

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

Collated batch with bbox, type, mask, length, and optional

dict[str, Shaped[Tensor, '...'] | list[str]]

id.

Raises:

Type Description
ValueError

If box format is unsupported.

Examples:

>>> sample = {"bbox": torch.tensor([[0.0, 0.0, 0.2, 0.4]]), "type": torch.tensor([1]), "length": torch.tensor(1)}
>>> collate_layout_flow_batch([sample], max_length=2)["bbox"].shape
torch.Size([1, 2, 4])
Source code in models/layout-flow/src/layout_flow/training/dataset.py
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
def collate_layout_flow_batch(
    batch: Sequence[dict[str, Shaped[torch.Tensor, "..."] | str]],
    *,
    max_length: int | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
) -> dict[str, Shaped[torch.Tensor, "..."] | list[str]]:
    """Collate LayoutFlow samples with fixed-length padding.

    Args:
        batch: Raw sample dictionaries.
        max_length: Optional fixed maximum sequence length.
        box_format: Output box format. ``xywh`` converts source ``ltwh`` boxes to
            center coordinates.

    Returns:
        Collated batch with ``bbox``, ``type``, ``mask``, ``length``, and optional
        ``id``.

    Raises:
        ValueError: If box format is unsupported.

    Examples:
        >>> sample = {"bbox": torch.tensor([[0.0, 0.0, 0.2, 0.4]]), "type": torch.tensor([1]), "length": torch.tensor(1)}
        >>> collate_layout_flow_batch([sample], max_length=2)["bbox"].shape
        torch.Size([1, 2, 4])
    """
    total_elems = [len(example["type"]) for example in batch]
    target_length = max(total_elems) if max_length is None else max_length
    collated: list[dict[str, Shaped[torch.Tensor, "..."] | str]] = []
    fmt = BoxFormat(box_format)
    for example, total in zip(batch, total_elems, strict=True):
        item = dict(example)
        length = int(torch.as_tensor(item["length"]).squeeze().item())
        clipped = min(length, target_length)
        item["length"] = torch.tensor(clipped, dtype=torch.int)
        item["mask"] = _mask_tensor(clipped, target_length)
        item["type"] = _copy_1d(torch.as_tensor(item["type"]), target_length, torch.int)
        bbox = _copy_bbox(
            torch.as_tensor(item["bbox"], dtype=torch.float32), total, target_length
        )
        if fmt is BoxFormat.xywh:
            bbox[:, 0] += bbox[:, 2] / 2
            bbox[:, 1] += bbox[:, 3] / 2
        elif fmt is BoxFormat.ltrb:
            bbox[:, 2] += bbox[:, 0]
            bbox[:, 3] += bbox[:, 1]
        elif fmt is not BoxFormat.ltwh:
            raise ValueError(f"Unsupported box_format: {box_format}")

        item["bbox"] = bbox
        collated.append(item)
    ids = [str(item.pop("id")) for item in collated if "id" in item]
    output = default_collate(collated)
    if ids:
        output["id"] = ids
    return output

lightning_module

PyTorch Lightning module for LayoutFlow training.

LayoutFlowTrainingModule

Bases: LightningModule

Lightning training wrapper around LayoutFlowTransformerModel.

Source code in models/layout-flow/src/layout_flow/training/lightning_module.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 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
class LayoutFlowTrainingModule(LightningModule):
    """Lightning training wrapper around ``LayoutFlowTransformerModel``."""

    def __init__(
        self,
        *,
        config: LayoutFlowConfig,
        model: LayoutFlowTransformerModel | None = None,
        learning_rate: float = 0.0005,
        scheduler: LayoutFlowTrainingScheduler | None = "reduce_on_plateau",
        condition_policy: LayoutFlowConditionPolicy = "random4",
        geom_l1_weight: float = 0.2,
        seed_mode: LayoutFlowSeedMode | str = LayoutFlowSeedMode.default,
        fid_calc_every_n: int = 20,
    ) -> None:
        """Initialize LayoutFlow training state."""
        super().__init__()
        self.layout_flow_config = config
        self.model = model or LayoutFlowTransformerModel(
            num_labels=self.layout_flow_config.num_labels,
            latent_dim=self.layout_flow_config.latent_dim,
            tr_enc_only=self.layout_flow_config.tr_enc_only,
            d_model=self.layout_flow_config.d_model,
            nhead=self.layout_flow_config.nhead,
            dim_feedforward=self.layout_flow_config.dim_feedforward,
            num_layers=self.layout_flow_config.num_layers,
            dropout=self.layout_flow_config.dropout,
            use_pos_enc=self.layout_flow_config.use_pos_enc,
            attr_encoding=self.layout_flow_config.attr_encoding,
            seq_type=self.layout_flow_config.seq_type,
        )
        self.processor = LayoutFlowProcessor(self.layout_flow_config)
        self.learning_rate = learning_rate
        self.scheduler = scheduler
        self.condition_policy = condition_policy
        self.geom_l1_weight = geom_l1_weight
        self.seed_mode = LayoutFlowSeedMode(seed_mode)
        self.fid_calc_every_n = fid_calc_every_n
        self.geom_dim = 4
        self.attr_dim = self.layout_flow_config.attr_dim
        self.latest_step_trace: dict[str, Shaped[torch.Tensor, "..."]] = {}

    def configure_optimizers(self) -> OptimizerLRScheduler:
        """Return AdamW and optional ReduceLROnPlateau."""
        optimizer = torch.optim.AdamW(
            self.model.parameters(), lr=self.learning_rate, betas=(0.9, 0.98)
        )
        if self.scheduler == "reduce_on_plateau":
            scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer)
            return {
                "optimizer": optimizer,
                "lr_scheduler": {
                    "scheduler": scheduler,
                    "monitor": "FID_Layout",
                    "frequency": self.fid_calc_every_n,
                },
            }
        return optimizer

    def forward(
        self,
        xt: Float[torch.Tensor, "batch elements channels"],
        cond_mask: Int[torch.Tensor, "batch elements channels"],
        timestep: Float[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Predict the vector field for LayoutFlow training."""
        return self.model(sample=xt, timestep=timestep, cond_mask=cond_mask).sample

    def training_step(
        self, batch: dict[str, Shaped[torch.Tensor, "..."]], batch_idx: int
    ) -> Float[torch.Tensor, ""]:
        """Run one LayoutFlow training step."""
        del batch_idx
        prepared = self._prepare_batch(batch)
        cond_mask = self.random4_condition_mask(
            prepared["length"], prepared["bbox"].shape[1]
        )
        x0, x1 = self.get_start_end(prepared)
        t = self.sample_t(x0)
        xt, ut = self.sample_xt(prepared, x0, x1, cond_mask, t)
        vt = self(xt, cond_mask, t.squeeze(-1))
        losses = layout_flow_losses(
            cond_mask.to(vt.dtype),
            ut,
            vt,
            geom_dim=self.geom_dim,
            geom_l1_weight=self.geom_l1_weight,
        )
        for key, value in losses.items():
            self.log(
                key, value, prog_bar=key == "train_loss", on_step=True, on_epoch=True
            )
        self.latest_step_trace = {
            "bbox": prepared["bbox"].detach(),
            "type": prepared["type"].detach(),
            "mask": prepared["mask"].detach(),
            "length": prepared["length"].detach(),
            "cond_mask": cond_mask.detach(),
            "x0": x0.detach(),
            "x1": x1.detach(),
            "t": t.detach(),
            "xt": xt.detach(),
            "ut": ut.detach(),
            "vt": vt.detach(),
            **{key: value.detach() for key, value in losses.items()},
        }
        return losses["train_loss"]

    def get_start_end(
        self, batch: dict[str, Shaped[torch.Tensor, "..."]]
    ) -> tuple[
        Float[torch.Tensor, "batch elements channels"],
        Float[torch.Tensor, "batch elements channels"],
    ]:
        """Return sampled ``x0`` and preprocessed data sample ``x1``."""
        bbox = batch["bbox"]
        labels = batch["type"].long()
        conv_type = self.processor.encode_labels(labels)
        gt = torch.cat([bbox, conv_type], dim=-1)
        x0 = torch.zeros_like(gt)
        if self.layout_flow_config.distribution != "gaussian":
            raise ValueError(
                "LayoutFlow training parity currently supports gaussian x0 sampling"
            )

        for i, length_tensor in enumerate(batch["length"]):
            length = int(length_tensor.item())
            x0[i, :length] = torch.randn(
                length, gt.shape[-1], device=gt.device, dtype=gt.dtype
            )
        x1 = self.processor.preprocess_state(gt)
        mask = batch["mask"].unsqueeze(-1)
        x1 = mask * x1 + (~mask) * gt
        return x0, x1

    def sample_t(
        self, x0: Float[torch.Tensor, "batch elements channels"]
    ) -> Float[torch.Tensor, "batch"]:
        """Sample uniform training times."""
        return torch.rand(x0.shape[0]).type_as(x0)

    def sample_xt(
        self,
        batch: dict[str, Shaped[torch.Tensor, "..."]],
        x0: Float[torch.Tensor, "batch elements channels"],
        x1: Float[torch.Tensor, "batch elements channels"],
        cond_mask: Int[torch.Tensor, "batch elements channels"],
        t: Float[torch.Tensor, "batch"],
    ) -> tuple[
        Float[torch.Tensor, "batch elements channels"],
        Float[torch.Tensor, "batch elements channels"],
    ]:
        """Return linear ``x_t`` and vector field ``u_t``."""
        del batch
        tpad = t.reshape(-1, *([1] * (x0.dim() - 1)))
        xt = (1 - tpad) * x0 + tpad * x1
        ut = x1 - x0
        cond = cond_mask.to(dtype=xt.dtype)
        xt = (1 - cond) * x1 + cond * xt
        return xt, ut

    def random4_condition_mask(
        self,
        lengths: Int[torch.Tensor, "batch"],
        seq_len: int,
    ) -> Int[torch.Tensor, "batch elements channels"]:
        """Return the ``random4`` condition mask."""
        batch = lengths.shape[0]
        device = lengths.device
        cond_mask = torch.ones(
            batch,
            seq_len,
            self.geom_dim + self.attr_dim,
            dtype=torch.int,
            device=device,
        )
        if self.condition_policy != "random4":
            raise ValueError(f"Unsupported condition_policy: {self.condition_policy}")

        div = batch // 4
        for i, length_tensor in enumerate(lengths[:div]):
            length = int(length_tensor.item())
            n = length * 0.2 * torch.rand(1).to(device)
            if length > 1:
                idx = torch.multinomial(
                    torch.arange(length).float(),
                    int(n.item() + 1),
                ).to(device)
                cond_mask[i, idx] = 0
        cond_mask[div : 2 * div, :, self.geom_dim :] = 0
        cond_mask[2 * div : 3 * div, :, 2:] = 0
        return cond_mask

    def _prepare_batch(
        self, batch: dict[str, Shaped[torch.Tensor, "..."]]
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        bbox = batch["bbox"].float()
        labels = batch.get("type", batch.get("labels"))
        if labels is None:
            raise ValueError("LayoutFlow training batch requires 'type' labels")

        mask = batch["mask"]
        if mask.ndim == 3:
            mask = mask.squeeze(-1)
        return {
            "bbox": bbox,
            "type": labels.long(),
            "mask": mask.bool(),
            "length": batch["length"].long(),
        }
__init__
__init__(
    *,
    config: LayoutFlowConfig,
    model: LayoutFlowTransformerModel | None = None,
    learning_rate: float = 0.0005,
    scheduler: LayoutFlowTrainingScheduler
    | None = "reduce_on_plateau",
    condition_policy: LayoutFlowConditionPolicy = "random4",
    geom_l1_weight: float = 0.2,
    seed_mode: LayoutFlowSeedMode
    | str = LayoutFlowSeedMode.default,
    fid_calc_every_n: int = 20,
) -> None

Initialize LayoutFlow training state.

Source code in models/layout-flow/src/layout_flow/training/lightning_module.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def __init__(
    self,
    *,
    config: LayoutFlowConfig,
    model: LayoutFlowTransformerModel | None = None,
    learning_rate: float = 0.0005,
    scheduler: LayoutFlowTrainingScheduler | None = "reduce_on_plateau",
    condition_policy: LayoutFlowConditionPolicy = "random4",
    geom_l1_weight: float = 0.2,
    seed_mode: LayoutFlowSeedMode | str = LayoutFlowSeedMode.default,
    fid_calc_every_n: int = 20,
) -> None:
    """Initialize LayoutFlow training state."""
    super().__init__()
    self.layout_flow_config = config
    self.model = model or LayoutFlowTransformerModel(
        num_labels=self.layout_flow_config.num_labels,
        latent_dim=self.layout_flow_config.latent_dim,
        tr_enc_only=self.layout_flow_config.tr_enc_only,
        d_model=self.layout_flow_config.d_model,
        nhead=self.layout_flow_config.nhead,
        dim_feedforward=self.layout_flow_config.dim_feedforward,
        num_layers=self.layout_flow_config.num_layers,
        dropout=self.layout_flow_config.dropout,
        use_pos_enc=self.layout_flow_config.use_pos_enc,
        attr_encoding=self.layout_flow_config.attr_encoding,
        seq_type=self.layout_flow_config.seq_type,
    )
    self.processor = LayoutFlowProcessor(self.layout_flow_config)
    self.learning_rate = learning_rate
    self.scheduler = scheduler
    self.condition_policy = condition_policy
    self.geom_l1_weight = geom_l1_weight
    self.seed_mode = LayoutFlowSeedMode(seed_mode)
    self.fid_calc_every_n = fid_calc_every_n
    self.geom_dim = 4
    self.attr_dim = self.layout_flow_config.attr_dim
    self.latest_step_trace: dict[str, Shaped[torch.Tensor, "..."]] = {}
configure_optimizers
configure_optimizers() -> OptimizerLRScheduler

Return AdamW and optional ReduceLROnPlateau.

Source code in models/layout-flow/src/layout_flow/training/lightning_module.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def configure_optimizers(self) -> OptimizerLRScheduler:
    """Return AdamW and optional ReduceLROnPlateau."""
    optimizer = torch.optim.AdamW(
        self.model.parameters(), lr=self.learning_rate, betas=(0.9, 0.98)
    )
    if self.scheduler == "reduce_on_plateau":
        scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer)
        return {
            "optimizer": optimizer,
            "lr_scheduler": {
                "scheduler": scheduler,
                "monitor": "FID_Layout",
                "frequency": self.fid_calc_every_n,
            },
        }
    return optimizer
forward
forward(
    xt: Float[Tensor, "batch elements channels"],
    cond_mask: Int[Tensor, "batch elements channels"],
    timestep: Float[Tensor, "batch"],
) -> Float[torch.Tensor, "batch elements channels"]

Predict the vector field for LayoutFlow training.

Source code in models/layout-flow/src/layout_flow/training/lightning_module.py
81
82
83
84
85
86
87
88
def forward(
    self,
    xt: Float[torch.Tensor, "batch elements channels"],
    cond_mask: Int[torch.Tensor, "batch elements channels"],
    timestep: Float[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch elements channels"]:
    """Predict the vector field for LayoutFlow training."""
    return self.model(sample=xt, timestep=timestep, cond_mask=cond_mask).sample
training_step
training_step(
    batch: dict[str, Shaped[Tensor, "..."]], batch_idx: int
) -> Float[torch.Tensor, ""]

Run one LayoutFlow training step.

Source code in models/layout-flow/src/layout_flow/training/lightning_module.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
def training_step(
    self, batch: dict[str, Shaped[torch.Tensor, "..."]], batch_idx: int
) -> Float[torch.Tensor, ""]:
    """Run one LayoutFlow training step."""
    del batch_idx
    prepared = self._prepare_batch(batch)
    cond_mask = self.random4_condition_mask(
        prepared["length"], prepared["bbox"].shape[1]
    )
    x0, x1 = self.get_start_end(prepared)
    t = self.sample_t(x0)
    xt, ut = self.sample_xt(prepared, x0, x1, cond_mask, t)
    vt = self(xt, cond_mask, t.squeeze(-1))
    losses = layout_flow_losses(
        cond_mask.to(vt.dtype),
        ut,
        vt,
        geom_dim=self.geom_dim,
        geom_l1_weight=self.geom_l1_weight,
    )
    for key, value in losses.items():
        self.log(
            key, value, prog_bar=key == "train_loss", on_step=True, on_epoch=True
        )
    self.latest_step_trace = {
        "bbox": prepared["bbox"].detach(),
        "type": prepared["type"].detach(),
        "mask": prepared["mask"].detach(),
        "length": prepared["length"].detach(),
        "cond_mask": cond_mask.detach(),
        "x0": x0.detach(),
        "x1": x1.detach(),
        "t": t.detach(),
        "xt": xt.detach(),
        "ut": ut.detach(),
        "vt": vt.detach(),
        **{key: value.detach() for key, value in losses.items()},
    }
    return losses["train_loss"]
get_start_end
get_start_end(
    batch: dict[str, Shaped[Tensor, "..."]],
) -> tuple[
    Float[torch.Tensor, "batch elements channels"],
    Float[torch.Tensor, "batch elements channels"],
]

Return sampled x0 and preprocessed data sample x1.

Source code in models/layout-flow/src/layout_flow/training/lightning_module.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def get_start_end(
    self, batch: dict[str, Shaped[torch.Tensor, "..."]]
) -> tuple[
    Float[torch.Tensor, "batch elements channels"],
    Float[torch.Tensor, "batch elements channels"],
]:
    """Return sampled ``x0`` and preprocessed data sample ``x1``."""
    bbox = batch["bbox"]
    labels = batch["type"].long()
    conv_type = self.processor.encode_labels(labels)
    gt = torch.cat([bbox, conv_type], dim=-1)
    x0 = torch.zeros_like(gt)
    if self.layout_flow_config.distribution != "gaussian":
        raise ValueError(
            "LayoutFlow training parity currently supports gaussian x0 sampling"
        )

    for i, length_tensor in enumerate(batch["length"]):
        length = int(length_tensor.item())
        x0[i, :length] = torch.randn(
            length, gt.shape[-1], device=gt.device, dtype=gt.dtype
        )
    x1 = self.processor.preprocess_state(gt)
    mask = batch["mask"].unsqueeze(-1)
    x1 = mask * x1 + (~mask) * gt
    return x0, x1
sample_t
sample_t(
    x0: Float[Tensor, "batch elements channels"],
) -> Float[torch.Tensor, "batch"]

Sample uniform training times.

Source code in models/layout-flow/src/layout_flow/training/lightning_module.py
157
158
159
160
161
def sample_t(
    self, x0: Float[torch.Tensor, "batch elements channels"]
) -> Float[torch.Tensor, "batch"]:
    """Sample uniform training times."""
    return torch.rand(x0.shape[0]).type_as(x0)
sample_xt
sample_xt(
    batch: dict[str, Shaped[Tensor, "..."]],
    x0: Float[Tensor, "batch elements channels"],
    x1: Float[Tensor, "batch elements channels"],
    cond_mask: Int[Tensor, "batch elements channels"],
    t: Float[Tensor, "batch"],
) -> tuple[
    Float[torch.Tensor, "batch elements channels"],
    Float[torch.Tensor, "batch elements channels"],
]

Return linear x_t and vector field u_t.

Source code in models/layout-flow/src/layout_flow/training/lightning_module.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def sample_xt(
    self,
    batch: dict[str, Shaped[torch.Tensor, "..."]],
    x0: Float[torch.Tensor, "batch elements channels"],
    x1: Float[torch.Tensor, "batch elements channels"],
    cond_mask: Int[torch.Tensor, "batch elements channels"],
    t: Float[torch.Tensor, "batch"],
) -> tuple[
    Float[torch.Tensor, "batch elements channels"],
    Float[torch.Tensor, "batch elements channels"],
]:
    """Return linear ``x_t`` and vector field ``u_t``."""
    del batch
    tpad = t.reshape(-1, *([1] * (x0.dim() - 1)))
    xt = (1 - tpad) * x0 + tpad * x1
    ut = x1 - x0
    cond = cond_mask.to(dtype=xt.dtype)
    xt = (1 - cond) * x1 + cond * xt
    return xt, ut
random4_condition_mask
random4_condition_mask(
    lengths: Int[Tensor, "batch"], seq_len: int
) -> Int[torch.Tensor, "batch elements channels"]

Return the random4 condition mask.

Source code in models/layout-flow/src/layout_flow/training/lightning_module.py
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
def random4_condition_mask(
    self,
    lengths: Int[torch.Tensor, "batch"],
    seq_len: int,
) -> Int[torch.Tensor, "batch elements channels"]:
    """Return the ``random4`` condition mask."""
    batch = lengths.shape[0]
    device = lengths.device
    cond_mask = torch.ones(
        batch,
        seq_len,
        self.geom_dim + self.attr_dim,
        dtype=torch.int,
        device=device,
    )
    if self.condition_policy != "random4":
        raise ValueError(f"Unsupported condition_policy: {self.condition_policy}")

    div = batch // 4
    for i, length_tensor in enumerate(lengths[:div]):
        length = int(length_tensor.item())
        n = length * 0.2 * torch.rand(1).to(device)
        if length > 1:
            idx = torch.multinomial(
                torch.arange(length).float(),
                int(n.item() + 1),
            ).to(device)
            cond_mask[i, idx] = 0
    cond_mask[div : 2 * div, :, self.geom_dim :] = 0
    cond_mask[2 * div : 3 * div, :, 2:] = 0
    return cond_mask

losses

Loss functions for LayoutFlow training parity.

layout_flow_losses

layout_flow_losses(
    cond_mask: Float[Tensor, "batch elements channels"],
    ut: Float[Tensor, "batch elements channels"],
    vt: Float[Tensor, "batch elements channels"],
    *,
    geom_dim: int = 4,
    geom_l1_weight: float = 0.2,
) -> dict[str, Float[torch.Tensor, ""]]

Compute the LayoutFlow training losses.

Parameters:

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

Condition mask where 1 marks generated fields.

required
ut Float[Tensor, 'batch elements channels']

Target conditional vector field.

required
vt Float[Tensor, 'batch elements channels']

Predicted vector field.

required
geom_dim int

Number of geometry channels.

4
geom_l1_weight float

Weight applied to the geometry L1 auxiliary loss.

0.2

Returns:

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

Dictionary with flow_loss, geom_l1_loss, and train_loss.

Raises:

Type Description
RuntimeError

If tensor shapes are incompatible.

Examples:

>>> x = torch.ones(1, 2, 3)
>>> out = layout_flow_losses(x, x, x, geom_dim=2)
>>> out["train_loss"].item()
0.0
Source code in models/layout-flow/src/layout_flow/training/losses.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def layout_flow_losses(
    cond_mask: Float[torch.Tensor, "batch elements channels"],
    ut: Float[torch.Tensor, "batch elements channels"],
    vt: Float[torch.Tensor, "batch elements channels"],
    *,
    geom_dim: int = 4,
    geom_l1_weight: float = 0.2,
) -> dict[str, Float[torch.Tensor, ""]]:
    """Compute the LayoutFlow training losses.

    Args:
        cond_mask: Condition mask where ``1`` marks generated fields.
        ut: Target conditional vector field.
        vt: Predicted vector field.
        geom_dim: Number of geometry channels.
        geom_l1_weight: Weight applied to the geometry L1 auxiliary loss.

    Returns:
        Dictionary with ``flow_loss``, ``geom_l1_loss``, and ``train_loss``.

    Raises:
        RuntimeError: If tensor shapes are incompatible.

    Examples:
        >>> x = torch.ones(1, 2, 3)
        >>> out = layout_flow_losses(x, x, x, geom_dim=2)
        >>> out["train_loss"].item()
        0.0
    """
    flow_loss = torch.nn.functional.mse_loss(cond_mask * vt, cond_mask * ut)
    geom_l1_loss = torch.nn.functional.l1_loss(
        cond_mask[..., :geom_dim] * vt[..., :geom_dim],
        cond_mask[..., :geom_dim] * ut[..., :geom_dim],
    )
    train_loss = flow_loss + geom_l1_weight * geom_l1_loss
    return {
        "flow_loss": flow_loss,
        "geom_l1_loss": geom_l1_loss,
        "train_loss": train_loss,
    }

parity

LayoutFlow-specific S0-S2 parity helpers.

trace_layout_flow_step

trace_layout_flow_step(
    module: TrainingStepModule,
    batch: dict[str, Shaped[Tensor, "..."]],
    rng_state: RNGState | None = None,
) -> StepTrace

Trace one LayoutFlow training step with the canonical trace points.

Source code in models/layout-flow/src/layout_flow/training/parity.py
36
37
38
39
40
41
42
def trace_layout_flow_step(
    module: TrainingStepModule,
    batch: dict[str, Shaped[torch.Tensor, "..."]],
    rng_state: RNGState | None = None,
) -> StepTrace:
    """Trace one LayoutFlow training step with the canonical trace points."""
    return trace_training_step(module, batch, rng_state, TRACE_POINTS)

compare_layout_flow_step

compare_layout_flow_step(
    reference: StepTrace,
    target: StepTrace,
    *,
    tolerance: TensorTolerance | None = None,
) -> StepReport

Compare S1 LayoutFlow pre-optimizer traces.

Source code in models/layout-flow/src/layout_flow/training/parity.py
45
46
47
48
49
50
51
52
53
def compare_layout_flow_step(
    reference: StepTrace,
    target: StepTrace,
    *,
    tolerance: TensorTolerance | None = None,
) -> StepReport:
    """Compare S1 LayoutFlow pre-optimizer traces."""
    tolerances = {name: tolerance or TensorTolerance() for name in TRACE_POINTS}
    return compare_step_trace(reference, target, tolerances)

compare_layout_flow_optimizer_step

compare_layout_flow_optimizer_step(
    reference_state: dict[str, Shaped[Tensor, "..."]],
    target_state: dict[str, Shaped[Tensor, "..."]],
    *,
    tolerance: TensorTolerance | None = None,
) -> OptimizerStepReport

Compare S2 LayoutFlow post-optimizer parameters.

Source code in models/layout-flow/src/layout_flow/training/parity.py
56
57
58
59
60
61
62
63
64
def compare_layout_flow_optimizer_step(
    reference_state: dict[str, Shaped[torch.Tensor, "..."]],
    target_state: dict[str, Shaped[torch.Tensor, "..."]],
    *,
    tolerance: TensorTolerance | None = None,
) -> OptimizerStepReport:
    """Compare S2 LayoutFlow post-optimizer parameters."""
    tolerances = {name: tolerance or TensorTolerance() for name in reference_state}
    return compare_optimizer_step(reference_state, target_state, tolerances)

seed

Seed policy helpers for LayoutFlow training.

apply_layout_flow_seed_mode

apply_layout_flow_seed_mode(
    seed_mode: LayoutFlowSeedMode | str,
    *,
    seed: int = 42975,
) -> None

Apply the selected LayoutFlow seed mode.

Parameters:

Name Type Description Default
seed_mode LayoutFlowSeedMode | str

Regular or deterministic seed mode.

required
seed int

Seed used by both modes.

42975

Returns:

Type Description
None

None.

Raises:

Type Description
ValueError

If the seed mode is unsupported.

Examples:

>>> apply_layout_flow_seed_mode("default", seed=1)
Source code in models/layout-flow/src/layout_flow/training/seed.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
def apply_layout_flow_seed_mode(
    seed_mode: LayoutFlowSeedMode | str,
    *,
    seed: int = 42975,
) -> None:
    """Apply the selected LayoutFlow seed mode.

    Args:
        seed_mode: Regular or deterministic seed mode.
        seed: Seed used by both modes.

    Returns:
        None.

    Raises:
        ValueError: If the seed mode is unsupported.

    Examples:
        >>> apply_layout_flow_seed_mode("default", seed=1)
    """
    mode = LayoutFlowSeedMode(seed_mode)
    if mode is LayoutFlowSeedMode.default:
        if torch.cuda.is_available():
            torch.cuda.manual_seed_all(seed)
        torch.set_float32_matmul_precision("medium")
    elif mode is LayoutFlowSeedMode.deterministic:
        apply_determinism(DeterminismConfig(seed=seed))