Skip to content

Model Card for RADM

arXiv venue license base dataset vendor-parity hub

This package ports RADM, the CIKM 2023 relation-aware diffusion method for controllable poster layout generation, into a 🧨diffusers-style package.

Model Details

Model Description

RADM is a proposal-box diffusion pipeline for content-aware poster layout generation. The public pipeline accepts a content image plus optional RADM text-feature tensors, runs reverse diffusion over normalized proposal boxes, and returns normalized center xywh boxes, dataset-local labels, a valid-element mask, id2label, scores, and optional denoising trajectory metadata.

  • Developed by: RADM authors.
  • Shared by: creative-graphic-design.
  • Model type: content-aware; task: single-task; conditioning: none.
  • Language(s) (NLP): not applicable.
  • License: unconfirmed for the checked original source and any user-supplied or converted weights.

Model Sources

  • Repository: RADM repository
  • Paper: arXiv 2306.09086
  • Original checkpoint host: the checked RADM README publishes dataset and testing-feature assets only; no released checkpoint is published.

Supported Checkpoints

Checkpoint Hub ID Status
CGL creative-graphic-design/radm-cgl not-published
CGL-v2 creative-graphic-design/radm-cgl-v2 not-published

Uses

Direct Use

Use this package for local RADM conversion experiments, CPU smoke tests, and gated original-code parity preparation after a user-supplied local checkpoint and license terms have been confirmed.

The first public condition is content_image. images or content["image"] is required; text_features and text_mask are auxiliary payload fields under the same content-image condition.

Downstream Use

Generated poster layouts may feed research evaluation, poster composition studies, or downstream rendering systems after dataset, license, and quality review.

Out-of-Scope Use

Do not use synthetic RADM smoke artifacts as release checkpoints, claim original-code agreement without generated references, or redistribute converted weights before local checkpoint provenance and license status are resolved.

Bias, Risks, and Limitations

The checked RADM README publishes dataset and testing-feature assets only; no public RADM checkpoint is published. The checked source has no license file, so weight redistribution is blocked until maintainers verify the source license, checkpoint provenance, and dataset terms.

Recommendations

Use explicit local paths for user-supplied checkpoints, datasets, text features, and vendor source. Run the gated parity workflow before comparing generated layouts against the original method.

How to Get Started with the Model

Install the package directly from this repository. The command includes shared packages when they are not published on PyPI.

pip install \
  "laygen @ git+https://github.com/creative-graphic-design/design-generators.git#subdirectory=lib/laygen" \
  "posgen @ git+https://github.com/creative-graphic-design/design-generators.git#subdirectory=lib/posgen" \
  "radm @ git+https://github.com/creative-graphic-design/design-generators.git#subdirectory=models/radm"

Clone this repository, install the workspace member, and run the conversion steps in REPRODUCING.md. Those steps create .cache/radm/converted/cgl.

git clone https://github.com/creative-graphic-design/design-generators.git
cd design-generators
uv sync --package radm
uv run --package radm python
from PIL import Image
from radm import RADMPipeline

path = ".cache/radm/converted/cgl"
# After Hub publication: from_pretrained("creative-graphic-design/radm-cgl")
pipe = RADMPipeline.from_pretrained(path)
out = pipe(Image.new("RGB", (512, 768)), seed=0)

print(out.bbox)
print(out.labels)
print(out.mask)
torch.Size([1, 100, 4])
torch.Size([1, 100])
torch.Size([1, 100])

Training Details

Training Data

The package-local adapter accepts explicit CGL and CGL-v2 COCO-style annotation, image, and precomputed 768-dimensional text-feature paths. It does not download data. Data provenance and the CGL-v2 license remain unresolved; see TRAINING.md for the current gate status.

Training Procedure

The CGL LightningCLI recipe predicts four classes while preserving the five-entry source vocabulary in its captured class mapping. It initializes ResNet-50 from Detectron2’s torchvision ImageNet checkpoint, which the Model Zoo lists under CC BY-SA 3.0. The three-seed, 1,500-step production runs verify the configured training path only. The 250,000-step CGL S5 retrain has not been rerun, so no trained-checkpoint reproduction is claimed; see TRAINING.md.

Evaluation

Testing Data, Factors & Metrics

Testing Data

Vendor parity requires local original-code outputs generated from a user-supplied checkpoint, CGL dataset root, text-feature root, and one selected GPU. Large generated tensors, images, weights, and downloaded artifacts are not committed.

Factors

Parity is disaggregated by checkpoint hash, config hash, seed, timestep sequence, proposal noise, denoiser logits, denoised boxes, post-threshold boxes, labels, scores, and NMS indices.

Metrics

Metrics are exact scheduler/timestep agreement, exact tensor equality where the converted and original paths share operations, and explicit tolerance only after dtype, TF32, operation-order, and Detectron2 versus torchvision differences are diagnosed.

Parity Results

Dataset Compared path Cases Assertion
CGL original Detectron2 RADM path vs. converted 🧨diffusers path 0 not run; vendor parity is not verified, and the recorded evaluation-path artifact is diagnosis evidence, not an accepted parity result
Synthetic smoke randomly initialized RADM pipeline save/load 1 schema smoke only, no original-code parity claim

No accepted original-code inference parity number is available yet. The package includes a gated parity harness that fails under PARITY_REQUIRE=1 when required local assets are absent.

Reproducibility

Reproduce original-implementation agreement by supplying an approved local checkpoint and data assets, capturing the original Detectron2 reference state, running the gated parity tests, converting the checkpoint, and smoke-testing from_pretrained as described in REPRODUCING.md. Training-stage status and the future member-scoped training commands are in TRAINING.md.

Environmental Impact

No model weights are included. Conversion, reference generation, and parity costs depend on the selected checkpoint and local hardware.

Technical Specifications

Model Architecture and Objective

RADM samples proposal boxes and denoises them with relation-aware image and text conditioning. The package-level runtime keeps the image/text feature processor, proposal scheduler, denoiser, and postprocessing separate so converted artifacts can load through 🧨diffusers component folders.

Compute Infrastructure

Vendor parity commands are intended for one explicitly selected GPU when the original Detectron2 path is available.

Hardware

CPU is sufficient for import, tiny random-weight smoke tests, and config round trips. CUDA is required for heavyweight original-code parity.

Software

Use uv run --package radm ... from the repository root so workspace dependency sources and extras resolve correctly.

License

Repository wrapper code is Apache-2.0. The checked original RADM source has no license file, and weight redistribution is blocked until license and checkpoint provenance are confirmed.

Citation

@inproceedings{zhang2023radm,
  title={Relation-Aware Diffusion Model for Controllable Poster Layout Generation},
  author={Zhang, Yiheng and others},
  booktitle={Proceedings of the 32nd ACM International Conference on Information and Knowledge Management},
  year={2023}
}

API Reference

RADM Diffusers package for content-aware poster layout generation.

RADMConfig

Bases: ConfigMixin

Configuration saved with RADM pipeline artifacts.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Poster dataset variant.

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

Public label mapping. Defaults to the shared CGL labels.

None
original_id2label Mapping[int | str, str] | None

Label spellings from the checked RADM source.

None
num_proposals int

Number of proposal boxes sampled during inference.

100
num_classes int

Number of semantic classes predicted by the denoiser.

5
hidden_dim int

Proposal and condition hidden dimension.

256
text_feature_dim int

Last dimension of RADM text-feature tensors.

768
max_text_num int

Maximum text-feature rows per example.

20
num_heads int

Number of repeated proposal refinement blocks.

6
num_attention_heads int

Proposal self-attention head count.

8
dim_feedforward int

Proposal feed-forward hidden dimension.

2048
num_dynamic int

Number of dynamic-convolution projections.

2
dim_dynamic int

Intermediate dynamic-convolution dimension.

64
num_cls int

Number of repeated classification projection layers.

1
num_reg int

Number of repeated box-regression projection layers.

3
roi_resolution int

ROIAlign output resolution.

7
roi_sampling_ratio int

ROIAlign sampling ratio.

2
with_vtram bool

Whether visual-textual relation features are enabled.

True
with_gram bool

Whether geometric relation features are enabled.

True
deep_supervision bool

Whether intermediate proposal heads are returned.

True
backbone_depth int

ResNet backbone depth.

50
backbone_freeze_at int

Number of initial ResNet stages to freeze.

2
image_size int

Short-side image preprocessing target.

800
size_divisibility int

Padding multiple for image tensors.

32
pixel_mean Sequence[float]

RGB mean in 0-255 space.

(123.675, 116.28, 103.53)
pixel_std Sequence[float]

RGB standard deviation in 0-255 space.

(58.395, 57.12, 57.375)
inference_steps int

Default number of reverse-diffusion steps.

50
num_train_timesteps int

Number of training diffusion timesteps.

1000
snr_scale float

RADM signal-to-noise scaling metadata.

2.0
sample_step int

Original sampling stride metadata.

1
class_threshold float

Default confidence threshold.

0.3
nms_threshold float

Default class-wise NMS threshold.

0.5
denoiser_subfolder str

Pipeline denoiser component subfolder.

'denoiser'
scheduler_subfolder str

Pipeline scheduler component subfolder.

'scheduler'
processor_subfolder str

Pipeline processor component subfolder.

'processor'
conversion_report Mapping[str, YamlValue] | None

Conversion metadata persisted with local artifacts.

None
checkpoint_status str

Human-readable checkpoint status.

'RADM README confirms datasets only; no released checkpoint is published'
license_status str

Human-readable source/weight license status.

'checked original source has no license file'

Examples:

>>> config = RADMConfig(num_proposals=2, hidden_dim=8)
>>> config.num_labels
5
Source code in models/radm/src/radm/configuration_radm.py
 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
class RADMConfig(ConfigMixin):
    """Configuration saved with RADM pipeline artifacts.

    Args:
        dataset_name: Poster dataset variant.
        id2label: Public label mapping. Defaults to the shared CGL labels.
        original_id2label: Label spellings from the checked RADM source.
        num_proposals: Number of proposal boxes sampled during inference.
        num_classes: Number of semantic classes predicted by the denoiser.
        hidden_dim: Proposal and condition hidden dimension.
        text_feature_dim: Last dimension of RADM text-feature tensors.
        max_text_num: Maximum text-feature rows per example.
        num_heads: Number of repeated proposal refinement blocks.
        num_attention_heads: Proposal self-attention head count.
        dim_feedforward: Proposal feed-forward hidden dimension.
        num_dynamic: Number of dynamic-convolution projections.
        dim_dynamic: Intermediate dynamic-convolution dimension.
        num_cls: Number of repeated classification projection layers.
        num_reg: Number of repeated box-regression projection layers.
        roi_resolution: ROIAlign output resolution.
        roi_sampling_ratio: ROIAlign sampling ratio.
        with_vtram: Whether visual-textual relation features are enabled.
        with_gram: Whether geometric relation features are enabled.
        deep_supervision: Whether intermediate proposal heads are returned.
        backbone_depth: ResNet backbone depth.
        backbone_freeze_at: Number of initial ResNet stages to freeze.
        image_size: Short-side image preprocessing target.
        size_divisibility: Padding multiple for image tensors.
        pixel_mean: RGB mean in 0-255 space.
        pixel_std: RGB standard deviation in 0-255 space.
        inference_steps: Default number of reverse-diffusion steps.
        num_train_timesteps: Number of training diffusion timesteps.
        snr_scale: RADM signal-to-noise scaling metadata.
        sample_step: Original sampling stride metadata.
        class_threshold: Default confidence threshold.
        nms_threshold: Default class-wise NMS threshold.
        denoiser_subfolder: Pipeline denoiser component subfolder.
        scheduler_subfolder: Pipeline scheduler component subfolder.
        processor_subfolder: Pipeline processor component subfolder.
        conversion_report: Conversion metadata persisted with local artifacts.
        checkpoint_status: Human-readable checkpoint status.
        license_status: Human-readable source/weight license status.

    Examples:
        >>> config = RADMConfig(num_proposals=2, hidden_dim=8)
        >>> config.num_labels
        5
    """

    config_name: str = "radm_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: DatasetName | str = DatasetName.cgl,
        id2label: Mapping[int | str, str] | None = None,
        original_id2label: Mapping[int | str, str] | None = None,
        num_proposals: int = 100,
        num_classes: int = 5,
        hidden_dim: int = 256,
        text_feature_dim: int = 768,
        max_text_num: int = 20,
        num_heads: int = 6,
        num_attention_heads: int = 8,
        dim_feedforward: int = 2048,
        num_dynamic: int = 2,
        dim_dynamic: int = 64,
        num_cls: int = 1,
        num_reg: int = 3,
        roi_resolution: int = 7,
        roi_sampling_ratio: int = 2,
        with_vtram: bool = True,
        with_gram: bool = True,
        deep_supervision: bool = True,
        backbone_depth: int = 50,
        backbone_freeze_at: int = 2,
        image_size: int = 800,
        size_divisibility: int = 32,
        pixel_mean: Sequence[float] = (123.675, 116.280, 103.530),
        pixel_std: Sequence[float] = (58.395, 57.120, 57.375),
        inference_steps: int = 50,
        num_train_timesteps: int = 1000,
        snr_scale: float = 2.0,
        sample_step: int = 1,
        class_threshold: float = 0.30,
        nms_threshold: float = 0.50,
        denoiser_subfolder: str = "denoiser",
        scheduler_subfolder: str = "scheduler",
        processor_subfolder: str = "processor",
        conversion_report: Mapping[str, YamlValue] | None = None,
        checkpoint_status: str = "RADM README confirms datasets only; no released checkpoint is published",
        license_status: str = "checked original source has no license file",
    ) -> None:
        """Initialize RADM settings."""
        dataset = normalize_dataset_name(dataset_name)
        if dataset not in {DatasetName.cgl, DatasetName.cgl_v2}:
            raise ValueError(f"Unsupported RADM dataset_name: {dataset_name}")

        raw_id2label = id2label or default_id2label(dataset)
        raw_original = original_id2label or default_id2label(
            dataset, label_mode=RADMLabelMode.original
        )
        self.dataset_name = str(dataset)
        self.id2label = {int(key): value for key, value in raw_id2label.items()}
        self.original_id2label = {
            int(key): value for key, value in raw_original.items()
        }
        self.num_proposals = int(num_proposals)
        self.num_classes = int(num_classes)
        self.hidden_dim = int(hidden_dim)
        self.text_feature_dim = int(text_feature_dim)
        self.max_text_num = int(max_text_num)
        self.num_heads = int(num_heads)
        self.num_attention_heads = int(num_attention_heads)
        self.dim_feedforward = int(dim_feedforward)
        self.num_dynamic = int(num_dynamic)
        self.dim_dynamic = int(dim_dynamic)
        self.num_cls = int(num_cls)
        self.num_reg = int(num_reg)
        self.roi_resolution = int(roi_resolution)
        self.roi_sampling_ratio = int(roi_sampling_ratio)
        self.with_vtram = bool(with_vtram)

        self.with_gram = bool(with_gram)
        self.deep_supervision = bool(deep_supervision)
        self.backbone_depth = int(backbone_depth)
        self.backbone_freeze_at = int(backbone_freeze_at)
        self.image_size = int(image_size)
        self.size_divisibility = int(size_divisibility)
        self.pixel_mean = tuple(float(value) for value in pixel_mean)
        self.pixel_std = tuple(float(value) for value in pixel_std)
        self.inference_steps = int(inference_steps)
        self.num_train_timesteps = int(num_train_timesteps)
        self.snr_scale = float(snr_scale)
        self.sample_step = int(sample_step)
        self.class_threshold = float(class_threshold)
        self.nms_threshold = float(nms_threshold)
        self.denoiser_subfolder = denoiser_subfolder

        self.scheduler_subfolder = scheduler_subfolder
        self.processor_subfolder = processor_subfolder
        self.conversion_report = dict(conversion_report or {})
        self.checkpoint_status = checkpoint_status
        self.license_status = license_status

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

    @property
    def num_labels(self) -> int:
        """Return the configured public class count."""
        return len(self.id2label)

label2id property

label2id: dict[str, int]

Return the public label-name to integer-id mapping.

num_labels property

num_labels: int

Return the configured public class count.

__init__

__init__(
    *,
    dataset_name: DatasetName | str = DatasetName.cgl,
    id2label: Mapping[int | str, str] | None = None,
    original_id2label: Mapping[int | str, str]
    | None = None,
    num_proposals: int = 100,
    num_classes: int = 5,
    hidden_dim: int = 256,
    text_feature_dim: int = 768,
    max_text_num: int = 20,
    num_heads: int = 6,
    num_attention_heads: int = 8,
    dim_feedforward: int = 2048,
    num_dynamic: int = 2,
    dim_dynamic: int = 64,
    num_cls: int = 1,
    num_reg: int = 3,
    roi_resolution: int = 7,
    roi_sampling_ratio: int = 2,
    with_vtram: bool = True,
    with_gram: bool = True,
    deep_supervision: bool = True,
    backbone_depth: int = 50,
    backbone_freeze_at: int = 2,
    image_size: int = 800,
    size_divisibility: int = 32,
    pixel_mean: Sequence[float] = (123.675, 116.28, 103.53),
    pixel_std: Sequence[float] = (58.395, 57.12, 57.375),
    inference_steps: int = 50,
    num_train_timesteps: int = 1000,
    snr_scale: float = 2.0,
    sample_step: int = 1,
    class_threshold: float = 0.3,
    nms_threshold: float = 0.5,
    denoiser_subfolder: str = "denoiser",
    scheduler_subfolder: str = "scheduler",
    processor_subfolder: str = "processor",
    conversion_report: Mapping[str, YamlValue]
    | None = None,
    checkpoint_status: str = "RADM README confirms datasets only; no released checkpoint is published",
    license_status: str = "checked original source has no license file",
) -> None

Initialize RADM settings.

Source code in models/radm/src/radm/configuration_radm.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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: DatasetName | str = DatasetName.cgl,
    id2label: Mapping[int | str, str] | None = None,
    original_id2label: Mapping[int | str, str] | None = None,
    num_proposals: int = 100,
    num_classes: int = 5,
    hidden_dim: int = 256,
    text_feature_dim: int = 768,
    max_text_num: int = 20,
    num_heads: int = 6,
    num_attention_heads: int = 8,
    dim_feedforward: int = 2048,
    num_dynamic: int = 2,
    dim_dynamic: int = 64,
    num_cls: int = 1,
    num_reg: int = 3,
    roi_resolution: int = 7,
    roi_sampling_ratio: int = 2,
    with_vtram: bool = True,
    with_gram: bool = True,
    deep_supervision: bool = True,
    backbone_depth: int = 50,
    backbone_freeze_at: int = 2,
    image_size: int = 800,
    size_divisibility: int = 32,
    pixel_mean: Sequence[float] = (123.675, 116.280, 103.530),
    pixel_std: Sequence[float] = (58.395, 57.120, 57.375),
    inference_steps: int = 50,
    num_train_timesteps: int = 1000,
    snr_scale: float = 2.0,
    sample_step: int = 1,
    class_threshold: float = 0.30,
    nms_threshold: float = 0.50,
    denoiser_subfolder: str = "denoiser",
    scheduler_subfolder: str = "scheduler",
    processor_subfolder: str = "processor",
    conversion_report: Mapping[str, YamlValue] | None = None,
    checkpoint_status: str = "RADM README confirms datasets only; no released checkpoint is published",
    license_status: str = "checked original source has no license file",
) -> None:
    """Initialize RADM settings."""
    dataset = normalize_dataset_name(dataset_name)
    if dataset not in {DatasetName.cgl, DatasetName.cgl_v2}:
        raise ValueError(f"Unsupported RADM dataset_name: {dataset_name}")

    raw_id2label = id2label or default_id2label(dataset)
    raw_original = original_id2label or default_id2label(
        dataset, label_mode=RADMLabelMode.original
    )
    self.dataset_name = str(dataset)
    self.id2label = {int(key): value for key, value in raw_id2label.items()}
    self.original_id2label = {
        int(key): value for key, value in raw_original.items()
    }
    self.num_proposals = int(num_proposals)
    self.num_classes = int(num_classes)
    self.hidden_dim = int(hidden_dim)
    self.text_feature_dim = int(text_feature_dim)
    self.max_text_num = int(max_text_num)
    self.num_heads = int(num_heads)
    self.num_attention_heads = int(num_attention_heads)
    self.dim_feedforward = int(dim_feedforward)
    self.num_dynamic = int(num_dynamic)
    self.dim_dynamic = int(dim_dynamic)
    self.num_cls = int(num_cls)
    self.num_reg = int(num_reg)
    self.roi_resolution = int(roi_resolution)
    self.roi_sampling_ratio = int(roi_sampling_ratio)
    self.with_vtram = bool(with_vtram)

    self.with_gram = bool(with_gram)
    self.deep_supervision = bool(deep_supervision)
    self.backbone_depth = int(backbone_depth)
    self.backbone_freeze_at = int(backbone_freeze_at)
    self.image_size = int(image_size)
    self.size_divisibility = int(size_divisibility)
    self.pixel_mean = tuple(float(value) for value in pixel_mean)
    self.pixel_std = tuple(float(value) for value in pixel_std)
    self.inference_steps = int(inference_steps)
    self.num_train_timesteps = int(num_train_timesteps)
    self.snr_scale = float(snr_scale)
    self.sample_step = int(sample_step)
    self.class_threshold = float(class_threshold)
    self.nms_threshold = float(nms_threshold)
    self.denoiser_subfolder = denoiser_subfolder

    self.scheduler_subfolder = scheduler_subfolder
    self.processor_subfolder = processor_subfolder
    self.conversion_report = dict(conversion_report or {})
    self.checkpoint_status = checkpoint_status
    self.license_status = license_status

RADMLabelMode

Bases: StrEnum

Closed set of public RADM label vocabularies.

Source code in models/radm/src/radm/configuration_radm.py
19
20
21
22
23
class RADMLabelMode(StrEnum):
    """Closed set of public RADM label vocabularies."""

    english = auto()
    original = auto()

RADMImageProcessor

Bases: BaseImageProcessor

Prepare RGB images for RADM inference.

Parameters:

Name Type Description Default
image_size int

Short-side resize target.

800
size_divisibility int

Padding multiple used by FPN-style backbones.

32
pixel_mean Sequence[float]

RGB mean in 0-255 space.

(123.675, 116.28, 103.53)
pixel_std Sequence[float]

RGB standard deviation in 0-255 space.

(58.395, 57.12, 57.375)

Examples:

>>> processor = RADMImageProcessor(image_size=32)
>>> batch = processor.preprocess(Image.new("RGB", (16, 24)))
>>> tuple(batch["pixel_values"].shape[:2])
(1, 3)
Source code in models/radm/src/radm/image_processing_radm.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
class RADMImageProcessor(BaseImageProcessor):
    """Prepare RGB images for RADM inference.

    Args:
        image_size: Short-side resize target.
        size_divisibility: Padding multiple used by FPN-style backbones.
        pixel_mean: RGB mean in 0-255 space.
        pixel_std: RGB standard deviation in 0-255 space.

    Examples:
        >>> processor = RADMImageProcessor(image_size=32)
        >>> batch = processor.preprocess(Image.new("RGB", (16, 24)))
        >>> tuple(batch["pixel_values"].shape[:2])
        (1, 3)
    """

    model_input_names = ["pixel_values"]

    def __init__(
        self,
        image_size: int = 800,
        size_divisibility: int = 32,
        pixel_mean: Sequence[float] = (123.675, 116.280, 103.530),
        pixel_std: Sequence[float] = (58.395, 57.120, 57.375),
        **kwargs: Unpack[ImagesKwargs],
    ) -> None:
        """Initialize image normalization settings."""
        super().__init__(**kwargs)
        self.image_size = int(image_size)
        self.size_divisibility = int(size_divisibility)
        self.pixel_mean = tuple(float(value) for value in pixel_mean)
        self.pixel_std = tuple(float(value) for value in pixel_std)

    @classmethod
    def from_config(cls, config: RADMConfig) -> "RADMImageProcessor":
        """Build an image processor from RADM configuration.

        Args:
            config: RADM pipeline configuration.

        Returns:
            Image processor with matching resize and normalization metadata.
        """
        return cls(
            image_size=config.image_size,
            size_divisibility=config.size_divisibility,
            pixel_mean=config.pixel_mean,
            pixel_std=config.pixel_std,
        )

    def preprocess(
        self,
        images: ImageInput | Sequence[ImageInput],
        **kwargs: Unpack[ImagesKwargs],
    ) -> BatchFeature:
        """Preprocess one image or an image batch.

        Args:
            images: PIL, numpy, or torch image input.
            return_tensors: Tensor framework. Only ``pt`` is supported.
            kwargs: Ignored compatibility kwargs.

        Returns:
            Batch feature with padded ``pixel_values`` and original sizes.

        Raises:
            ValueError: If ``return_tensors`` is not ``pt``.
        """
        return_tensors = kwargs.pop("return_tensors", "pt")
        if return_tensors != "pt":
            raise ValueError("RADMImageProcessor only supports return_tensors='pt'")

        tensors: list[Float[torch.Tensor, "channels height width"]] = []
        original_sizes: list[tuple[int, int]] = []
        resized_sizes: list[tuple[int, int]] = []
        for image in _ensure_pil_batch(images):
            width, height = image.size
            original_sizes.append((height, width))
            resized = _resize_short_side(image.convert("RGB"), self.image_size)
            resized_width, resized_height = resized.size
            resized_sizes.append((resized_height, resized_width))
            array = np.asarray(resized, dtype=np.float32)
            mean = np.asarray(self.pixel_mean, dtype=np.float32)
            std = np.asarray(self.pixel_std, dtype=np.float32)

            tensors.append(torch.from_numpy(((array - mean) / std).transpose(2, 0, 1)))

        padded, padding_mask = _pad_tensors(tensors, self.size_divisibility)
        return BatchFeature(
            {
                "pixel_values": padded.float(),
                "pixel_mask": padding_mask,
                "original_sizes": torch.tensor(original_sizes, dtype=torch.long),
                "resized_sizes": torch.tensor(resized_sizes, dtype=torch.long),
            }
        )

__init__

__init__(
    image_size: int = 800,
    size_divisibility: int = 32,
    pixel_mean: Sequence[float] = (123.675, 116.28, 103.53),
    pixel_std: Sequence[float] = (58.395, 57.12, 57.375),
    **kwargs: Unpack[ImagesKwargs],
) -> None

Initialize image normalization settings.

Source code in models/radm/src/radm/image_processing_radm.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def __init__(
    self,
    image_size: int = 800,
    size_divisibility: int = 32,
    pixel_mean: Sequence[float] = (123.675, 116.280, 103.530),
    pixel_std: Sequence[float] = (58.395, 57.120, 57.375),
    **kwargs: Unpack[ImagesKwargs],
) -> None:
    """Initialize image normalization settings."""
    super().__init__(**kwargs)
    self.image_size = int(image_size)
    self.size_divisibility = int(size_divisibility)
    self.pixel_mean = tuple(float(value) for value in pixel_mean)
    self.pixel_std = tuple(float(value) for value in pixel_std)

from_config classmethod

from_config(config: RADMConfig) -> 'RADMImageProcessor'

Build an image processor from RADM configuration.

Parameters:

Name Type Description Default
config RADMConfig

RADM pipeline configuration.

required

Returns:

Type Description
'RADMImageProcessor'

Image processor with matching resize and normalization metadata.

Source code in models/radm/src/radm/image_processing_radm.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@classmethod
def from_config(cls, config: RADMConfig) -> "RADMImageProcessor":
    """Build an image processor from RADM configuration.

    Args:
        config: RADM pipeline configuration.

    Returns:
        Image processor with matching resize and normalization metadata.
    """
    return cls(
        image_size=config.image_size,
        size_divisibility=config.size_divisibility,
        pixel_mean=config.pixel_mean,
        pixel_std=config.pixel_std,
    )

preprocess

preprocess(
    images: ImageInput | Sequence[ImageInput],
    **kwargs: Unpack[ImagesKwargs],
) -> BatchFeature

Preprocess one image or an image batch.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput]

PIL, numpy, or torch image input.

required
return_tensors

Tensor framework. Only pt is supported.

required
kwargs Unpack[ImagesKwargs]

Ignored compatibility kwargs.

{}

Returns:

Type Description
BatchFeature

Batch feature with padded pixel_values and original sizes.

Raises:

Type Description
ValueError

If return_tensors is not pt.

Source code in models/radm/src/radm/image_processing_radm.py
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def preprocess(
    self,
    images: ImageInput | Sequence[ImageInput],
    **kwargs: Unpack[ImagesKwargs],
) -> BatchFeature:
    """Preprocess one image or an image batch.

    Args:
        images: PIL, numpy, or torch image input.
        return_tensors: Tensor framework. Only ``pt`` is supported.
        kwargs: Ignored compatibility kwargs.

    Returns:
        Batch feature with padded ``pixel_values`` and original sizes.

    Raises:
        ValueError: If ``return_tensors`` is not ``pt``.
    """
    return_tensors = kwargs.pop("return_tensors", "pt")
    if return_tensors != "pt":
        raise ValueError("RADMImageProcessor only supports return_tensors='pt'")

    tensors: list[Float[torch.Tensor, "channels height width"]] = []
    original_sizes: list[tuple[int, int]] = []
    resized_sizes: list[tuple[int, int]] = []
    for image in _ensure_pil_batch(images):
        width, height = image.size
        original_sizes.append((height, width))
        resized = _resize_short_side(image.convert("RGB"), self.image_size)
        resized_width, resized_height = resized.size
        resized_sizes.append((resized_height, resized_width))
        array = np.asarray(resized, dtype=np.float32)
        mean = np.asarray(self.pixel_mean, dtype=np.float32)
        std = np.asarray(self.pixel_std, dtype=np.float32)

        tensors.append(torch.from_numpy(((array - mean) / std).transpose(2, 0, 1)))

    padded, padding_mask = _pad_tensors(tensors, self.size_divisibility)
    return BatchFeature(
        {
            "pixel_values": padded.float(),
            "pixel_mask": padding_mask,
            "original_sizes": torch.tensor(original_sizes, dtype=torch.long),
            "resized_sizes": torch.tensor(resized_sizes, dtype=torch.long),
        }
    )

RADMDenoiser

Bases: ModelMixin, ConfigMixin

Genuine RADM proposal denoiser shared by inference and training.

Source code in models/radm/src/radm/modeling_radm.py
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
class RADMDenoiser(ModelMixin, ConfigMixin):
    """Genuine RADM proposal denoiser shared by inference and training."""

    config_name: str = "denoiser_config.json"

    def __init__(
        self,
        *,
        config: RADMConfig | None = None,
        num_classes: int | None = None,
        num_proposals: int | None = None,
        hidden_dim: int | None = None,
        text_feature_dim: int | None = None,
        max_text_num: int | None = None,
        num_heads: int | None = None,
        num_attention_heads: int | None = None,
        dim_feedforward: int | None = None,
        num_dynamic: int | None = None,
        dim_dynamic: int | None = None,
        num_cls: int | None = None,
        num_reg: int | None = None,
        roi_resolution: int | None = None,
        roi_sampling_ratio: int | None = None,
        with_vtram: bool | None = None,
        with_gram: bool | None = None,
        deep_supervision: bool | None = None,
        backbone_depth: int | None = None,
        backbone_freeze_at: int | None = None,
        num_train_timesteps: int | None = None,
        snr_scale: float | None = None,
    ) -> None:
        """Initialize from an explicit package config or serialized model values."""
        super().__init__()
        if config is None:
            values = {
                "num_classes": num_classes,
                "num_proposals": num_proposals,
                "hidden_dim": hidden_dim,
                "text_feature_dim": text_feature_dim,
                "max_text_num": max_text_num,
                "num_heads": num_heads,
                "num_attention_heads": num_attention_heads,
                "dim_feedforward": dim_feedforward,
                "num_dynamic": num_dynamic,
                "dim_dynamic": dim_dynamic,
                "num_cls": num_cls,
                "num_reg": num_reg,
                "roi_resolution": roi_resolution,
                "roi_sampling_ratio": roi_sampling_ratio,
                "with_vtram": with_vtram,
                "with_gram": with_gram,
                "deep_supervision": deep_supervision,
                "backbone_depth": backbone_depth,
                "backbone_freeze_at": backbone_freeze_at,
                "num_train_timesteps": num_train_timesteps,
                "snr_scale": snr_scale,
            }
            if any(value is None for value in values.values()):
                raise TypeError("RADMDenoiser requires an explicit RADMConfig")

            config = RADMConfig(**values)  # type: ignore[arg-type]

        self.register_to_config(
            **{
                "dataset_name": config.dataset_name,
                "id2label": config.id2label,
                "original_id2label": config.original_id2label,
                "num_classes": config.num_classes,
                "num_proposals": config.num_proposals,
                "hidden_dim": config.hidden_dim,
                "text_feature_dim": config.text_feature_dim,
                "max_text_num": config.max_text_num,
                "num_heads": config.num_heads,
                "num_attention_heads": config.num_attention_heads,
                "dim_feedforward": config.dim_feedforward,
                "num_dynamic": config.num_dynamic,
                "dim_dynamic": config.dim_dynamic,
                "num_cls": config.num_cls,
                "num_reg": config.num_reg,
                "roi_resolution": config.roi_resolution,
                "roi_sampling_ratio": config.roi_sampling_ratio,
                "with_vtram": config.with_vtram,
                "with_gram": config.with_gram,
                "deep_supervision": config.deep_supervision,
                "backbone_depth": config.backbone_depth,
                "backbone_freeze_at": config.backbone_freeze_at,
                "num_train_timesteps": config.num_train_timesteps,
                "snr_scale": config.snr_scale,
                "sample_step": config.sample_step,
            }
        )
        if config.backbone_depth not in (18, 50):
            raise ValueError("backbone_depth must be 18 or 50")

        self.num_classes = config.num_classes
        self.num_proposals = config.num_proposals
        self.hidden_dim = config.hidden_dim
        self.backbone = RADMBackbone(
            depth=cast(Literal[18, 50], config.backbone_depth),
            freeze_at=config.backbone_freeze_at,
        )
        self.head = RADMProposalHead(config)
        self._register_diffusion_buffers(config)

    def _register_diffusion_buffers(self, config: RADMConfig) -> None:
        betas = cosine_beta_schedule(config.num_train_timesteps)
        alphas = 1.0 - betas
        alphas_cumprod = torch.cumprod(alphas, dim=0)
        alphas_cumprod_prev = F.pad(alphas_cumprod[:-1], (1, 0), value=1.0)
        self.register_buffer("betas", betas)
        self.register_buffer("alphas_cumprod", alphas_cumprod)
        self.register_buffer("alphas_cumprod_prev", alphas_cumprod_prev)
        self.register_buffer("sqrt_alphas_cumprod", alphas_cumprod.sqrt())
        self.register_buffer(
            "sqrt_one_minus_alphas_cumprod", (1.0 - alphas_cumprod).sqrt()
        )
        self.register_buffer(
            "log_one_minus_alphas_cumprod", torch.log(1.0 - alphas_cumprod)
        )
        self.register_buffer("sqrt_recip_alphas_cumprod", (1.0 / alphas_cumprod).sqrt())
        self.register_buffer(
            "sqrt_recipm1_alphas_cumprod", (1.0 / alphas_cumprod - 1.0).sqrt()
        )
        posterior_variance = (
            betas * (1.0 - alphas_cumprod_prev) / (1.0 - alphas_cumprod)
        )
        self.register_buffer("posterior_variance", posterior_variance)
        self.register_buffer(
            "posterior_log_variance_clipped",
            torch.log(posterior_variance.clamp(min=1e-20)),
        )
        self.register_buffer(
            "posterior_mean_coef1",
            betas * alphas_cumprod_prev.sqrt() / (1.0 - alphas_cumprod),
        )
        self.register_buffer(
            "posterior_mean_coef2",
            (1.0 - alphas_cumprod_prev) * alphas.sqrt() / (1.0 - alphas_cumprod),
        )

    def q_sample(
        self,
        x_start: Float[torch.Tensor, "batch proposals 4"],
        timesteps: Int[torch.Tensor, "batch"],
        noise: Float[torch.Tensor, "batch proposals 4"] | None = None,
    ) -> Float[torch.Tensor, "batch proposals 4"]:
        """Sample the configured cosine forward diffusion process."""
        sampled_noise = torch.randn_like(x_start) if noise is None else noise
        return (
            _extract(self.sqrt_alphas_cumprod, timesteps, x_start.shape) * x_start
            + _extract(self.sqrt_one_minus_alphas_cumprod, timesteps, x_start.shape)
            * sampled_noise
        )

    def predict_noise_from_start(
        self,
        x_t: Float[torch.Tensor, "batch proposals 4"],
        timesteps: Int[torch.Tensor, "batch"],
        x_start: Float[torch.Tensor, "batch proposals 4"],
    ) -> Float[torch.Tensor, "batch proposals 4"]:
        """Recover epsilon from a predicted clean diffusion sample."""
        return (
            _extract(self.sqrt_recip_alphas_cumprod, timesteps, x_t.shape) * x_t
            - x_start
        ) / _extract(self.sqrt_recipm1_alphas_cumprod, timesteps, x_t.shape)

    def prepare_diffusion_concat(
        self,
        boxes_cxcywh: Float[torch.Tensor, "elements 4"],
        *,
        generator: torch.Generator | None = None,
    ) -> tuple[
        Float[torch.Tensor, "proposals 4"],
        Float[torch.Tensor, "proposals 4"],
        Int[torch.Tensor, "1"],
    ]:
        """Prepare one image's padded proposals using the configured sampler."""
        device = boxes_cxcywh.device
        timestep = randint(
            0,
            self.config.num_train_timesteps,
            (1,),
            generator=generator,
            device=device,
        ).long()
        noise = randn(
            self.num_proposals,
            4,
            generator=generator,
            device=device,
        )
        count = boxes_cxcywh.shape[0]
        if count == 0:
            boxes_cxcywh = boxes_cxcywh.new_tensor([[0.5, 0.5, 1.0, 1.0]])
            count = 1

        if count < self.num_proposals:
            placeholders = (
                randn(
                    self.num_proposals - count,
                    4,
                    generator=generator,
                    device=device,
                )
                / 6.0
                + 0.5
            )
            placeholders[:, 2:] = placeholders[:, 2:].clamp_min(1e-4)
            clean = torch.cat((boxes_cxcywh, placeholders), dim=0)
        elif count > self.num_proposals:
            clean = boxes_cxcywh[: self.num_proposals]
        else:
            clean = boxes_cxcywh

        clean = (clean * 2.0 - 1.0) * self.config.snr_scale
        diffused = self.q_sample(clean, timestep, noise=noise)
        diffused = diffused.clamp(-self.config.snr_scale, self.config.snr_scale)
        diffused = (diffused / self.config.snr_scale + 1.0) / 2.0
        return _cxcywh_to_xyxy(diffused), noise, timestep

    def forward(
        self,
        boxes_xyxy: Float[torch.Tensor, "batch proposals 4"],
        timesteps: Int[torch.Tensor, "batch"]
        | Int[torch.Tensor, ""]
        | Float[torch.Tensor, "batch"]
        | Float[torch.Tensor, ""],
        text_features: Float[torch.Tensor, "batch text text_dim"],
        text_mask: Bool[torch.Tensor, "batch text 1"] | None = None,
        images: Float[torch.Tensor, "batch channels height width"] | None = None,
        image_scales: Float[torch.Tensor, "batch 4"] | None = None,
        absolute_boxes_xyxy: Float[torch.Tensor, "batch proposals 4"] | None = None,
    ) -> RADMDenoiserOutput:
        """Predict proposal classes and denoised boxes."""
        if text_features.ndim != 3:
            raise ValueError("text_features must have shape (batch, text, dim)")

        if boxes_xyxy.shape[1] != self.num_proposals:
            raise ValueError(f"expected {self.num_proposals} proposals")

        if timesteps.ndim == 0:
            timesteps = timesteps.repeat(boxes_xyxy.shape[0])

        timestep_batch = timesteps.to(
            device=boxes_xyxy.device, dtype=torch.long
        ).reshape(-1)
        if images is None:
            features = self._fallback_features(boxes_xyxy)
            default_scale = boxes_xyxy.new_ones((boxes_xyxy.shape[0], 4))
        else:
            features = self.backbone(images)
            default_scale = boxes_xyxy.new_tensor(
                (images.shape[-1], images.shape[-2], images.shape[-1], images.shape[-2])
            ).expand(boxes_xyxy.shape[0], -1)

        resolved_scales = default_scale if image_scales is None else image_scales
        auxiliary_logits, auxiliary_boxes_absolute = self.head(
            features,
            boxes_xyxy,
            text_features,
            text_mask,
            timestep_batch,
            resolved_scales,
            absolute_boxes_xyxy,
        )
        image_scale = resolved_scales[:, None, :].to(device=boxes_xyxy.device)
        auxiliary_boxes = auxiliary_boxes_absolute / image_scale
        logits = auxiliary_logits[-1]
        pred_original = auxiliary_boxes[-1]
        return RADMDenoiserOutput(
            logits=logits,
            boxes_xyxy=pred_original,
            pred_original_sample=pred_original,
            pred_noise=boxes_xyxy - pred_original,
            auxiliary_logits=auxiliary_logits,
            auxiliary_boxes_xyxy=auxiliary_boxes,
            auxiliary_boxes_absolute_xyxy=auxiliary_boxes_absolute,
        )

    def _fallback_features(
        self, boxes_xyxy: Float[torch.Tensor, "batch proposals 4"]
    ) -> dict[str, Float[torch.Tensor, "batch channels height width"]]:
        """Create a deterministic proposal-only feature pyramid for serialization tests."""
        zero = boxes_xyxy.new_zeros(boxes_xyxy.shape[0], 256, 1, 1)
        return {name: zero for name in ("p2", "p3", "p4", "p5")}

__init__

__init__(
    *,
    config: RADMConfig | None = None,
    num_classes: int | None = None,
    num_proposals: int | None = None,
    hidden_dim: int | None = None,
    text_feature_dim: int | None = None,
    max_text_num: int | None = None,
    num_heads: int | None = None,
    num_attention_heads: int | None = None,
    dim_feedforward: int | None = None,
    num_dynamic: int | None = None,
    dim_dynamic: int | None = None,
    num_cls: int | None = None,
    num_reg: int | None = None,
    roi_resolution: int | None = None,
    roi_sampling_ratio: int | None = None,
    with_vtram: bool | None = None,
    with_gram: bool | None = None,
    deep_supervision: bool | None = None,
    backbone_depth: int | None = None,
    backbone_freeze_at: int | None = None,
    num_train_timesteps: int | None = None,
    snr_scale: float | None = None,
) -> None

Initialize from an explicit package config or serialized model values.

Source code in models/radm/src/radm/modeling_radm.py
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
def __init__(
    self,
    *,
    config: RADMConfig | None = None,
    num_classes: int | None = None,
    num_proposals: int | None = None,
    hidden_dim: int | None = None,
    text_feature_dim: int | None = None,
    max_text_num: int | None = None,
    num_heads: int | None = None,
    num_attention_heads: int | None = None,
    dim_feedforward: int | None = None,
    num_dynamic: int | None = None,
    dim_dynamic: int | None = None,
    num_cls: int | None = None,
    num_reg: int | None = None,
    roi_resolution: int | None = None,
    roi_sampling_ratio: int | None = None,
    with_vtram: bool | None = None,
    with_gram: bool | None = None,
    deep_supervision: bool | None = None,
    backbone_depth: int | None = None,
    backbone_freeze_at: int | None = None,
    num_train_timesteps: int | None = None,
    snr_scale: float | None = None,
) -> None:
    """Initialize from an explicit package config or serialized model values."""
    super().__init__()
    if config is None:
        values = {
            "num_classes": num_classes,
            "num_proposals": num_proposals,
            "hidden_dim": hidden_dim,
            "text_feature_dim": text_feature_dim,
            "max_text_num": max_text_num,
            "num_heads": num_heads,
            "num_attention_heads": num_attention_heads,
            "dim_feedforward": dim_feedforward,
            "num_dynamic": num_dynamic,
            "dim_dynamic": dim_dynamic,
            "num_cls": num_cls,
            "num_reg": num_reg,
            "roi_resolution": roi_resolution,
            "roi_sampling_ratio": roi_sampling_ratio,
            "with_vtram": with_vtram,
            "with_gram": with_gram,
            "deep_supervision": deep_supervision,
            "backbone_depth": backbone_depth,
            "backbone_freeze_at": backbone_freeze_at,
            "num_train_timesteps": num_train_timesteps,
            "snr_scale": snr_scale,
        }
        if any(value is None for value in values.values()):
            raise TypeError("RADMDenoiser requires an explicit RADMConfig")

        config = RADMConfig(**values)  # type: ignore[arg-type]

    self.register_to_config(
        **{
            "dataset_name": config.dataset_name,
            "id2label": config.id2label,
            "original_id2label": config.original_id2label,
            "num_classes": config.num_classes,
            "num_proposals": config.num_proposals,
            "hidden_dim": config.hidden_dim,
            "text_feature_dim": config.text_feature_dim,
            "max_text_num": config.max_text_num,
            "num_heads": config.num_heads,
            "num_attention_heads": config.num_attention_heads,
            "dim_feedforward": config.dim_feedforward,
            "num_dynamic": config.num_dynamic,
            "dim_dynamic": config.dim_dynamic,
            "num_cls": config.num_cls,
            "num_reg": config.num_reg,
            "roi_resolution": config.roi_resolution,
            "roi_sampling_ratio": config.roi_sampling_ratio,
            "with_vtram": config.with_vtram,
            "with_gram": config.with_gram,
            "deep_supervision": config.deep_supervision,
            "backbone_depth": config.backbone_depth,
            "backbone_freeze_at": config.backbone_freeze_at,
            "num_train_timesteps": config.num_train_timesteps,
            "snr_scale": config.snr_scale,
            "sample_step": config.sample_step,
        }
    )
    if config.backbone_depth not in (18, 50):
        raise ValueError("backbone_depth must be 18 or 50")

    self.num_classes = config.num_classes
    self.num_proposals = config.num_proposals
    self.hidden_dim = config.hidden_dim
    self.backbone = RADMBackbone(
        depth=cast(Literal[18, 50], config.backbone_depth),
        freeze_at=config.backbone_freeze_at,
    )
    self.head = RADMProposalHead(config)
    self._register_diffusion_buffers(config)

q_sample

q_sample(
    x_start: Float[Tensor, "batch proposals 4"],
    timesteps: Int[Tensor, "batch"],
    noise: Float[Tensor, "batch proposals 4"] | None = None,
) -> Float[torch.Tensor, "batch proposals 4"]

Sample the configured cosine forward diffusion process.

Source code in models/radm/src/radm/modeling_radm.py
968
969
970
971
972
973
974
975
976
977
978
979
980
def q_sample(
    self,
    x_start: Float[torch.Tensor, "batch proposals 4"],
    timesteps: Int[torch.Tensor, "batch"],
    noise: Float[torch.Tensor, "batch proposals 4"] | None = None,
) -> Float[torch.Tensor, "batch proposals 4"]:
    """Sample the configured cosine forward diffusion process."""
    sampled_noise = torch.randn_like(x_start) if noise is None else noise
    return (
        _extract(self.sqrt_alphas_cumprod, timesteps, x_start.shape) * x_start
        + _extract(self.sqrt_one_minus_alphas_cumprod, timesteps, x_start.shape)
        * sampled_noise
    )

predict_noise_from_start

predict_noise_from_start(
    x_t: Float[Tensor, "batch proposals 4"],
    timesteps: Int[Tensor, "batch"],
    x_start: Float[Tensor, "batch proposals 4"],
) -> Float[torch.Tensor, "batch proposals 4"]

Recover epsilon from a predicted clean diffusion sample.

Source code in models/radm/src/radm/modeling_radm.py
982
983
984
985
986
987
988
989
990
991
992
def predict_noise_from_start(
    self,
    x_t: Float[torch.Tensor, "batch proposals 4"],
    timesteps: Int[torch.Tensor, "batch"],
    x_start: Float[torch.Tensor, "batch proposals 4"],
) -> Float[torch.Tensor, "batch proposals 4"]:
    """Recover epsilon from a predicted clean diffusion sample."""
    return (
        _extract(self.sqrt_recip_alphas_cumprod, timesteps, x_t.shape) * x_t
        - x_start
    ) / _extract(self.sqrt_recipm1_alphas_cumprod, timesteps, x_t.shape)

prepare_diffusion_concat

prepare_diffusion_concat(
    boxes_cxcywh: Float[Tensor, "elements 4"],
    *,
    generator: Generator | None = None,
) -> tuple[
    Float[torch.Tensor, "proposals 4"],
    Float[torch.Tensor, "proposals 4"],
    Int[torch.Tensor, "1"],
]

Prepare one image's padded proposals using the configured sampler.

Source code in models/radm/src/radm/modeling_radm.py
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
def prepare_diffusion_concat(
    self,
    boxes_cxcywh: Float[torch.Tensor, "elements 4"],
    *,
    generator: torch.Generator | None = None,
) -> tuple[
    Float[torch.Tensor, "proposals 4"],
    Float[torch.Tensor, "proposals 4"],
    Int[torch.Tensor, "1"],
]:
    """Prepare one image's padded proposals using the configured sampler."""
    device = boxes_cxcywh.device
    timestep = randint(
        0,
        self.config.num_train_timesteps,
        (1,),
        generator=generator,
        device=device,
    ).long()
    noise = randn(
        self.num_proposals,
        4,
        generator=generator,
        device=device,
    )
    count = boxes_cxcywh.shape[0]
    if count == 0:
        boxes_cxcywh = boxes_cxcywh.new_tensor([[0.5, 0.5, 1.0, 1.0]])
        count = 1

    if count < self.num_proposals:
        placeholders = (
            randn(
                self.num_proposals - count,
                4,
                generator=generator,
                device=device,
            )
            / 6.0
            + 0.5
        )
        placeholders[:, 2:] = placeholders[:, 2:].clamp_min(1e-4)
        clean = torch.cat((boxes_cxcywh, placeholders), dim=0)
    elif count > self.num_proposals:
        clean = boxes_cxcywh[: self.num_proposals]
    else:
        clean = boxes_cxcywh

    clean = (clean * 2.0 - 1.0) * self.config.snr_scale
    diffused = self.q_sample(clean, timestep, noise=noise)
    diffused = diffused.clamp(-self.config.snr_scale, self.config.snr_scale)
    diffused = (diffused / self.config.snr_scale + 1.0) / 2.0
    return _cxcywh_to_xyxy(diffused), noise, timestep

forward

forward(
    boxes_xyxy: Float[Tensor, "batch proposals 4"],
    timesteps: Int[Tensor, "batch"]
    | Int[Tensor, ""]
    | Float[Tensor, "batch"]
    | Float[Tensor, ""],
    text_features: Float[Tensor, "batch text text_dim"],
    text_mask: Bool[Tensor, "batch text 1"] | None = None,
    images: Float[Tensor, "batch channels height width"]
    | None = None,
    image_scales: Float[Tensor, "batch 4"] | None = None,
    absolute_boxes_xyxy: Float[Tensor, "batch proposals 4"]
    | None = None,
) -> RADMDenoiserOutput

Predict proposal classes and denoised boxes.

Source code in models/radm/src/radm/modeling_radm.py
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
def forward(
    self,
    boxes_xyxy: Float[torch.Tensor, "batch proposals 4"],
    timesteps: Int[torch.Tensor, "batch"]
    | Int[torch.Tensor, ""]
    | Float[torch.Tensor, "batch"]
    | Float[torch.Tensor, ""],
    text_features: Float[torch.Tensor, "batch text text_dim"],
    text_mask: Bool[torch.Tensor, "batch text 1"] | None = None,
    images: Float[torch.Tensor, "batch channels height width"] | None = None,
    image_scales: Float[torch.Tensor, "batch 4"] | None = None,
    absolute_boxes_xyxy: Float[torch.Tensor, "batch proposals 4"] | None = None,
) -> RADMDenoiserOutput:
    """Predict proposal classes and denoised boxes."""
    if text_features.ndim != 3:
        raise ValueError("text_features must have shape (batch, text, dim)")

    if boxes_xyxy.shape[1] != self.num_proposals:
        raise ValueError(f"expected {self.num_proposals} proposals")

    if timesteps.ndim == 0:
        timesteps = timesteps.repeat(boxes_xyxy.shape[0])

    timestep_batch = timesteps.to(
        device=boxes_xyxy.device, dtype=torch.long
    ).reshape(-1)
    if images is None:
        features = self._fallback_features(boxes_xyxy)
        default_scale = boxes_xyxy.new_ones((boxes_xyxy.shape[0], 4))
    else:
        features = self.backbone(images)
        default_scale = boxes_xyxy.new_tensor(
            (images.shape[-1], images.shape[-2], images.shape[-1], images.shape[-2])
        ).expand(boxes_xyxy.shape[0], -1)

    resolved_scales = default_scale if image_scales is None else image_scales
    auxiliary_logits, auxiliary_boxes_absolute = self.head(
        features,
        boxes_xyxy,
        text_features,
        text_mask,
        timestep_batch,
        resolved_scales,
        absolute_boxes_xyxy,
    )
    image_scale = resolved_scales[:, None, :].to(device=boxes_xyxy.device)
    auxiliary_boxes = auxiliary_boxes_absolute / image_scale
    logits = auxiliary_logits[-1]
    pred_original = auxiliary_boxes[-1]
    return RADMDenoiserOutput(
        logits=logits,
        boxes_xyxy=pred_original,
        pred_original_sample=pred_original,
        pred_noise=boxes_xyxy - pred_original,
        auxiliary_logits=auxiliary_logits,
        auxiliary_boxes_xyxy=auxiliary_boxes,
        auxiliary_boxes_absolute_xyxy=auxiliary_boxes_absolute,
    )

RADMDenoiserOutput dataclass

Bases: BaseOutput

Predictions from one relation-aware proposal refinement pass.

Source code in models/radm/src/radm/modeling_radm.py
27
28
29
30
31
32
33
34
35
36
37
38
39
@dataclass
class RADMDenoiserOutput(BaseOutput):
    """Predictions from one relation-aware proposal refinement pass."""

    logits: Float[torch.Tensor, "batch proposals classes"]
    boxes_xyxy: Float[torch.Tensor, "batch proposals 4"]
    pred_original_sample: Float[torch.Tensor, "batch proposals 4"]
    pred_noise: Float[torch.Tensor, "batch proposals 4"]
    auxiliary_logits: Float[torch.Tensor, "heads batch proposals classes"] | None = None
    auxiliary_boxes_xyxy: Float[torch.Tensor, "heads batch proposals 4"] | None = None
    auxiliary_boxes_absolute_xyxy: (
        Float[torch.Tensor, "heads batch proposals 4"] | None
    ) = None

RADMPipeline

Bases: DiffusionPipeline

Generate poster layouts with a RADM proposal diffusion pipeline.

Source code in models/radm/src/radm/pipeline_radm.py
 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
class RADMPipeline(DiffusionPipeline):
    """Generate poster layouts with a RADM proposal diffusion pipeline."""

    model_cpu_offload_seq: str = "denoiser"

    def __init__(
        self,
        denoiser: RADMDenoiser,
        scheduler: RADMScheduler,
        config: RADMConfig,
        processor: RADMProcessor | None = None,
    ) -> None:
        """Create a RADM pipeline.

        Args:
            denoiser: Proposal denoiser component.
            scheduler: Proposal diffusion scheduler.
            config: RADM configuration.
            processor: Optional input/output processor derived from ``config``.
        """
        super().__init__()
        self.register_modules(denoiser=denoiser, scheduler=scheduler)
        self.processor = processor or RADMProcessor(config=config)
        self.denoiser.eval()

    @torch.no_grad()
    def __call__(
        self,
        images: ImageInput | Sequence[ImageInput] | None = None,
        *,
        content: Mapping[
            str,
            ImageInput
            | Sequence[ImageInput]
            | Float[torch.Tensor, "batch text text_dim"]
            | Float[np.ndarray, "batch text text_dim"]
            | Bool[torch.Tensor, "batch text 1"]
            | Bool[np.ndarray, "batch text 1"]
            | NestedFloatSequence
            | NestedBoolSequence
            | JsonValue,
        ]
        | None = None,
        text_features: Float[torch.Tensor, "batch text text_dim"]
        | Float[np.ndarray, "batch text text_dim"]
        | NestedFloatSequence
        | None = None,
        text_mask: Bool[torch.Tensor, "batch text 1"]
        | Bool[np.ndarray, "batch text 1"]
        | NestedBoolSequence
        | None = None,
        batch_size: int = 1,
        seed: int | None = None,
        generator: torch.Generator | None = None,
        condition_type: ConditionType | str = ConditionType.content_image,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[Sequence[int | str]]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[Sequence[float]]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[Sequence[bool]]
        | None = None,
        num_elements: int | Sequence[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_intermediates: bool = False,
        class_threshold: float | None = None,
        nms_threshold: float | None = None,
        return_raw_predictions: bool = False,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | Mapping[int, str]
            | Mapping[
                str,
                str
                | int
                | float
                | bool
                | None
                | Float[torch.Tensor, "..."]
                | Int[torch.Tensor, "..."]
                | Bool[torch.Tensor, "..."]
                | list[Float[torch.Tensor, "..."]],
            ]
            | list[Float[torch.Tensor, "..."]]
            | None,
        ]
    ):
        """Generate normalized poster layout boxes and labels.

        Args:
            images: Content image or image batch.
            content: Optional content carrier with image and text-feature fields.
            text_features: Optional RADM text feature tensor.
            text_mask: Optional text-feature mask.
            batch_size: Batch size for synthetic smoke inputs.
            seed: Optional seed used when ``generator`` is omitted.
            generator: Optional torch generator. Takes precedence over ``seed``.
            condition_type: Public condition type. RADM supports ``content_image``.
            labels: Accepted common layout argument; unsupported in the first PR.
            bbox: Accepted common layout argument; unsupported in the first PR.
            mask: Accepted common layout argument; unsupported in the first PR.
            num_elements: Accepted common layout argument; unsupported in the first PR.
            box_format: Accepted common layout argument.
            normalized: Accepted common layout argument.
            canvas_size: Accepted common layout argument.
            num_inference_steps: Number of reverse diffusion steps.
            output_type: ``"dataclass"`` or ``"dict"``.
            return_intermediates: Whether to return debug intermediates.
            class_threshold: Optional confidence threshold.
            nms_threshold: Optional NMS threshold.
            return_raw_predictions: Whether raw predictions should be included.

        Returns:
            Layout generation output dataclass or dictionary.

        Raises:
            NotImplementedError: If the selected condition is unsupported.
            ValueError: If public arguments are unsupported.

        Examples:
            >>> from PIL import Image
            >>> config = RADMConfig(num_proposals=2, hidden_dim=8, text_feature_dim=4, backbone_depth=18)
            >>> pipe = RADMPipeline(
            ...     denoiser=RADMDenoiser(config=config),
            ...     scheduler=RADMScheduler(num_train_timesteps=10, num_inference_steps=2),
            ...     config=config,
            ...     processor=RADMProcessor(config=config),
            ... )
            >>> out = pipe(Image.new("RGB", (16, 16)), seed=0, num_inference_steps=2)
            >>> out.bbox.shape[-1]
            4
        """
        del labels, bbox, mask, num_elements, box_format, normalized, canvas_size
        self.processor.validate_condition(condition_type)
        generator = resolve_torch_generator(generator=generator, seed=seed)
        encoded = self.processor(
            images,
            content=content,
            text_features=text_features,
            text_mask=text_mask,
            batch_size=batch_size,
        )
        batch_size = int(encoded["pixel_values"].shape[0])
        sample = self.scheduler.sample_initial_proposals(
            batch_size=batch_size,
            num_proposals=self.denoiser.config.num_proposals,
            generator=generator,
            device=self.device,
            dtype=next(self.denoiser.parameters()).dtype,
        )
        self.scheduler.set_timesteps(num_inference_steps, device=self.device)
        text = encoded["text_features"].to(self.device)
        text_valid = encoded["text_mask"].to(self.device)
        trajectory = [] if return_intermediates else None
        final_boxes = self.scheduler.latent_to_normalized_boxes(sample)
        logits = sample.new_zeros(
            batch_size,
            self.denoiser.config.num_proposals,
            self.denoiser.config.num_classes,
        )
        for timestep in self.scheduler.timesteps:
            normalized_sample = self.scheduler.latent_to_normalized_boxes(sample)
            t_batch = torch.full(
                (batch_size,),
                float(timestep.item()),
                device=self.device,
                dtype=sample.dtype,
            )
            denoised = self.denoiser(
                boxes_xyxy=normalized_sample,
                timesteps=t_batch,
                text_features=text,
                text_mask=text_valid,
                images=encoded["pixel_values"].to(self.device),
            )
            logits = denoised.logits
            final_boxes = denoised.boxes_xyxy
            sample = self.scheduler.step(
                self.scheduler.normalized_boxes_to_latent(
                    denoised.pred_original_sample
                ),
                timestep,
                sample,
                generator=generator,
            ).prev_sample
            if trajectory is not None:
                trajectory.append(sample.detach().cpu())

        intermediates = {
            "condition_type": str(ConditionType.content_image),
            "original_sizes": encoded["original_sizes"].detach().cpu(),
            "resized_sizes": encoded["resized_sizes"].detach().cpu(),
            "text_mask_valid": encoded["text_mask"].sum(dim=(1, 2)).detach().cpu(),
        }
        if trajectory is not None:
            intermediates["trajectory"] = trajectory

        output = self.processor.decode(
            boxes_xyxy=final_boxes,
            logits=logits,
            class_threshold=class_threshold
            if class_threshold is not None
            else self.processor.config.class_threshold,
            nms_threshold=nms_threshold
            if nms_threshold is not None
            else self.processor.config.nms_threshold,
            output_type=output_type,
            return_intermediates=return_intermediates or return_raw_predictions,
            extra_intermediates=intermediates,
        )
        if isinstance(output, LayoutGenerationOutput) and trajectory is not None:
            output.trajectory = trajectory

        return output

    generate = __call__

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

        Args:
            save_directory: Output directory.
        """
        super().save_pretrained(save_directory)
        self.processor.config.save_config(save_directory)
        self.processor.save_pretrained(
            Path(save_directory) / self.processor.config.processor_subfolder
        )

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

        Args:
            pretrained_model_name_or_path: Local directory or Hub id.

        Returns:
            Loaded RADM pipeline.
        """
        config_dict, _ = RADMConfig.load_config(
            pretrained_model_name_or_path,
            return_unused_kwargs=True,
        )
        config = cast(RADMConfig, RADMConfig.from_config(config_dict))
        pipe = super().from_pretrained(pretrained_model_name_or_path, config=config)
        pipe.processor = RADMProcessor.from_pretrained(
            pretrained_model_name_or_path,
            subfolder=config.processor_subfolder,
        )
        return pipe

__init__

__init__(
    denoiser: RADMDenoiser,
    scheduler: RADMScheduler,
    config: RADMConfig,
    processor: RADMProcessor | None = None,
) -> None

Create a RADM pipeline.

Parameters:

Name Type Description Default
denoiser RADMDenoiser

Proposal denoiser component.

required
scheduler RADMScheduler

Proposal diffusion scheduler.

required
config RADMConfig

RADM configuration.

required
processor RADMProcessor | None

Optional input/output processor derived from config.

None
Source code in models/radm/src/radm/pipeline_radm.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def __init__(
    self,
    denoiser: RADMDenoiser,
    scheduler: RADMScheduler,
    config: RADMConfig,
    processor: RADMProcessor | None = None,
) -> None:
    """Create a RADM pipeline.

    Args:
        denoiser: Proposal denoiser component.
        scheduler: Proposal diffusion scheduler.
        config: RADM configuration.
        processor: Optional input/output processor derived from ``config``.
    """
    super().__init__()
    self.register_modules(denoiser=denoiser, scheduler=scheduler)
    self.processor = processor or RADMProcessor(config=config)
    self.denoiser.eval()

__call__

__call__(
    images: ImageInput | Sequence[ImageInput] | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Float[Tensor, "batch text text_dim"]
        | Float[ndarray, "batch text text_dim"]
        | Bool[Tensor, "batch text 1"]
        | Bool[ndarray, "batch text 1"]
        | NestedFloatSequence
        | NestedBoolSequence
        | JsonValue,
    ]
    | None = None,
    text_features: Float[Tensor, "batch text text_dim"]
    | Float[ndarray, "batch text text_dim"]
    | NestedFloatSequence
    | None = None,
    text_mask: Bool[Tensor, "batch text 1"]
    | Bool[ndarray, "batch text 1"]
    | NestedBoolSequence
    | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.content_image,
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[Sequence[int | str]]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[Sequence[float]]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[Sequence[bool]]
    | None = None,
    num_elements: int
    | Sequence[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    class_threshold: float | None = None,
    nms_threshold: float | None = None,
    return_raw_predictions: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | Mapping[int, str]
        | Mapping[
            str,
            str
            | int
            | float
            | bool
            | None
            | Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | list[Float[torch.Tensor, "..."]],
        ]
        | list[Float[torch.Tensor, "..."]]
        | None,
    ]
)

Generate normalized poster layout boxes and labels.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput] | None

Content image or image batch.

None
content Mapping[str, ImageInput | Sequence[ImageInput] | Float[Tensor, 'batch text text_dim'] | Float[ndarray, 'batch text text_dim'] | Bool[Tensor, 'batch text 1'] | Bool[ndarray, 'batch text 1'] | NestedFloatSequence | NestedBoolSequence | JsonValue] | None

Optional content carrier with image and text-feature fields.

None
text_features Float[Tensor, 'batch text text_dim'] | Float[ndarray, 'batch text text_dim'] | NestedFloatSequence | None

Optional RADM text feature tensor.

None
text_mask Bool[Tensor, 'batch text 1'] | Bool[ndarray, 'batch text 1'] | NestedBoolSequence | None

Optional text-feature mask.

None
batch_size int

Batch size for synthetic smoke inputs.

1
seed int | None

Optional seed used when generator is omitted.

None
generator Generator | None

Optional torch generator. Takes precedence over seed.

None
condition_type ConditionType | str

Public condition type. RADM supports content_image.

content_image
labels Int[Tensor, 'batch elements'] | Int[ndarray, 'batch elements'] | Sequence[Sequence[int | str]] | None

Accepted common layout argument; unsupported in the first PR.

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

Accepted common layout argument; unsupported in the first PR.

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

Accepted common layout argument; unsupported in the first PR.

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

Accepted common layout argument; unsupported in the first PR.

None
box_format BoxFormat | str

Accepted common layout argument.

xywh
normalized bool

Accepted common layout argument.

True
canvas_size tuple[int, int] | None

Accepted common layout argument.

None
num_inference_steps int | None

Number of reverse diffusion steps.

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

"dataclass" or "dict".

'dataclass'
return_intermediates bool

Whether to return debug intermediates.

False
class_threshold float | None

Optional confidence threshold.

None
nms_threshold float | None

Optional NMS threshold.

None
return_raw_predictions bool

Whether raw predictions should be included.

False

Returns:

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

Layout generation output dataclass or dictionary.

Raises:

Type Description
NotImplementedError

If the selected condition is unsupported.

ValueError

If public arguments are unsupported.

Examples:

>>> from PIL import Image
>>> config = RADMConfig(num_proposals=2, hidden_dim=8, text_feature_dim=4, backbone_depth=18)
>>> pipe = RADMPipeline(
...     denoiser=RADMDenoiser(config=config),
...     scheduler=RADMScheduler(num_train_timesteps=10, num_inference_steps=2),
...     config=config,
...     processor=RADMProcessor(config=config),
... )
>>> out = pipe(Image.new("RGB", (16, 16)), seed=0, num_inference_steps=2)
>>> out.bbox.shape[-1]
4
Source code in models/radm/src/radm/pipeline_radm.py
 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
@torch.no_grad()
def __call__(
    self,
    images: ImageInput | Sequence[ImageInput] | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch text text_dim"]
        | Float[np.ndarray, "batch text text_dim"]
        | Bool[torch.Tensor, "batch text 1"]
        | Bool[np.ndarray, "batch text 1"]
        | NestedFloatSequence
        | NestedBoolSequence
        | JsonValue,
    ]
    | None = None,
    text_features: Float[torch.Tensor, "batch text text_dim"]
    | Float[np.ndarray, "batch text text_dim"]
    | NestedFloatSequence
    | None = None,
    text_mask: Bool[torch.Tensor, "batch text 1"]
    | Bool[np.ndarray, "batch text 1"]
    | NestedBoolSequence
    | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: torch.Generator | None = None,
    condition_type: ConditionType | str = ConditionType.content_image,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[Sequence[int | str]]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[Sequence[float]]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[Sequence[bool]]
    | None = None,
    num_elements: int | Sequence[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    class_threshold: float | None = None,
    nms_threshold: float | None = None,
    return_raw_predictions: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | Mapping[int, str]
        | Mapping[
            str,
            str
            | int
            | float
            | bool
            | None
            | Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | list[Float[torch.Tensor, "..."]],
        ]
        | list[Float[torch.Tensor, "..."]]
        | None,
    ]
):
    """Generate normalized poster layout boxes and labels.

    Args:
        images: Content image or image batch.
        content: Optional content carrier with image and text-feature fields.
        text_features: Optional RADM text feature tensor.
        text_mask: Optional text-feature mask.
        batch_size: Batch size for synthetic smoke inputs.
        seed: Optional seed used when ``generator`` is omitted.
        generator: Optional torch generator. Takes precedence over ``seed``.
        condition_type: Public condition type. RADM supports ``content_image``.
        labels: Accepted common layout argument; unsupported in the first PR.
        bbox: Accepted common layout argument; unsupported in the first PR.
        mask: Accepted common layout argument; unsupported in the first PR.
        num_elements: Accepted common layout argument; unsupported in the first PR.
        box_format: Accepted common layout argument.
        normalized: Accepted common layout argument.
        canvas_size: Accepted common layout argument.
        num_inference_steps: Number of reverse diffusion steps.
        output_type: ``"dataclass"`` or ``"dict"``.
        return_intermediates: Whether to return debug intermediates.
        class_threshold: Optional confidence threshold.
        nms_threshold: Optional NMS threshold.
        return_raw_predictions: Whether raw predictions should be included.

    Returns:
        Layout generation output dataclass or dictionary.

    Raises:
        NotImplementedError: If the selected condition is unsupported.
        ValueError: If public arguments are unsupported.

    Examples:
        >>> from PIL import Image
        >>> config = RADMConfig(num_proposals=2, hidden_dim=8, text_feature_dim=4, backbone_depth=18)
        >>> pipe = RADMPipeline(
        ...     denoiser=RADMDenoiser(config=config),
        ...     scheduler=RADMScheduler(num_train_timesteps=10, num_inference_steps=2),
        ...     config=config,
        ...     processor=RADMProcessor(config=config),
        ... )
        >>> out = pipe(Image.new("RGB", (16, 16)), seed=0, num_inference_steps=2)
        >>> out.bbox.shape[-1]
        4
    """
    del labels, bbox, mask, num_elements, box_format, normalized, canvas_size
    self.processor.validate_condition(condition_type)
    generator = resolve_torch_generator(generator=generator, seed=seed)
    encoded = self.processor(
        images,
        content=content,
        text_features=text_features,
        text_mask=text_mask,
        batch_size=batch_size,
    )
    batch_size = int(encoded["pixel_values"].shape[0])
    sample = self.scheduler.sample_initial_proposals(
        batch_size=batch_size,
        num_proposals=self.denoiser.config.num_proposals,
        generator=generator,
        device=self.device,
        dtype=next(self.denoiser.parameters()).dtype,
    )
    self.scheduler.set_timesteps(num_inference_steps, device=self.device)
    text = encoded["text_features"].to(self.device)
    text_valid = encoded["text_mask"].to(self.device)
    trajectory = [] if return_intermediates else None
    final_boxes = self.scheduler.latent_to_normalized_boxes(sample)
    logits = sample.new_zeros(
        batch_size,
        self.denoiser.config.num_proposals,
        self.denoiser.config.num_classes,
    )
    for timestep in self.scheduler.timesteps:
        normalized_sample = self.scheduler.latent_to_normalized_boxes(sample)
        t_batch = torch.full(
            (batch_size,),
            float(timestep.item()),
            device=self.device,
            dtype=sample.dtype,
        )
        denoised = self.denoiser(
            boxes_xyxy=normalized_sample,
            timesteps=t_batch,
            text_features=text,
            text_mask=text_valid,
            images=encoded["pixel_values"].to(self.device),
        )
        logits = denoised.logits
        final_boxes = denoised.boxes_xyxy
        sample = self.scheduler.step(
            self.scheduler.normalized_boxes_to_latent(
                denoised.pred_original_sample
            ),
            timestep,
            sample,
            generator=generator,
        ).prev_sample
        if trajectory is not None:
            trajectory.append(sample.detach().cpu())

    intermediates = {
        "condition_type": str(ConditionType.content_image),
        "original_sizes": encoded["original_sizes"].detach().cpu(),
        "resized_sizes": encoded["resized_sizes"].detach().cpu(),
        "text_mask_valid": encoded["text_mask"].sum(dim=(1, 2)).detach().cpu(),
    }
    if trajectory is not None:
        intermediates["trajectory"] = trajectory

    output = self.processor.decode(
        boxes_xyxy=final_boxes,
        logits=logits,
        class_threshold=class_threshold
        if class_threshold is not None
        else self.processor.config.class_threshold,
        nms_threshold=nms_threshold
        if nms_threshold is not None
        else self.processor.config.nms_threshold,
        output_type=output_type,
        return_intermediates=return_intermediates or return_raw_predictions,
        extra_intermediates=intermediates,
    )
    if isinstance(output, LayoutGenerationOutput) and trajectory is not None:
        output.trajectory = trajectory

    return output

save_pretrained

save_pretrained(save_directory: str | Path) -> None

Save pipeline components and RADM config.

Parameters:

Name Type Description Default
save_directory str | Path

Output directory.

required
Source code in models/radm/src/radm/pipeline_radm.py
265
266
267
268
269
270
271
272
273
274
275
def save_pretrained(self, save_directory: str | Path) -> None:
    """Save pipeline components and RADM config.

    Args:
        save_directory: Output directory.
    """
    super().save_pretrained(save_directory)
    self.processor.config.save_config(save_directory)
    self.processor.save_pretrained(
        Path(save_directory) / self.processor.config.processor_subfolder
    )

from_pretrained classmethod

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

Load a saved RADM pipeline.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Local directory or Hub id.

required

Returns:

Type Description
Self

Loaded RADM pipeline.

Source code in models/radm/src/radm/pipeline_radm.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
@classmethod
def from_pretrained(cls, pretrained_model_name_or_path: str | Path) -> Self:
    """Load a saved RADM pipeline.

    Args:
        pretrained_model_name_or_path: Local directory or Hub id.

    Returns:
        Loaded RADM pipeline.
    """
    config_dict, _ = RADMConfig.load_config(
        pretrained_model_name_or_path,
        return_unused_kwargs=True,
    )
    config = cast(RADMConfig, RADMConfig.from_config(config_dict))
    pipe = super().from_pretrained(pretrained_model_name_or_path, config=config)
    pipe.processor = RADMProcessor.from_pretrained(
        pretrained_model_name_or_path,
        subfolder=config.processor_subfolder,
    )
    return pipe

RADMProcessor

Bases: ProcessorMixin

Normalize RADM content payloads and decode proposal predictions.

Parameters:

Name Type Description Default
image_processor RADMImageProcessor | None

Image processor for content images.

None
config RADMConfig

RADM configuration.

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

Optional public label mapping.

None

Examples:

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

    Args:
        image_processor: Image processor for content images.
        config: RADM configuration.
        id2label: Optional public label mapping.

    Examples:
        >>> processor = RADMProcessor(config=RADMConfig(num_proposals=2))
        >>> processor.id2label[0]
        'logo'
    """

    attributes = ["image_processor"]
    image_processor_class = "RADMImageProcessor"
    config_name = "processor_config.json"

    def __init__(
        self,
        *,
        image_processor: RADMImageProcessor | None = None,
        config: RADMConfig,
        id2label: Mapping[int | str, str] | None = None,
    ) -> None:
        """Initialize processor metadata."""
        self.config = config
        self.image_processor = image_processor or RADMImageProcessor.from_config(config)
        label_source = id2label if id2label is not None else config.id2label
        self.id2label = {int(key): value for key, value in label_source.items()}
        self.chat_template = None

    def save_pretrained(
        self,
        save_directory: str | Path,
        push_to_hub: bool = False,
        **kwargs: Unpack[ProcessingKwargs],
    ) -> None:
        """Save processor metadata.

        Args:
            save_directory: Directory receiving processor files.
            push_to_hub: Accepted for ``ProcessorMixin`` compatibility.
            kwargs: Accepted for compatibility.
        """
        del push_to_hub, kwargs
        root = Path(save_directory)
        root.mkdir(parents=True, exist_ok=True)
        self._write_metadata(root)
        self.image_processor.save_pretrained(root)

    def _write_metadata(self, root: Path) -> None:
        payload = {
            "processor_class": self.__class__.__name__,
            "id2label": self.id2label,
            "config": json.loads(self.config.to_json_string()),
        }
        (root / self.config_name).write_text(
            json.dumps(payload, indent=2, sort_keys=True),
            encoding="utf-8",
        )

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | PathLike[str],
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        subfolder: str | None = None,
        **kwargs: Unpack[ProcessingKwargs],
    ) -> Self:
        """Load processor metadata from a local pipeline directory.

        Args:
            pretrained_model_name_or_path: Root path or processor subfolder.
            cache_dir: Accepted for API compatibility.
            force_download: Accepted for API compatibility.
            local_files_only: Accepted for API compatibility.
            token: Accepted for API compatibility.
            revision: Accepted for API compatibility.
            subfolder: Optional processor subfolder.
            kwargs: Ignored compatibility kwargs.

        Returns:
            Loaded RADM processor.
        """
        del cache_dir, force_download, local_files_only, token, revision, kwargs
        root = _processor_root(pretrained_model_name_or_path, subfolder)
        payload = _read_processor_metadata(root, cls.config_name)
        raw_config = payload.get("config")
        config_payload = raw_config if isinstance(raw_config, dict) else {}
        raw_id2label = payload.get("id2label")
        id2label = (
            cast(Mapping[int | str, str], raw_id2label)
            if isinstance(raw_id2label, Mapping)
            else None
        )
        config = RADMConfig.from_config(cast(dict[str, JsonValue], config_payload))
        image_processor = RADMImageProcessor.from_pretrained(root)
        return cls(
            image_processor=image_processor,
            config=cast(RADMConfig, config),
            id2label=id2label,
        )

    def __call__(
        self,
        images: ImageInput | Sequence[ImageInput] | None = None,
        *,
        content: Mapping[
            str,
            ImageInput
            | Sequence[ImageInput]
            | Float[torch.Tensor, "batch text text_dim"]
            | Float[np.ndarray, "batch text text_dim"]
            | Bool[torch.Tensor, "batch text 1"]
            | Bool[np.ndarray, "batch text 1"]
            | NestedFloatSequence
            | NestedBoolSequence
            | JsonValue,
        ]
        | None = None,
        text_features: Float[torch.Tensor, "batch text text_dim"]
        | Float[np.ndarray, "batch text text_dim"]
        | NestedFloatSequence
        | None = None,
        text_mask: Bool[torch.Tensor, "batch text 1"]
        | Bool[np.ndarray, "batch text 1"]
        | NestedBoolSequence
        | None = None,
        batch_size: int = 1,
        return_tensors: Literal["pt"] = "pt",
        **kwargs: Unpack[ProcessingKwargs],
    ) -> BatchEncoding:
        """Encode public RADM inputs.

        Args:
            images: Image or image batch.
            content: Optional content carrier with ``image`` and text features.
            text_features: Optional text feature tensor.
            text_mask: Optional text-feature mask.
            batch_size: Synthetic batch size used when images are absent.
            return_tensors: Tensor framework. Only ``pt`` is supported.
            kwargs: Ignored compatibility kwargs.

        Returns:
            Batch encoding with image and text tensors.

        Raises:
            ValueError: If no image/content image is supplied.
        """
        del kwargs
        if return_tensors != "pt":
            raise ValueError("RADMProcessor only supports return_tensors='pt'")

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

        encoded = self.image_processor.preprocess(
            cast(ImageInput | Sequence[ImageInput], resolved_images),
            return_tensors="pt",
        )
        resolved_batch = int(encoded["pixel_values"].shape[0])
        features = self._text_features(
            text_features
            if text_features is not None
            else cast(
                Float[torch.Tensor, "batch text text_dim"]
                | Float[np.ndarray, "batch text text_dim"]
                | NestedFloatSequence
                | None,
                content.get("text_features"),
            ),
            batch_size=resolved_batch,
            device=encoded["pixel_values"].device,
        )
        mask = self._text_mask(
            text_mask
            if text_mask is not None
            else cast(
                Bool[torch.Tensor, "batch text 1"]
                | Bool[np.ndarray, "batch text 1"]
                | NestedBoolSequence
                | None,
                content.get("text_mask"),
            ),
            batch_size=resolved_batch,
            text_count=features.shape[1],
            device=features.device,
        )
        encoded.update(
            {
                "text_features": features,
                "text_mask": mask,
                "batch_size": resolved_batch if resolved_batch else batch_size,
            }
        )
        return BatchEncoding(encoded)

    def validate_condition(self, condition_type: ConditionType | str) -> ConditionType:
        """Normalize and validate RADM condition mode.

        Args:
            condition_type: Public condition name or alias.

        Returns:
            ``ConditionType.content_image``.

        Raises:
            NotImplementedError: If RADM does not support the condition.
        """
        canonical = normalize_condition_type(condition_type)
        if canonical is not ConditionType.content_image:
            raise NotImplementedError(
                f"RADM does not support condition_type={canonical}"
            )

        return canonical

    def decode(
        self,
        *,
        boxes_xyxy: Float[torch.Tensor, "batch proposals 4"],
        logits: Float[torch.Tensor, "batch proposals classes"],
        class_threshold: float,
        nms_threshold: float,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_intermediates: bool = False,
        extra_intermediates: Mapping[
            str,
            str
            | int
            | float
            | bool
            | None
            | Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | list[Float[torch.Tensor, "..."]],
        ]
        | None = None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | Mapping[int, str]
            | Mapping[
                str,
                str
                | int
                | float
                | bool
                | None
                | Float[torch.Tensor, "..."]
                | Int[torch.Tensor, "..."]
                | Bool[torch.Tensor, "..."]
                | list[Float[torch.Tensor, "..."]],
            ]
            | list[Float[torch.Tensor, "..."]]
            | None,
        ]
    ):
        """Decode denoiser predictions into the common layout schema.

        Args:
            boxes_xyxy: Normalized proposal boxes.
            logits: Class logits.
            class_threshold: Confidence threshold.
            nms_threshold: NMS threshold.
            output_type: Output container selector.
            return_intermediates: Whether to include raw predictions.
            extra_intermediates: Optional debug metadata.

        Returns:
            Layout output dataclass or dictionary.

        Raises:
            ValueError: If ``output_type`` is unsupported.
        """
        selected_boxes, labels, mask, scores, keep = select_predictions(
            boxes_xyxy=boxes_xyxy,
            logits=logits,
            class_threshold=class_threshold,
            nms_threshold=nms_threshold,
        )
        intermediates = None
        if return_intermediates:
            intermediates = dict(extra_intermediates or {})
            intermediates.update(
                {
                    "raw_boxes_xyxy": boxes_xyxy.detach().cpu(),
                    "raw_logits": logits.detach().cpu(),
                    "nms_keep_indices": keep,
                }
            )

        output = LayoutGenerationOutput(
            bbox=xyxy_to_xywh_normalized(selected_boxes).detach().cpu(),
            labels=labels.detach().cpu(),
            mask=mask.detach().cpu(),
            id2label=self.id2label,
            scores=scores.detach().cpu(),
            intermediates=intermediates,
        )
        if output_type == "dataclass":
            return output

        if output_type == "dict":
            return dict(output)

        raise ValueError(f"Unsupported output_type: {output_type}")

    def _text_features(
        self,
        value: Float[torch.Tensor, "batch text text_dim"]
        | Float[np.ndarray, "batch text text_dim"]
        | NestedFloatSequence
        | None,
        *,
        batch_size: int,
        device: torch.device,
    ) -> Float[torch.Tensor, "batch text text_dim"]:
        if value is None:
            return torch.zeros(
                batch_size,
                1,
                self.config.text_feature_dim,
                dtype=torch.float32,
                device=device,
            )

        tensor = torch.as_tensor(value, dtype=torch.float32, device=device)
        if tensor.ndim == 2:
            tensor = tensor.unsqueeze(0)

        if tensor.shape[-1] != self.config.text_feature_dim:
            raise ValueError(
                f"text_features last dimension must be {self.config.text_feature_dim}"
            )

        return tensor

    def _text_mask(
        self,
        value: Bool[torch.Tensor, "batch text 1"]
        | Bool[np.ndarray, "batch text 1"]
        | NestedBoolSequence
        | None,
        *,
        batch_size: int,
        text_count: int,
        device: torch.device,
    ) -> Bool[torch.Tensor, "batch text 1"]:
        if value is None:
            return torch.ones(
                batch_size, text_count, 1, dtype=torch.bool, device=device
            )

        tensor = torch.as_tensor(value, dtype=torch.bool, device=device)
        if tensor.ndim == 2:
            tensor = tensor.unsqueeze(-1)

        return tensor

__init__

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

Initialize processor metadata.

Source code in models/radm/src/radm/processing_radm.py
53
54
55
56
57
58
59
60
61
62
63
64
65
def __init__(
    self,
    *,
    image_processor: RADMImageProcessor | None = None,
    config: RADMConfig,
    id2label: Mapping[int | str, str] | None = None,
) -> None:
    """Initialize processor metadata."""
    self.config = config
    self.image_processor = image_processor or RADMImageProcessor.from_config(config)
    label_source = id2label if id2label is not None else config.id2label
    self.id2label = {int(key): value for key, value in label_source.items()}
    self.chat_template = None

save_pretrained

save_pretrained(
    save_directory: str | Path,
    push_to_hub: bool = False,
    **kwargs: Unpack[ProcessingKwargs],
) -> None

Save processor metadata.

Parameters:

Name Type Description Default
save_directory str | Path

Directory receiving processor files.

required
push_to_hub bool

Accepted for ProcessorMixin compatibility.

False
kwargs Unpack[ProcessingKwargs]

Accepted for compatibility.

{}
Source code in models/radm/src/radm/processing_radm.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def save_pretrained(
    self,
    save_directory: str | Path,
    push_to_hub: bool = False,
    **kwargs: Unpack[ProcessingKwargs],
) -> None:
    """Save processor metadata.

    Args:
        save_directory: Directory receiving processor files.
        push_to_hub: Accepted for ``ProcessorMixin`` compatibility.
        kwargs: Accepted for compatibility.
    """
    del push_to_hub, kwargs
    root = Path(save_directory)
    root.mkdir(parents=True, exist_ok=True)
    self._write_metadata(root)
    self.image_processor.save_pretrained(root)

from_pretrained classmethod

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

Load processor metadata from a local pipeline directory.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | PathLike[str]

Root path or processor subfolder.

required
cache_dir str | PathLike[str] | None

Accepted for API compatibility.

None
force_download bool

Accepted for API compatibility.

False
local_files_only bool

Accepted for API compatibility.

False
token str | bool | None

Accepted for API compatibility.

None
revision str

Accepted for API compatibility.

'main'
subfolder str | None

Optional processor subfolder.

None
kwargs Unpack[ProcessingKwargs]

Ignored compatibility kwargs.

{}

Returns:

Type Description
Self

Loaded RADM processor.

Source code in models/radm/src/radm/processing_radm.py
 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
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    subfolder: str | None = None,
    **kwargs: Unpack[ProcessingKwargs],
) -> Self:
    """Load processor metadata from a local pipeline directory.

    Args:
        pretrained_model_name_or_path: Root path or processor subfolder.
        cache_dir: Accepted for API compatibility.
        force_download: Accepted for API compatibility.
        local_files_only: Accepted for API compatibility.
        token: Accepted for API compatibility.
        revision: Accepted for API compatibility.
        subfolder: Optional processor subfolder.
        kwargs: Ignored compatibility kwargs.

    Returns:
        Loaded RADM processor.
    """
    del cache_dir, force_download, local_files_only, token, revision, kwargs
    root = _processor_root(pretrained_model_name_or_path, subfolder)
    payload = _read_processor_metadata(root, cls.config_name)
    raw_config = payload.get("config")
    config_payload = raw_config if isinstance(raw_config, dict) else {}
    raw_id2label = payload.get("id2label")
    id2label = (
        cast(Mapping[int | str, str], raw_id2label)
        if isinstance(raw_id2label, Mapping)
        else None
    )
    config = RADMConfig.from_config(cast(dict[str, JsonValue], config_payload))
    image_processor = RADMImageProcessor.from_pretrained(root)
    return cls(
        image_processor=image_processor,
        config=cast(RADMConfig, config),
        id2label=id2label,
    )

__call__

__call__(
    images: ImageInput | Sequence[ImageInput] | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Float[Tensor, "batch text text_dim"]
        | Float[ndarray, "batch text text_dim"]
        | Bool[Tensor, "batch text 1"]
        | Bool[ndarray, "batch text 1"]
        | NestedFloatSequence
        | NestedBoolSequence
        | JsonValue,
    ]
    | None = None,
    text_features: Float[Tensor, "batch text text_dim"]
    | Float[ndarray, "batch text text_dim"]
    | NestedFloatSequence
    | None = None,
    text_mask: Bool[Tensor, "batch text 1"]
    | Bool[ndarray, "batch text 1"]
    | NestedBoolSequence
    | None = None,
    batch_size: int = 1,
    return_tensors: Literal["pt"] = "pt",
    **kwargs: Unpack[ProcessingKwargs],
) -> BatchEncoding

Encode public RADM inputs.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput] | None

Image or image batch.

None
content Mapping[str, ImageInput | Sequence[ImageInput] | Float[Tensor, 'batch text text_dim'] | Float[ndarray, 'batch text text_dim'] | Bool[Tensor, 'batch text 1'] | Bool[ndarray, 'batch text 1'] | NestedFloatSequence | NestedBoolSequence | JsonValue] | None

Optional content carrier with image and text features.

None
text_features Float[Tensor, 'batch text text_dim'] | Float[ndarray, 'batch text text_dim'] | NestedFloatSequence | None

Optional text feature tensor.

None
text_mask Bool[Tensor, 'batch text 1'] | Bool[ndarray, 'batch text 1'] | NestedBoolSequence | None

Optional text-feature mask.

None
batch_size int

Synthetic batch size used when images are absent.

1
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

'pt'
kwargs Unpack[ProcessingKwargs]

Ignored compatibility kwargs.

{}

Returns:

Type Description
BatchEncoding

Batch encoding with image and text tensors.

Raises:

Type Description
ValueError

If no image/content image is supplied.

Source code in models/radm/src/radm/processing_radm.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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
def __call__(
    self,
    images: ImageInput | Sequence[ImageInput] | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch text text_dim"]
        | Float[np.ndarray, "batch text text_dim"]
        | Bool[torch.Tensor, "batch text 1"]
        | Bool[np.ndarray, "batch text 1"]
        | NestedFloatSequence
        | NestedBoolSequence
        | JsonValue,
    ]
    | None = None,
    text_features: Float[torch.Tensor, "batch text text_dim"]
    | Float[np.ndarray, "batch text text_dim"]
    | NestedFloatSequence
    | None = None,
    text_mask: Bool[torch.Tensor, "batch text 1"]
    | Bool[np.ndarray, "batch text 1"]
    | NestedBoolSequence
    | None = None,
    batch_size: int = 1,
    return_tensors: Literal["pt"] = "pt",
    **kwargs: Unpack[ProcessingKwargs],
) -> BatchEncoding:
    """Encode public RADM inputs.

    Args:
        images: Image or image batch.
        content: Optional content carrier with ``image`` and text features.
        text_features: Optional text feature tensor.
        text_mask: Optional text-feature mask.
        batch_size: Synthetic batch size used when images are absent.
        return_tensors: Tensor framework. Only ``pt`` is supported.
        kwargs: Ignored compatibility kwargs.

    Returns:
        Batch encoding with image and text tensors.

    Raises:
        ValueError: If no image/content image is supplied.
    """
    del kwargs
    if return_tensors != "pt":
        raise ValueError("RADMProcessor only supports return_tensors='pt'")

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

    encoded = self.image_processor.preprocess(
        cast(ImageInput | Sequence[ImageInput], resolved_images),
        return_tensors="pt",
    )
    resolved_batch = int(encoded["pixel_values"].shape[0])
    features = self._text_features(
        text_features
        if text_features is not None
        else cast(
            Float[torch.Tensor, "batch text text_dim"]
            | Float[np.ndarray, "batch text text_dim"]
            | NestedFloatSequence
            | None,
            content.get("text_features"),
        ),
        batch_size=resolved_batch,
        device=encoded["pixel_values"].device,
    )
    mask = self._text_mask(
        text_mask
        if text_mask is not None
        else cast(
            Bool[torch.Tensor, "batch text 1"]
            | Bool[np.ndarray, "batch text 1"]
            | NestedBoolSequence
            | None,
            content.get("text_mask"),
        ),
        batch_size=resolved_batch,
        text_count=features.shape[1],
        device=features.device,
    )
    encoded.update(
        {
            "text_features": features,
            "text_mask": mask,
            "batch_size": resolved_batch if resolved_batch else batch_size,
        }
    )
    return BatchEncoding(encoded)

validate_condition

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

Normalize and validate RADM condition mode.

Parameters:

Name Type Description Default
condition_type ConditionType | str

Public condition name or alias.

required

Returns:

Type Description
ConditionType

ConditionType.content_image.

Raises:

Type Description
NotImplementedError

If RADM does not support the condition.

Source code in models/radm/src/radm/processing_radm.py
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
def validate_condition(self, condition_type: ConditionType | str) -> ConditionType:
    """Normalize and validate RADM condition mode.

    Args:
        condition_type: Public condition name or alias.

    Returns:
        ``ConditionType.content_image``.

    Raises:
        NotImplementedError: If RADM does not support the condition.
    """
    canonical = normalize_condition_type(condition_type)
    if canonical is not ConditionType.content_image:
        raise NotImplementedError(
            f"RADM does not support condition_type={canonical}"
        )

    return canonical

decode

decode(
    *,
    boxes_xyxy: Float[Tensor, "batch proposals 4"],
    logits: Float[Tensor, "batch proposals classes"],
    class_threshold: float,
    nms_threshold: float,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    extra_intermediates: Mapping[
        str,
        str
        | int
        | float
        | bool
        | None
        | Float[Tensor, "..."]
        | Int[Tensor, "..."]
        | Bool[Tensor, "..."]
        | list[Float[Tensor, "..."]],
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | Mapping[int, str]
        | Mapping[
            str,
            str
            | int
            | float
            | bool
            | None
            | Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | list[Float[torch.Tensor, "..."]],
        ]
        | list[Float[torch.Tensor, "..."]]
        | None,
    ]
)

Decode denoiser predictions into the common layout schema.

Parameters:

Name Type Description Default
boxes_xyxy Float[Tensor, 'batch proposals 4']

Normalized proposal boxes.

required
logits Float[Tensor, 'batch proposals classes']

Class logits.

required
class_threshold float

Confidence threshold.

required
nms_threshold float

NMS threshold.

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

Output container selector.

'dataclass'
return_intermediates bool

Whether to include raw predictions.

False
extra_intermediates Mapping[str, str | int | float | bool | None | Float[Tensor, '...'] | Int[Tensor, '...'] | Bool[Tensor, '...'] | list[Float[Tensor, '...']]] | None

Optional debug metadata.

None

Returns:

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

Layout output dataclass or dictionary.

Raises:

Type Description
ValueError

If output_type is unsupported.

Source code in models/radm/src/radm/processing_radm.py
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
def decode(
    self,
    *,
    boxes_xyxy: Float[torch.Tensor, "batch proposals 4"],
    logits: Float[torch.Tensor, "batch proposals classes"],
    class_threshold: float,
    nms_threshold: float,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    extra_intermediates: Mapping[
        str,
        str
        | int
        | float
        | bool
        | None
        | Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | list[Float[torch.Tensor, "..."]],
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "..."]
        | Int[torch.Tensor, "..."]
        | Bool[torch.Tensor, "..."]
        | Mapping[int, str]
        | Mapping[
            str,
            str
            | int
            | float
            | bool
            | None
            | Float[torch.Tensor, "..."]
            | Int[torch.Tensor, "..."]
            | Bool[torch.Tensor, "..."]
            | list[Float[torch.Tensor, "..."]],
        ]
        | list[Float[torch.Tensor, "..."]]
        | None,
    ]
):
    """Decode denoiser predictions into the common layout schema.

    Args:
        boxes_xyxy: Normalized proposal boxes.
        logits: Class logits.
        class_threshold: Confidence threshold.
        nms_threshold: NMS threshold.
        output_type: Output container selector.
        return_intermediates: Whether to include raw predictions.
        extra_intermediates: Optional debug metadata.

    Returns:
        Layout output dataclass or dictionary.

    Raises:
        ValueError: If ``output_type`` is unsupported.
    """
    selected_boxes, labels, mask, scores, keep = select_predictions(
        boxes_xyxy=boxes_xyxy,
        logits=logits,
        class_threshold=class_threshold,
        nms_threshold=nms_threshold,
    )
    intermediates = None
    if return_intermediates:
        intermediates = dict(extra_intermediates or {})
        intermediates.update(
            {
                "raw_boxes_xyxy": boxes_xyxy.detach().cpu(),
                "raw_logits": logits.detach().cpu(),
                "nms_keep_indices": keep,
            }
        )

    output = LayoutGenerationOutput(
        bbox=xyxy_to_xywh_normalized(selected_boxes).detach().cpu(),
        labels=labels.detach().cpu(),
        mask=mask.detach().cpu(),
        id2label=self.id2label,
        scores=scores.detach().cpu(),
        intermediates=intermediates,
    )
    if output_type == "dataclass":
        return output

    if output_type == "dict":
        return dict(output)

    raise ValueError(f"Unsupported output_type: {output_type}")

RADMScheduler

Bases: SchedulerMixin, ConfigMixin

DDIM-style scheduler for RADM's scaled proposal latents.

Parameters:

Name Type Description Default
num_train_timesteps int

Number of training diffusion timesteps.

1000
num_inference_steps int

Default inference step count.

50
beta_schedule Literal['cosine']

Schedule name. Only "cosine" is supported.

'cosine'
eta float

DDIM stochasticity parameter.

1.0
prediction_type Literal['sample']

Model prediction type.

'sample'
snr_scale float

Scaling applied to the latent at the model boundary.

2.0

Examples:

>>> scheduler = RADMScheduler(num_train_timesteps=10, num_inference_steps=3)
>>> scheduler.set_timesteps(3)
>>> scheduler.timesteps.tolist()
[9, 5, 2]
Source code in models/radm/src/radm/scheduling_radm.py
 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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
class RADMScheduler(SchedulerMixin, ConfigMixin):
    """DDIM-style scheduler for RADM's scaled proposal latents.

    Args:
        num_train_timesteps: Number of training diffusion timesteps.
        num_inference_steps: Default inference step count.
        beta_schedule: Schedule name. Only ``"cosine"`` is supported.
        eta: DDIM stochasticity parameter.
        prediction_type: Model prediction type.
        snr_scale: Scaling applied to the latent at the model boundary.

    Examples:
        >>> scheduler = RADMScheduler(num_train_timesteps=10, num_inference_steps=3)
        >>> scheduler.set_timesteps(3)
        >>> scheduler.timesteps.tolist()
        [9, 5, 2]
    """

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

    @register_to_config
    def __init__(
        self,
        num_train_timesteps: int = 1000,
        num_inference_steps: int = 50,
        beta_schedule: Literal["cosine"] = "cosine",
        eta: float = 1.0,
        prediction_type: Literal["sample"] = "sample",
        snr_scale: float = 2.0,
    ) -> None:
        """Initialize RADM scheduler metadata."""
        if beta_schedule != "cosine":
            raise ValueError("RADM only supports beta_schedule='cosine'")

        if prediction_type != "sample":
            raise ValueError("RADM only supports prediction_type='sample'")

        self.num_train_timesteps = int(num_train_timesteps)
        self.num_inference_steps = int(num_inference_steps)
        self.eta = float(eta)
        self.snr_scale = float(snr_scale)
        betas = cosine_beta_schedule(self.num_train_timesteps)
        alphas = 1.0 - betas
        alphas_cumprod_prev = F.pad(
            torch.cumprod(alphas, dim=0)[:-1], (1, 0), value=1.0
        )
        self.betas = betas
        self.alphas_cumprod = torch.cumprod(alphas, dim=0)
        self.alphas_cumprod_prev = alphas_cumprod_prev
        self.sqrt_alphas_cumprod = torch.sqrt(self.alphas_cumprod)
        self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1.0 - self.alphas_cumprod)
        self.log_one_minus_alphas_cumprod = torch.log(1.0 - self.alphas_cumprod)
        self.sqrt_recip_alphas_cumprod = torch.sqrt(1.0 / self.alphas_cumprod)
        self.sqrt_recipm1_alphas_cumprod = torch.sqrt(1.0 / self.alphas_cumprod - 1.0)
        posterior_variance = (
            betas * (1.0 - alphas_cumprod_prev) / (1.0 - self.alphas_cumprod)
        )
        self.posterior_variance = posterior_variance
        self.posterior_log_variance_clipped = torch.log(
            posterior_variance.clamp(min=1e-20)
        )
        self.posterior_mean_coef1 = (
            betas * torch.sqrt(alphas_cumprod_prev) / (1.0 - self.alphas_cumprod)
        )
        self.posterior_mean_coef2 = (
            (1.0 - alphas_cumprod_prev)
            * torch.sqrt(alphas)
            / (1.0 - self.alphas_cumprod)
        )
        self.timesteps = torch.arange(self.num_train_timesteps - 1, -1, -1)

    def set_timesteps(
        self,
        num_inference_steps: int | None = None,
        *,
        device: torch.device | str | None = None,
    ) -> None:
        """Set reversed inference timesteps.

        Args:
            num_inference_steps: Optional step count.
            device: Optional target device.
        """
        steps = int(num_inference_steps or self.config.num_inference_steps)
        raw = torch.linspace(-1, self.config.num_train_timesteps - 1, steps + 1)
        self.timesteps = raw.long().flip(0)[:-1].to(device)
        self.num_inference_steps = steps

    def sample_initial_proposals(
        self,
        *,
        batch_size: int,
        num_proposals: int,
        generator: torch.Generator | None = None,
        device: torch.device | str | None = None,
        dtype: torch.dtype = torch.float32,
    ) -> Float[torch.Tensor, "batch proposals 4"]:
        """Sample the source-effective unbounded Gaussian proposal latents.

        Args:
            batch_size: Number of examples.
            num_proposals: Number of proposal boxes.
            generator: Optional random generator.
            device: Output device.
            dtype: Output dtype.

        Returns:
            Raw Gaussian proposal latents in source ``cxcywh`` coordinates.
        """
        noise = randn(
            batch_size,
            num_proposals,
            4,
            generator=generator,
            device=device or "cpu",
            dtype=dtype,
        )
        return noise

    def latent_to_normalized_boxes(
        self,
        sample: Float[torch.Tensor, "batch proposals 4"],
    ) -> Float[torch.Tensor, "batch proposals 4"]:
        """Convert a scaled source latent to normalized ``xyxy`` boxes."""
        scale = sample.new_tensor(self.snr_scale)
        cxcywh = (sample.clamp(-scale, scale) / scale + 1.0) * 0.5
        center_x, center_y, width, height = cxcywh.unbind(dim=-1)
        return torch.stack(
            (
                center_x - 0.5 * width,
                center_y - 0.5 * height,
                center_x + 0.5 * width,
                center_y + 0.5 * height,
            ),
            dim=-1,
        )

    def normalized_boxes_to_latent(
        self,
        boxes_xyxy: Float[torch.Tensor, "batch proposals 4"],
    ) -> Float[torch.Tensor, "batch proposals 4"]:
        """Convert normalized ``xyxy`` model output to a scaled source latent."""
        left, top, right, bottom = boxes_xyxy.unbind(dim=-1)
        cxcywh = torch.stack(
            (
                (left + right) * 0.5,
                (top + bottom) * 0.5,
                right - left,
                bottom - top,
            ),
            dim=-1,
        )
        scale = boxes_xyxy.new_tensor(self.snr_scale)
        return ((cxcywh * 2.0 - 1.0) * scale).clamp(-scale, scale)

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

    def predict_noise_from_start(
        self,
        sample: Float[torch.Tensor, "batch proposals 4"],
        timestep: Int[torch.Tensor, "batch"] | Int[torch.Tensor, ""] | int,
        pred_original_sample: Float[torch.Tensor, "batch proposals 4"],
    ) -> Float[torch.Tensor, "batch proposals 4"]:
        """Return the noise implied by ``x_t`` and predicted ``x_0``.

        Args:
            sample: Current latent sample.
            timestep: Training timestep indices.
            pred_original_sample: Predicted denoised sample.

        Returns:
            Predicted noise tensor.
        """
        t = _as_batch_timesteps(timestep, sample.shape[0], sample.device)
        sqrt_recip = _extract(self.sqrt_recip_alphas_cumprod, t, sample.shape).to(
            device=sample.device, dtype=sample.dtype
        )
        sqrt_recipm1 = _extract(self.sqrt_recipm1_alphas_cumprod, t, sample.shape).to(
            device=sample.device, dtype=sample.dtype
        )
        return (sqrt_recip * sample - pred_original_sample) / sqrt_recipm1

    def q_sample(
        self,
        x_start: Float[torch.Tensor, "batch proposals 4"],
        timestep: Int[torch.Tensor, "batch"] | Int[torch.Tensor, ""] | int,
        noise: Float[torch.Tensor, "batch proposals 4"] | None = None,
    ) -> Float[torch.Tensor, "batch proposals 4"]:
        """Diffuse ``x_start`` to ``x_t`` with the RADM forward process.

        Args:
            x_start: Initial clean sample.
            timestep: Training timestep indices.
            noise: Optional fixed noise tensor.

        Returns:
            Noised sample at ``timestep``.
        """
        if noise is None:
            noise = torch.randn_like(x_start)

        t = _as_batch_timesteps(timestep, x_start.shape[0], x_start.device)
        sqrt_alpha = _extract(self.sqrt_alphas_cumprod, t, x_start.shape).to(
            device=x_start.device, dtype=x_start.dtype
        )
        sqrt_one_minus = _extract(
            self.sqrt_one_minus_alphas_cumprod, t, x_start.shape
        ).to(device=x_start.device, dtype=x_start.dtype)
        return sqrt_alpha * x_start + sqrt_one_minus * noise

    @overload
    def step(
        self,
        model_output: Float[torch.Tensor, "batch proposals 4"],
        timestep: Int[torch.Tensor, ""] | int,
        sample: Float[torch.Tensor, "batch proposals 4"],
        *,
        generator: torch.Generator | None = None,
        return_dict: Literal[True] = True,
    ) -> RADMSchedulerOutput: ...

    @overload
    def step(
        self,
        model_output: Float[torch.Tensor, "batch proposals 4"],
        timestep: Int[torch.Tensor, ""] | int,
        sample: Float[torch.Tensor, "batch proposals 4"],
        *,
        generator: torch.Generator | None = None,
        return_dict: Literal[False],
    ) -> tuple[
        Float[torch.Tensor, "batch proposals 4"],
        Float[torch.Tensor, "batch proposals 4"],
    ]: ...

    def step(
        self,
        model_output: Float[torch.Tensor, "batch proposals 4"],
        timestep: Int[torch.Tensor, ""] | int,
        sample: Float[torch.Tensor, "batch proposals 4"],
        *,
        generator: torch.Generator | None = None,
        return_dict: bool = True,
    ) -> (
        RADMSchedulerOutput
        | tuple[
            Float[torch.Tensor, "batch proposals 4"],
            Float[torch.Tensor, "batch proposals 4"],
        ]
    ):
        """Advance one reverse-diffusion step.

        Args:
            model_output: Predicted denoised sample.
            timestep: Current training timestep.
            sample: Current proposal sample.
            generator: Optional generator for DDIM noise.
            return_dict: Whether to return a dataclass.

        Returns:
            Scheduler output dataclass or tuple.
        """
        timestep_i = int(torch.as_tensor(timestep).item())
        prev_timestep = _previous_timestep(self.timesteps, timestep_i)
        if prev_timestep < 0:
            prev_sample = model_output
            noise = None
        else:
            alpha_prod_t = self.alphas_cumprod[timestep_i].to(
                device=model_output.device, dtype=model_output.dtype
            )
            alpha_prod_t_prev = self.alphas_cumprod[prev_timestep].to(
                device=model_output.device, dtype=model_output.dtype
            )
            pred_noise = self.predict_noise_from_start(sample, timestep_i, model_output)
            sigma = (
                float(self.config.eta)
                * (
                    (1.0 - alpha_prod_t / alpha_prod_t_prev)
                    * (1.0 - alpha_prod_t_prev)
                    / (1.0 - alpha_prod_t)
                ).sqrt()
            )
            direction = (1.0 - alpha_prod_t_prev - sigma**2).sqrt() * pred_noise
            noise = None
            prev_sample = alpha_prod_t_prev.sqrt() * model_output + direction
            if float(self.config.eta):
                noise = randn(
                    tuple(model_output.shape),
                    generator=generator,
                    device=model_output.device,
                    dtype=model_output.dtype,
                )
                prev_sample = prev_sample + sigma * noise

        if not return_dict:
            return (prev_sample, model_output)

        return RADMSchedulerOutput(
            prev_sample=prev_sample,
            pred_original_sample=model_output,
            noise=noise,
        )

    def posterior_mean_variance(
        self,
        x_start: Float[torch.Tensor, "batch proposals 4"],
        sample: Float[torch.Tensor, "batch proposals 4"],
        timestep: Int[torch.Tensor, "batch"] | Int[torch.Tensor, ""] | int,
    ) -> tuple[
        Float[torch.Tensor, "batch proposals 4"],
        Float[torch.Tensor, "batch proposals 4"],
        Float[torch.Tensor, "batch proposals 4"],
    ]:
        """Return RADM posterior mean, variance, and clipped log variance."""
        t = _as_batch_timesteps(timestep, sample.shape[0], sample.device)
        mean = (
            _extract(self.posterior_mean_coef1, t, sample.shape).to(
                device=sample.device, dtype=sample.dtype
            )
            * x_start
            + _extract(self.posterior_mean_coef2, t, sample.shape).to(
                device=sample.device, dtype=sample.dtype
            )
            * sample
        )
        variance = _extract(self.posterior_variance, t, sample.shape).to(
            device=sample.device, dtype=sample.dtype
        )
        log_variance = _extract(
            self.posterior_log_variance_clipped, t, sample.shape
        ).to(device=sample.device, dtype=sample.dtype)
        return mean, variance, log_variance

__init__

__init__(
    num_train_timesteps: int = 1000,
    num_inference_steps: int = 50,
    beta_schedule: Literal["cosine"] = "cosine",
    eta: float = 1.0,
    prediction_type: Literal["sample"] = "sample",
    snr_scale: float = 2.0,
) -> None

Initialize RADM scheduler metadata.

Source code in models/radm/src/radm/scheduling_radm.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@register_to_config
def __init__(
    self,
    num_train_timesteps: int = 1000,
    num_inference_steps: int = 50,
    beta_schedule: Literal["cosine"] = "cosine",
    eta: float = 1.0,
    prediction_type: Literal["sample"] = "sample",
    snr_scale: float = 2.0,
) -> None:
    """Initialize RADM scheduler metadata."""
    if beta_schedule != "cosine":
        raise ValueError("RADM only supports beta_schedule='cosine'")

    if prediction_type != "sample":
        raise ValueError("RADM only supports prediction_type='sample'")

    self.num_train_timesteps = int(num_train_timesteps)
    self.num_inference_steps = int(num_inference_steps)
    self.eta = float(eta)
    self.snr_scale = float(snr_scale)
    betas = cosine_beta_schedule(self.num_train_timesteps)
    alphas = 1.0 - betas
    alphas_cumprod_prev = F.pad(
        torch.cumprod(alphas, dim=0)[:-1], (1, 0), value=1.0
    )
    self.betas = betas
    self.alphas_cumprod = torch.cumprod(alphas, dim=0)
    self.alphas_cumprod_prev = alphas_cumprod_prev
    self.sqrt_alphas_cumprod = torch.sqrt(self.alphas_cumprod)
    self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1.0 - self.alphas_cumprod)
    self.log_one_minus_alphas_cumprod = torch.log(1.0 - self.alphas_cumprod)
    self.sqrt_recip_alphas_cumprod = torch.sqrt(1.0 / self.alphas_cumprod)
    self.sqrt_recipm1_alphas_cumprod = torch.sqrt(1.0 / self.alphas_cumprod - 1.0)
    posterior_variance = (
        betas * (1.0 - alphas_cumprod_prev) / (1.0 - self.alphas_cumprod)
    )
    self.posterior_variance = posterior_variance
    self.posterior_log_variance_clipped = torch.log(
        posterior_variance.clamp(min=1e-20)
    )
    self.posterior_mean_coef1 = (
        betas * torch.sqrt(alphas_cumprod_prev) / (1.0 - self.alphas_cumprod)
    )
    self.posterior_mean_coef2 = (
        (1.0 - alphas_cumprod_prev)
        * torch.sqrt(alphas)
        / (1.0 - self.alphas_cumprod)
    )
    self.timesteps = torch.arange(self.num_train_timesteps - 1, -1, -1)

set_timesteps

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

Set reversed inference timesteps.

Parameters:

Name Type Description Default
num_inference_steps int | None

Optional step count.

None
device device | str | None

Optional target device.

None
Source code in models/radm/src/radm/scheduling_radm.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def set_timesteps(
    self,
    num_inference_steps: int | None = None,
    *,
    device: torch.device | str | None = None,
) -> None:
    """Set reversed inference timesteps.

    Args:
        num_inference_steps: Optional step count.
        device: Optional target device.
    """
    steps = int(num_inference_steps or self.config.num_inference_steps)
    raw = torch.linspace(-1, self.config.num_train_timesteps - 1, steps + 1)
    self.timesteps = raw.long().flip(0)[:-1].to(device)
    self.num_inference_steps = steps

sample_initial_proposals

sample_initial_proposals(
    *,
    batch_size: int,
    num_proposals: int,
    generator: Generator | None = None,
    device: device | str | None = None,
    dtype: dtype = torch.float32,
) -> Float[torch.Tensor, "batch proposals 4"]

Sample the source-effective unbounded Gaussian proposal latents.

Parameters:

Name Type Description Default
batch_size int

Number of examples.

required
num_proposals int

Number of proposal boxes.

required
generator Generator | None

Optional random generator.

None
device device | str | None

Output device.

None
dtype dtype

Output dtype.

float32

Returns:

Type Description
Float[Tensor, 'batch proposals 4']

Raw Gaussian proposal latents in source cxcywh coordinates.

Source code in models/radm/src/radm/scheduling_radm.py
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
def sample_initial_proposals(
    self,
    *,
    batch_size: int,
    num_proposals: int,
    generator: torch.Generator | None = None,
    device: torch.device | str | None = None,
    dtype: torch.dtype = torch.float32,
) -> Float[torch.Tensor, "batch proposals 4"]:
    """Sample the source-effective unbounded Gaussian proposal latents.

    Args:
        batch_size: Number of examples.
        num_proposals: Number of proposal boxes.
        generator: Optional random generator.
        device: Output device.
        dtype: Output dtype.

    Returns:
        Raw Gaussian proposal latents in source ``cxcywh`` coordinates.
    """
    noise = randn(
        batch_size,
        num_proposals,
        4,
        generator=generator,
        device=device or "cpu",
        dtype=dtype,
    )
    return noise

latent_to_normalized_boxes

latent_to_normalized_boxes(
    sample: Float[Tensor, "batch proposals 4"],
) -> Float[torch.Tensor, "batch proposals 4"]

Convert a scaled source latent to normalized xyxy boxes.

Source code in models/radm/src/radm/scheduling_radm.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def latent_to_normalized_boxes(
    self,
    sample: Float[torch.Tensor, "batch proposals 4"],
) -> Float[torch.Tensor, "batch proposals 4"]:
    """Convert a scaled source latent to normalized ``xyxy`` boxes."""
    scale = sample.new_tensor(self.snr_scale)
    cxcywh = (sample.clamp(-scale, scale) / scale + 1.0) * 0.5
    center_x, center_y, width, height = cxcywh.unbind(dim=-1)
    return torch.stack(
        (
            center_x - 0.5 * width,
            center_y - 0.5 * height,
            center_x + 0.5 * width,
            center_y + 0.5 * height,
        ),
        dim=-1,
    )

normalized_boxes_to_latent

normalized_boxes_to_latent(
    boxes_xyxy: Float[Tensor, "batch proposals 4"],
) -> Float[torch.Tensor, "batch proposals 4"]

Convert normalized xyxy model output to a scaled source latent.

Source code in models/radm/src/radm/scheduling_radm.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
def normalized_boxes_to_latent(
    self,
    boxes_xyxy: Float[torch.Tensor, "batch proposals 4"],
) -> Float[torch.Tensor, "batch proposals 4"]:
    """Convert normalized ``xyxy`` model output to a scaled source latent."""
    left, top, right, bottom = boxes_xyxy.unbind(dim=-1)
    cxcywh = torch.stack(
        (
            (left + right) * 0.5,
            (top + bottom) * 0.5,
            right - left,
            bottom - top,
        ),
        dim=-1,
    )
    scale = boxes_xyxy.new_tensor(self.snr_scale)
    return ((cxcywh * 2.0 - 1.0) * scale).clamp(-scale, scale)

scale_model_input

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

Return sample unchanged for Diffusers scheduler compatibility.

Source code in models/radm/src/radm/scheduling_radm.py
182
183
184
185
186
187
188
189
def scale_model_input(
    self,
    sample: Float[torch.Tensor, "batch proposals 4"],
    timestep: Float[torch.Tensor, ""] | float,
) -> Float[torch.Tensor, "batch proposals 4"]:
    """Return sample unchanged for Diffusers scheduler compatibility."""
    del timestep
    return sample

predict_noise_from_start

predict_noise_from_start(
    sample: Float[Tensor, "batch proposals 4"],
    timestep: Int[Tensor, "batch"] | Int[Tensor, ""] | int,
    pred_original_sample: Float[
        Tensor, "batch proposals 4"
    ],
) -> Float[torch.Tensor, "batch proposals 4"]

Return the noise implied by x_t and predicted x_0.

Parameters:

Name Type Description Default
sample Float[Tensor, 'batch proposals 4']

Current latent sample.

required
timestep Int[Tensor, 'batch'] | Int[Tensor, ''] | int

Training timestep indices.

required
pred_original_sample Float[Tensor, 'batch proposals 4']

Predicted denoised sample.

required

Returns:

Type Description
Float[Tensor, 'batch proposals 4']

Predicted noise tensor.

Source code in models/radm/src/radm/scheduling_radm.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
def predict_noise_from_start(
    self,
    sample: Float[torch.Tensor, "batch proposals 4"],
    timestep: Int[torch.Tensor, "batch"] | Int[torch.Tensor, ""] | int,
    pred_original_sample: Float[torch.Tensor, "batch proposals 4"],
) -> Float[torch.Tensor, "batch proposals 4"]:
    """Return the noise implied by ``x_t`` and predicted ``x_0``.

    Args:
        sample: Current latent sample.
        timestep: Training timestep indices.
        pred_original_sample: Predicted denoised sample.

    Returns:
        Predicted noise tensor.
    """
    t = _as_batch_timesteps(timestep, sample.shape[0], sample.device)
    sqrt_recip = _extract(self.sqrt_recip_alphas_cumprod, t, sample.shape).to(
        device=sample.device, dtype=sample.dtype
    )
    sqrt_recipm1 = _extract(self.sqrt_recipm1_alphas_cumprod, t, sample.shape).to(
        device=sample.device, dtype=sample.dtype
    )
    return (sqrt_recip * sample - pred_original_sample) / sqrt_recipm1

q_sample

q_sample(
    x_start: Float[Tensor, "batch proposals 4"],
    timestep: Int[Tensor, "batch"] | Int[Tensor, ""] | int,
    noise: Float[Tensor, "batch proposals 4"] | None = None,
) -> Float[torch.Tensor, "batch proposals 4"]

Diffuse x_start to x_t with the RADM forward process.

Parameters:

Name Type Description Default
x_start Float[Tensor, 'batch proposals 4']

Initial clean sample.

required
timestep Int[Tensor, 'batch'] | Int[Tensor, ''] | int

Training timestep indices.

required
noise Float[Tensor, 'batch proposals 4'] | None

Optional fixed noise tensor.

None

Returns:

Type Description
Float[Tensor, 'batch proposals 4']

Noised sample at timestep.

Source code in models/radm/src/radm/scheduling_radm.py
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
def q_sample(
    self,
    x_start: Float[torch.Tensor, "batch proposals 4"],
    timestep: Int[torch.Tensor, "batch"] | Int[torch.Tensor, ""] | int,
    noise: Float[torch.Tensor, "batch proposals 4"] | None = None,
) -> Float[torch.Tensor, "batch proposals 4"]:
    """Diffuse ``x_start`` to ``x_t`` with the RADM forward process.

    Args:
        x_start: Initial clean sample.
        timestep: Training timestep indices.
        noise: Optional fixed noise tensor.

    Returns:
        Noised sample at ``timestep``.
    """
    if noise is None:
        noise = torch.randn_like(x_start)

    t = _as_batch_timesteps(timestep, x_start.shape[0], x_start.device)
    sqrt_alpha = _extract(self.sqrt_alphas_cumprod, t, x_start.shape).to(
        device=x_start.device, dtype=x_start.dtype
    )
    sqrt_one_minus = _extract(
        self.sqrt_one_minus_alphas_cumprod, t, x_start.shape
    ).to(device=x_start.device, dtype=x_start.dtype)
    return sqrt_alpha * x_start + sqrt_one_minus * noise

step

step(
    model_output: Float[Tensor, "batch proposals 4"],
    timestep: Int[Tensor, ""] | int,
    sample: Float[Tensor, "batch proposals 4"],
    *,
    generator: Generator | None = None,
    return_dict: Literal[True] = True,
) -> RADMSchedulerOutput
step(
    model_output: Float[Tensor, "batch proposals 4"],
    timestep: Int[Tensor, ""] | int,
    sample: Float[Tensor, "batch proposals 4"],
    *,
    generator: Generator | None = None,
    return_dict: Literal[False],
) -> tuple[
    Float[torch.Tensor, "batch proposals 4"],
    Float[torch.Tensor, "batch proposals 4"],
]
step(
    model_output: Float[Tensor, "batch proposals 4"],
    timestep: Int[Tensor, ""] | int,
    sample: Float[Tensor, "batch proposals 4"],
    *,
    generator: Generator | None = None,
    return_dict: bool = True,
) -> (
    RADMSchedulerOutput
    | tuple[
        Float[torch.Tensor, "batch proposals 4"],
        Float[torch.Tensor, "batch proposals 4"],
    ]
)

Advance one reverse-diffusion step.

Parameters:

Name Type Description Default
model_output Float[Tensor, 'batch proposals 4']

Predicted denoised sample.

required
timestep Int[Tensor, ''] | int

Current training timestep.

required
sample Float[Tensor, 'batch proposals 4']

Current proposal sample.

required
generator Generator | None

Optional generator for DDIM noise.

None
return_dict bool

Whether to return a dataclass.

True

Returns:

Type Description
RADMSchedulerOutput | tuple[Float[Tensor, 'batch proposals 4'], Float[Tensor, 'batch proposals 4']]

Scheduler output dataclass or tuple.

Source code in models/radm/src/radm/scheduling_radm.py
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
def step(
    self,
    model_output: Float[torch.Tensor, "batch proposals 4"],
    timestep: Int[torch.Tensor, ""] | int,
    sample: Float[torch.Tensor, "batch proposals 4"],
    *,
    generator: torch.Generator | None = None,
    return_dict: bool = True,
) -> (
    RADMSchedulerOutput
    | tuple[
        Float[torch.Tensor, "batch proposals 4"],
        Float[torch.Tensor, "batch proposals 4"],
    ]
):
    """Advance one reverse-diffusion step.

    Args:
        model_output: Predicted denoised sample.
        timestep: Current training timestep.
        sample: Current proposal sample.
        generator: Optional generator for DDIM noise.
        return_dict: Whether to return a dataclass.

    Returns:
        Scheduler output dataclass or tuple.
    """
    timestep_i = int(torch.as_tensor(timestep).item())
    prev_timestep = _previous_timestep(self.timesteps, timestep_i)
    if prev_timestep < 0:
        prev_sample = model_output
        noise = None
    else:
        alpha_prod_t = self.alphas_cumprod[timestep_i].to(
            device=model_output.device, dtype=model_output.dtype
        )
        alpha_prod_t_prev = self.alphas_cumprod[prev_timestep].to(
            device=model_output.device, dtype=model_output.dtype
        )
        pred_noise = self.predict_noise_from_start(sample, timestep_i, model_output)
        sigma = (
            float(self.config.eta)
            * (
                (1.0 - alpha_prod_t / alpha_prod_t_prev)
                * (1.0 - alpha_prod_t_prev)
                / (1.0 - alpha_prod_t)
            ).sqrt()
        )
        direction = (1.0 - alpha_prod_t_prev - sigma**2).sqrt() * pred_noise
        noise = None
        prev_sample = alpha_prod_t_prev.sqrt() * model_output + direction
        if float(self.config.eta):
            noise = randn(
                tuple(model_output.shape),
                generator=generator,
                device=model_output.device,
                dtype=model_output.dtype,
            )
            prev_sample = prev_sample + sigma * noise

    if not return_dict:
        return (prev_sample, model_output)

    return RADMSchedulerOutput(
        prev_sample=prev_sample,
        pred_original_sample=model_output,
        noise=noise,
    )

posterior_mean_variance

posterior_mean_variance(
    x_start: Float[Tensor, "batch proposals 4"],
    sample: Float[Tensor, "batch proposals 4"],
    timestep: Int[Tensor, "batch"] | Int[Tensor, ""] | int,
) -> tuple[
    Float[torch.Tensor, "batch proposals 4"],
    Float[torch.Tensor, "batch proposals 4"],
    Float[torch.Tensor, "batch proposals 4"],
]

Return RADM posterior mean, variance, and clipped log variance.

Source code in models/radm/src/radm/scheduling_radm.py
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
def posterior_mean_variance(
    self,
    x_start: Float[torch.Tensor, "batch proposals 4"],
    sample: Float[torch.Tensor, "batch proposals 4"],
    timestep: Int[torch.Tensor, "batch"] | Int[torch.Tensor, ""] | int,
) -> tuple[
    Float[torch.Tensor, "batch proposals 4"],
    Float[torch.Tensor, "batch proposals 4"],
    Float[torch.Tensor, "batch proposals 4"],
]:
    """Return RADM posterior mean, variance, and clipped log variance."""
    t = _as_batch_timesteps(timestep, sample.shape[0], sample.device)
    mean = (
        _extract(self.posterior_mean_coef1, t, sample.shape).to(
            device=sample.device, dtype=sample.dtype
        )
        * x_start
        + _extract(self.posterior_mean_coef2, t, sample.shape).to(
            device=sample.device, dtype=sample.dtype
        )
        * sample
    )
    variance = _extract(self.posterior_variance, t, sample.shape).to(
        device=sample.device, dtype=sample.dtype
    )
    log_variance = _extract(
        self.posterior_log_variance_clipped, t, sample.shape
    ).to(device=sample.device, dtype=sample.dtype)
    return mean, variance, log_variance

RADMSchedulerOutput dataclass

Bases: BaseOutput

Output of one RADM scheduler step.

Source code in models/radm/src/radm/scheduling_radm.py
17
18
19
20
21
22
23
@dataclass
class RADMSchedulerOutput(BaseOutput):
    """Output of one RADM scheduler step."""

    prev_sample: Float[torch.Tensor, "batch proposals 4"]
    pred_original_sample: Float[torch.Tensor, "batch proposals 4"]
    noise: Float[torch.Tensor, "batch proposals 4"] | None = None

default_id2label

default_id2label(
    dataset_name: DatasetName | str = DatasetName.cgl,
    *,
    label_mode: RADMLabelMode | str = RADMLabelMode.english,
) -> dict[int, str]

Return the public RADM label mapping.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Poster dataset name or alias.

cgl
label_mode RADMLabelMode | str

"english" uses shared CGL aliases; "original" keeps the checkpoint label spellings.

english

Returns:

Type Description
dict[int, str]

Integer label ids mapped to display names.

Raises:

Type Description
ValueError

If the dataset or label mode is unsupported.

Examples:

>>> default_id2label("cgl")[0]
'logo'
>>> default_id2label("cgl", label_mode="original")[1]
'文字'
Source code in models/radm/src/radm/configuration_radm.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def default_id2label(
    dataset_name: DatasetName | str = DatasetName.cgl,
    *,
    label_mode: RADMLabelMode | str = RADMLabelMode.english,
) -> dict[int, str]:
    """Return the public RADM label mapping.

    Args:
        dataset_name: Poster dataset name or alias.
        label_mode: ``"english"`` uses shared CGL aliases; ``"original"`` keeps
            the checkpoint label spellings.

    Returns:
        Integer label ids mapped to display names.

    Raises:
        ValueError: If the dataset or label mode is unsupported.

    Examples:
        >>> default_id2label("cgl")[0]
        'logo'
        >>> default_id2label("cgl", label_mode="original")[1]
        '文字'
    """
    dataset = normalize_dataset_name(dataset_name)
    mode = RADMLabelMode(label_mode)
    if dataset not in {DatasetName.cgl, DatasetName.cgl_v2}:
        raise ValueError(f"Unsupported RADM dataset_name: {dataset_name}")

    if mode is RADMLabelMode.english:
        return id2label_for_dataset(DatasetName.cgl)

    if mode is RADMLabelMode.original:
        return dict(enumerate(RADM_ORIGINAL_CGL_LABELS))

    raise ValueError(f"Unsupported RADM label_mode: {label_mode}")

evaluate_checkpoint

evaluate_checkpoint(
    checkpoint_path: str | Path,
    *,
    config: RADMConfig,
    effective: "RADMEffectiveConfig",
    data_module: "RADMDataModule",
    output_dir: str | Path | None = None,
    device: str | device = "cpu",
    seed: int = 1,
    num_inference_steps: int | None = None,
    class_threshold: float = 0.25,
    nms_threshold: float = 0.15,
    max_samples: int | None = None,
) -> CocoEvaluationReport

Load a Lightning checkpoint and evaluate its CGL test predictions.

Source code in models/radm/src/radm/evaluation.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
def evaluate_checkpoint(
    checkpoint_path: str | Path,
    *,
    config: RADMConfig,
    effective: "RADMEffectiveConfig",
    data_module: "RADMDataModule",
    output_dir: str | Path | None = None,
    device: str | torch.device = "cpu",
    seed: int = 1,
    num_inference_steps: int | None = None,
    class_threshold: float = 0.25,
    nms_threshold: float = 0.15,
    max_samples: int | None = None,
) -> CocoEvaluationReport:
    """Load a Lightning checkpoint and evaluate its CGL test predictions."""
    from .training.lightning_module import RADMTrainingModule

    torch.manual_seed(seed)
    module = RADMTrainingModule.load_from_checkpoint(
        checkpoint_path,
        config=config,
        effective=effective,
        map_location=device,
    )
    module.to(device)
    module.eval()
    module_model = module.model
    data_module.setup("test")
    test_loader = data_module.test_dataloader()
    test_dataset = data_module.test_dataset
    if test_loader is None or test_dataset is None:
        raise ValueError("test annotations, images, and text features are required")

    steps = int(num_inference_steps or effective.sample_step)
    scheduler = RADMScheduler(
        num_train_timesteps=config.num_train_timesteps,
        num_inference_steps=steps,
        eta=1.0,
        snr_scale=config.snr_scale,
    )
    generator = None
    predictions: list[CocoPrediction] = []
    offset = 0
    with torch.no_grad():
        for batch in test_loader:
            remaining = None if max_samples is None else max_samples - offset
            if remaining is not None and remaining <= 0:
                break

            current_batch = int(batch["images"].shape[0])
            if remaining is not None and remaining < current_batch:
                raise ValueError("max_samples must align with the test batch size")

            batch_device = {
                key: value.to(device) if isinstance(value, torch.Tensor) else value
                for key, value in batch.items()
            }
            source_image_scales = batch_device["image_scales"][:, [1, 0, 1, 0]]
            batch_size = current_batch
            sample = scheduler.sample_initial_proposals(
                batch_size=batch_size,
                num_proposals=config.num_proposals,
                generator=generator,
                device=device,
                dtype=batch_device["images"].dtype,
            )
            scheduler.set_timesteps(steps, device=device)
            logits = sample.new_zeros(
                batch_size, config.num_proposals, config.num_classes
            )
            denoised = None
            for timestep in scheduler.timesteps:
                normalized_sample = scheduler.latent_to_normalized_boxes(sample)
                timestep_batch = torch.full(
                    (batch_size,),
                    int(timestep.item()),
                    device=device,
                    dtype=torch.long,
                )
                denoised = module_model(
                    boxes_xyxy=normalized_sample,
                    timesteps=timestep_batch,
                    text_features=batch_device["text_features"],
                    text_mask=batch_device["text_mask"],
                    images=batch_device["images"],
                    image_scales=source_image_scales,
                )
                logits = denoised.logits
                sample = scheduler.step(
                    scheduler.normalized_boxes_to_latent(denoised.pred_original_sample),
                    timestep,
                    sample,
                    generator=generator,
                ).prev_sample

            if denoised is None:
                raise RuntimeError("the denoiser produced no inference output")

            if denoised.auxiliary_boxes_absolute_xyxy is None:
                raise RuntimeError("the denoiser did not return absolute boxes")

            final_boxes_absolute = denoised.auxiliary_boxes_absolute_xyxy[-1]

            selected_boxes, labels, mask, scores, _ = select_predictions(
                boxes_xyxy=final_boxes_absolute,
                logits=logits,
                class_threshold=class_threshold,
                nms_threshold=nms_threshold,
            )
            image_ids = [
                int(record["id"])
                for record in test_dataset.images[offset : offset + batch_size]
            ]
            predictions.extend(
                layout_predictions_to_coco(
                    image_ids=image_ids,
                    boxes_xyxy=selected_boxes,
                    labels=labels,
                    mask=mask,
                    scores=scores,
                    image_scales=batch_device["image_scales"],
                    original_image_scales=batch_device["original_image_scales"],
                    boxes_are_absolute=True,
                )
            )
            offset += batch_size

    return evaluate_cgl_predictions(
        test_dataset.annotation_path,
        predictions,
        output_dir=output_dir,
    )

evaluate_cgl_predictions

evaluate_cgl_predictions(
    annotation_path: str | Path,
    predictions: Sequence[
        Mapping[str, CocoPredictionValue]
    ],
    *,
    output_dir: str | Path | None = None,
    image_ids: Sequence[int] | None = None,
) -> CocoEvaluationReport

Compute the COCO bbox metrics used by the CGL evaluation entrypoint.

Parameters:

Name Type Description Default
annotation_path str | Path

COCO annotation JSON for the evaluated split.

required
predictions Sequence[Mapping[str, CocoPredictionValue]]

COCO detection records with xywh absolute boxes.

required
output_dir str | Path | None

Optional directory for the COCO result and metric files.

None
image_ids Sequence[int] | None

Optional subset evaluated by the result comparison.

None

Returns:

Type Description
CocoEvaluationReport

A JSON-serializable report with bbox and per-category AP metrics.

Raises:

Type Description
ImportError

If the optional COCO API is not installed.

Source code in models/radm/src/radm/evaluation.py
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
def evaluate_cgl_predictions(
    annotation_path: str | Path,
    predictions: Sequence[Mapping[str, CocoPredictionValue]],
    *,
    output_dir: str | Path | None = None,
    image_ids: Sequence[int] | None = None,
) -> CocoEvaluationReport:
    """Compute the COCO bbox metrics used by the CGL evaluation entrypoint.

    Args:
        annotation_path: COCO annotation JSON for the evaluated split.
        predictions: COCO detection records with ``xywh`` absolute boxes.
        output_dir: Optional directory for the COCO result and metric files.
        image_ids: Optional subset evaluated by the result comparison.

    Returns:
        A JSON-serializable report with ``bbox`` and per-category AP metrics.

    Raises:
        ImportError: If the optional COCO API is not installed.
    """
    try:
        from pycocotools.coco import COCO
        from pycocotools.cocoeval import COCOeval
    except ImportError as exc:  # pragma: no cover - exercised by minimal installs.
        raise ImportError(
            "CGL evaluation requires the optional pycocotools dependency"
        ) from exc

    annotation_path = Path(annotation_path)
    prediction_rows: list[CocoPrediction] = [dict(row) for row in predictions]
    coco = COCO(str(annotation_path))
    if prediction_rows:
        detections = coco.loadRes(prediction_rows)
        evaluator = COCOeval(coco, detections, "bbox")
        if image_ids is not None:
            evaluator.params.imgIds = [int(image_id) for image_id in image_ids]

        evaluator.evaluate()
        evaluator.accumulate()
        evaluator.summarize()
        stats = evaluator.stats
        bbox_metrics = {
            name: float(stats[index] * 100.0)
            if float(stats[index]) >= 0
            else float("nan")
            for index, name in enumerate(COCO_BBOX_METRIC_NAMES)
        }
        categories = coco.loadCats(coco.getCatIds())
        per_category: dict[str, float] = {}
        precisions = getattr(evaluator, "eval")["precision"]
        for category_index, category in enumerate(categories):
            precision = precisions[:, :, category_index, 0, -1]
            precision = precision[precision > -1]
            per_category[f"AP-{category['name']}"] = (
                float(precision.mean() * 100.0) if precision.size else float("nan")
            )

        results = {"bbox": {**bbox_metrics, **per_category}}
    else:
        results = {"bbox": {name: float("nan") for name in COCO_BBOX_METRIC_NAMES}}

    report: CocoEvaluationReport = {
        "format": "coco",
        "annotation_path": annotation_path.as_posix(),
        "image_ids": None if image_ids is None else [int(value) for value in image_ids],
        "prediction_count": len(prediction_rows),
        "results": results,
        "predictions": prediction_rows,
    }
    if output_dir is not None:
        output_root = Path(output_dir)
        output_root.mkdir(parents=True, exist_ok=True)
        (output_root / "coco_instances_results.json").write_text(
            json.dumps(prediction_rows, ensure_ascii=False, allow_nan=False) + "\n",
            encoding="utf-8",
        )
        (output_root / "metrics.json").write_text(
            json.dumps(results, ensure_ascii=False, allow_nan=True, indent=2) + "\n",
            encoding="utf-8",
        )
        report["results_path"] = (
            output_root / "coco_instances_results.json"
        ).as_posix()
        report["metrics_path"] = (output_root / "metrics.json").as_posix()

    return report

layout_predictions_to_coco

layout_predictions_to_coco(
    *,
    image_ids: Sequence[int],
    boxes_xyxy: Float[Tensor, "batch proposals 4"],
    labels: Int[Tensor, "batch proposals"],
    mask: Bool[Tensor, "batch proposals"],
    scores: Float[Tensor, "batch proposals"],
    image_scales: Float[Tensor, "batch 4"],
    original_image_scales: Float[Tensor, "batch 4"],
    model_image_scales: Float[Tensor, "batch 4"]
    | None = None,
    boxes_are_absolute: bool = False,
    category_id_map: Mapping[int, int] | None = None,
) -> list[CocoPrediction]

Convert selected normalized boxes into COCO detection records.

The CGL annotations use one-based contiguous category ids, while the public RADM prediction tensors use zero-based ids. category_id_map is available for datasets with a different explicit COCO category mapping.

Source code in models/radm/src/radm/evaluation.py
 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
def layout_predictions_to_coco(
    *,
    image_ids: Sequence[int],
    boxes_xyxy: Float[torch.Tensor, "batch proposals 4"],
    labels: Int[torch.Tensor, "batch proposals"],
    mask: Bool[torch.Tensor, "batch proposals"],
    scores: Float[torch.Tensor, "batch proposals"],
    image_scales: Float[torch.Tensor, "batch 4"],
    original_image_scales: Float[torch.Tensor, "batch 4"],
    model_image_scales: Float[torch.Tensor, "batch 4"] | None = None,
    boxes_are_absolute: bool = False,
    category_id_map: Mapping[int, int] | None = None,
) -> list[CocoPrediction]:
    """Convert selected normalized boxes into COCO detection records.

    The CGL annotations use one-based contiguous category ids, while the public
    RADM prediction tensors use zero-based ids.  ``category_id_map`` is
    available for datasets with a different explicit COCO category mapping.
    """
    if len(image_ids) != boxes_xyxy.shape[0]:
        raise ValueError("image_ids must align with the prediction batch")

    results: list[CocoPrediction] = []
    for batch_index, image_id in enumerate(image_ids):
        model_scale = image_scales if model_image_scales is None else model_image_scales
        resized_width, resized_height = image_scales[batch_index, :2]
        width, height = original_image_scales[batch_index, :2]
        scale_x = width / resized_width
        scale_y = height / resized_height
        for proposal_index in torch.nonzero(
            mask[batch_index], as_tuple=False
        ).flatten():
            box = boxes_xyxy[batch_index, proposal_index]
            if not boxes_are_absolute:
                box = box * model_scale[batch_index]

            box = box.clone()
            box[0::2] *= scale_x
            box[1::2] *= scale_y
            box[0::2].clamp_(min=0, max=width)
            box[1::2].clamp_(min=0, max=height)
            box[2] -= box[0]
            box[3] -= box[1]
            left, top, box_width, box_height = box.tolist()
            if box_width <= 0 or box_height <= 0:
                continue

            label = int(labels[batch_index, proposal_index].item())
            category_id = (
                int(category_id_map[label])
                if category_id_map is not None
                else label + 1
            )
            results.append(
                {
                    "image_id": int(image_id),
                    "bbox": [
                        float(left),
                        float(top),
                        float(box_width),
                        float(box_height),
                    ],
                    "score": float(scores[batch_index, proposal_index].item()),
                    "category_id": category_id,
                }
            )

    return results

options: show_submodules: true