Skip to content

Smarttext

SmartText Transformers-style text placement package.

SmartTextBackbone

Bases: StrEnum

Backbone names supported by the original SmartText scorer.

Source code in models/smarttext/src/smarttext/configuration_smarttext.py
26
27
28
29
30
31
32
class SmartTextBackbone(StrEnum):
    """Backbone names supported by the original SmartText scorer."""

    shufflenetv2 = "shufflenetv2"
    mobilenetv2 = "mobilenetv2"
    vgg16 = "vgg16"
    resnet50 = "resnet50"

SmartTextConfig

Bases: PretrainedConfig

Configuration for SmartText scorer, saliency model, and pipeline.

Parameters:

Name Type Description Default
id2label Mapping[int | str, str] | None

Public label mapping. Defaults to one text label.

None
scorer_scale str

Original scorer scale mode.

'multi'
scorer_backbone SmartTextBackbone | str

Original scorer backbone.

shufflenetv2
align_size int

RoI/RoD pooled spatial size.

9
reduction_dim int

Reduced feature channels before scoring.

8
downsample int

Scorer feature-map downsampling factor.

4
model_type_name SmartTextRegionMode | str

Original RoE/RoD region mode.

RoE
image_size int

Short-side normalization target for scorer preprocessing.

256
ratio_list Sequence[float]

Per-line font-size ratios.

(1.0, 0.8)
text_spacing int

Pixel spacing between prompt lines.

20
exp_prop int

Original expanded-region coefficient.

6
grid_num int

Candidate search grid count.

120
saliency_coef float

Saliency suppression coefficient.

2.6
max_text_area_coef float

Maximum candidate area divisor.

17.0
min_text_area_coef float

Minimum candidate area divisor.

7.0
min_font_size int

Minimum candidate font size.

10
max_font_size int

Maximum candidate font size.

500
font_inc_unit int

Candidate font-size step.

5
candi_res int

Number of selected candidates.

3
contrast_threshold float

Foreground/background contrast threshold.

5.0
mos_mean float

MOS score mean used by the original demo.

2.95
mos_std float

MOS score standard deviation.

0.8
rgb_mean Sequence[float]

RGB normalization mean for scorer inputs.

(0.485, 0.456, 0.406)
rgb_std Sequence[float]

RGB normalization std for scorer inputs.

(0.229, 0.224, 0.225)
scorer_subfolder str

Pipeline scorer subfolder.

'scorer'
saliency_subfolder str

Pipeline saliency-model subfolder.

'saliency_model'
processor_subfolder str

Pipeline processor subfolder.

'processor'
original_options Mapping[str, SmartTextMetadataValue] | None

Raw reference option values preserved for audit.

None
conversion_report Mapping[str, SmartTextMetadataValue | list[str]] | None

Conversion metadata persisted in configs.

None
kwargs str | int | float | bool | None

Extra PretrainedConfig fields.

{}

Examples:

>>> config = SmartTextConfig()
>>> config.id2label
{0: 'text'}
Source code in models/smarttext/src/smarttext/configuration_smarttext.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
class SmartTextConfig(PretrainedConfig):
    """Configuration for SmartText scorer, saliency model, and pipeline.

    Args:
        id2label: Public label mapping. Defaults to one ``text`` label.
        scorer_scale: Original scorer scale mode.
        scorer_backbone: Original scorer backbone.
        align_size: RoI/RoD pooled spatial size.
        reduction_dim: Reduced feature channels before scoring.
        downsample: Scorer feature-map downsampling factor.
        model_type_name: Original ``RoE``/``RoD`` region mode.
        image_size: Short-side normalization target for scorer preprocessing.
        ratio_list: Per-line font-size ratios.
        text_spacing: Pixel spacing between prompt lines.
        exp_prop: Original expanded-region coefficient.
        grid_num: Candidate search grid count.
        saliency_coef: Saliency suppression coefficient.
        max_text_area_coef: Maximum candidate area divisor.
        min_text_area_coef: Minimum candidate area divisor.
        min_font_size: Minimum candidate font size.
        max_font_size: Maximum candidate font size.
        font_inc_unit: Candidate font-size step.
        candi_res: Number of selected candidates.
        contrast_threshold: Foreground/background contrast threshold.
        mos_mean: MOS score mean used by the original demo.
        mos_std: MOS score standard deviation.
        rgb_mean: RGB normalization mean for scorer inputs.
        rgb_std: RGB normalization std for scorer inputs.
        scorer_subfolder: Pipeline scorer subfolder.
        saliency_subfolder: Pipeline saliency-model subfolder.
        processor_subfolder: Pipeline processor subfolder.
        original_options: Raw reference option values preserved for audit.
        conversion_report: Conversion metadata persisted in configs.
        kwargs: Extra ``PretrainedConfig`` fields.

    Examples:
        >>> config = SmartTextConfig()
        >>> config.id2label
        {0: 'text'}
    """

    model_type = "smarttext"

    def __init__(
        self,
        *,
        id2label: Mapping[int | str, str] | None = None,
        scorer_scale: str = "multi",
        scorer_backbone: SmartTextBackbone | str = SmartTextBackbone.shufflenetv2,
        align_size: int = 9,
        reduction_dim: int = 8,
        downsample: int = 4,
        model_type_name: SmartTextRegionMode | str = SmartTextRegionMode.RoE,
        image_size: int = 256,
        ratio_list: Sequence[float] = (1.0, 0.8),
        text_spacing: int = 20,
        exp_prop: int = 6,
        grid_num: int = 120,
        saliency_coef: float = 2.6,
        max_text_area_coef: float = 17.0,
        min_text_area_coef: float = 7.0,
        min_font_size: int = 10,
        max_font_size: int = 500,
        font_inc_unit: int = 5,
        candi_res: int = 3,
        contrast_threshold: float = 5.0,
        mos_mean: float = 2.95,
        mos_std: float = 0.8,
        rgb_mean: Sequence[float] = (0.485, 0.456, 0.406),
        rgb_std: Sequence[float] = (0.229, 0.224, 0.225),
        scorer_subfolder: str = "scorer",
        saliency_subfolder: str = "saliency_model",
        processor_subfolder: str = "processor",
        original_options: Mapping[str, SmartTextMetadataValue] | None = None,
        conversion_report: Mapping[str, SmartTextMetadataValue | list[str]]
        | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize SmartText configuration."""
        raw_id2label = id2label or DEFAULT_ID2LABEL
        normalized_id2label = {int(key): value for key, value in raw_id2label.items()}
        super().__init__(id2label=normalized_id2label, **kwargs)  # ty: ignore[invalid-argument-type]

        self.id2label = normalized_id2label
        self.label2id = {value: key for key, value in self.id2label.items()}

        self.scorer_scale = scorer_scale
        self.scorer_backbone = SmartTextBackbone(scorer_backbone).value
        self.align_size = int(align_size)
        self.reduction_dim = int(reduction_dim)
        self.downsample = int(downsample)

        self.model_type_name = SmartTextRegionMode(model_type_name).value
        self.image_size = int(image_size)
        self.ratio_list = tuple(float(value) for value in ratio_list)
        self.text_spacing = int(text_spacing)
        self.exp_prop = int(exp_prop)

        self.grid_num = int(grid_num)
        self.saliency_coef = float(saliency_coef)
        self.max_text_area_coef = float(max_text_area_coef)
        self.min_text_area_coef = float(min_text_area_coef)
        self.min_font_size = int(min_font_size)
        self.max_font_size = int(max_font_size)
        self.font_inc_unit = int(font_inc_unit)
        self.candi_res = int(candi_res)
        self.contrast_threshold = float(contrast_threshold)
        self.mos_mean = float(mos_mean)
        self.mos_std = float(mos_std)
        self.rgb_mean = tuple(float(value) for value in rgb_mean)
        self.rgb_std = tuple(float(value) for value in rgb_std)

        self.scorer_subfolder = scorer_subfolder
        self.saliency_subfolder = saliency_subfolder
        self.processor_subfolder = processor_subfolder

        self.original_options = dict(original_options or {})
        self.conversion_report = dict(conversion_report or {})

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

    @property
    def uses_expanded_region(self) -> bool:
        """Return whether the original ``RoE`` expanded-region mode is active."""
        return self.model_type_name == SmartTextRegionMode.RoE.value

    @property
    def scorer_input_channels(self) -> int:
        """Return the RGB scorer input channel count."""
        return 3

num_labels property

num_labels: int

Return the number of public semantic labels.

uses_expanded_region property

uses_expanded_region: bool

Return whether the original RoE expanded-region mode is active.

scorer_input_channels property

scorer_input_channels: int

Return the RGB scorer input channel count.

__init__

__init__(
    *,
    id2label: Mapping[int | str, str] | None = None,
    scorer_scale: str = "multi",
    scorer_backbone: SmartTextBackbone
    | str = SmartTextBackbone.shufflenetv2,
    align_size: int = 9,
    reduction_dim: int = 8,
    downsample: int = 4,
    model_type_name: SmartTextRegionMode
    | str = SmartTextRegionMode.RoE,
    image_size: int = 256,
    ratio_list: Sequence[float] = (1.0, 0.8),
    text_spacing: int = 20,
    exp_prop: int = 6,
    grid_num: int = 120,
    saliency_coef: float = 2.6,
    max_text_area_coef: float = 17.0,
    min_text_area_coef: float = 7.0,
    min_font_size: int = 10,
    max_font_size: int = 500,
    font_inc_unit: int = 5,
    candi_res: int = 3,
    contrast_threshold: float = 5.0,
    mos_mean: float = 2.95,
    mos_std: float = 0.8,
    rgb_mean: Sequence[float] = (0.485, 0.456, 0.406),
    rgb_std: Sequence[float] = (0.229, 0.224, 0.225),
    scorer_subfolder: str = "scorer",
    saliency_subfolder: str = "saliency_model",
    processor_subfolder: str = "processor",
    original_options: Mapping[str, SmartTextMetadataValue]
    | None = None,
    conversion_report: Mapping[
        str, SmartTextMetadataValue | list[str]
    ]
    | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize SmartText configuration.

Source code in models/smarttext/src/smarttext/configuration_smarttext.py
 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
def __init__(
    self,
    *,
    id2label: Mapping[int | str, str] | None = None,
    scorer_scale: str = "multi",
    scorer_backbone: SmartTextBackbone | str = SmartTextBackbone.shufflenetv2,
    align_size: int = 9,
    reduction_dim: int = 8,
    downsample: int = 4,
    model_type_name: SmartTextRegionMode | str = SmartTextRegionMode.RoE,
    image_size: int = 256,
    ratio_list: Sequence[float] = (1.0, 0.8),
    text_spacing: int = 20,
    exp_prop: int = 6,
    grid_num: int = 120,
    saliency_coef: float = 2.6,
    max_text_area_coef: float = 17.0,
    min_text_area_coef: float = 7.0,
    min_font_size: int = 10,
    max_font_size: int = 500,
    font_inc_unit: int = 5,
    candi_res: int = 3,
    contrast_threshold: float = 5.0,
    mos_mean: float = 2.95,
    mos_std: float = 0.8,
    rgb_mean: Sequence[float] = (0.485, 0.456, 0.406),
    rgb_std: Sequence[float] = (0.229, 0.224, 0.225),
    scorer_subfolder: str = "scorer",
    saliency_subfolder: str = "saliency_model",
    processor_subfolder: str = "processor",
    original_options: Mapping[str, SmartTextMetadataValue] | None = None,
    conversion_report: Mapping[str, SmartTextMetadataValue | list[str]]
    | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize SmartText configuration."""
    raw_id2label = id2label or DEFAULT_ID2LABEL
    normalized_id2label = {int(key): value for key, value in raw_id2label.items()}
    super().__init__(id2label=normalized_id2label, **kwargs)  # ty: ignore[invalid-argument-type]

    self.id2label = normalized_id2label
    self.label2id = {value: key for key, value in self.id2label.items()}

    self.scorer_scale = scorer_scale
    self.scorer_backbone = SmartTextBackbone(scorer_backbone).value
    self.align_size = int(align_size)
    self.reduction_dim = int(reduction_dim)
    self.downsample = int(downsample)

    self.model_type_name = SmartTextRegionMode(model_type_name).value
    self.image_size = int(image_size)
    self.ratio_list = tuple(float(value) for value in ratio_list)
    self.text_spacing = int(text_spacing)
    self.exp_prop = int(exp_prop)

    self.grid_num = int(grid_num)
    self.saliency_coef = float(saliency_coef)
    self.max_text_area_coef = float(max_text_area_coef)
    self.min_text_area_coef = float(min_text_area_coef)
    self.min_font_size = int(min_font_size)
    self.max_font_size = int(max_font_size)
    self.font_inc_unit = int(font_inc_unit)
    self.candi_res = int(candi_res)
    self.contrast_threshold = float(contrast_threshold)
    self.mos_mean = float(mos_mean)
    self.mos_std = float(mos_std)
    self.rgb_mean = tuple(float(value) for value in rgb_mean)
    self.rgb_std = tuple(float(value) for value in rgb_std)

    self.scorer_subfolder = scorer_subfolder
    self.saliency_subfolder = saliency_subfolder
    self.processor_subfolder = processor_subfolder

    self.original_options = dict(original_options or {})
    self.conversion_report = dict(conversion_report or {})

SmartTextRegionMode

Bases: StrEnum

Supported SmartText region scoring modes.

Source code in models/smarttext/src/smarttext/configuration_smarttext.py
19
20
21
22
23
class SmartTextRegionMode(StrEnum):
    """Supported SmartText region scoring modes."""

    RoD = "RoD"
    RoE = "RoE"

SmartTextImageProcessor

Bases: BaseImageProcessor

Prepare SmartText scorer and BASNet image tensors.

Parameters:

Name Type Description Default
image_size int

Scorer short-side target.

256
rgb_mean Sequence[float]

Scorer RGB normalization mean.

(0.485, 0.456, 0.406)
rgb_std Sequence[float]

Scorer RGB normalization standard deviation.

(0.229, 0.224, 0.225)

Examples:

>>> processor = SmartTextImageProcessor()
>>> batch = processor.preprocess(Image.new("RGB", (32, 32)))
>>> tuple(batch["pixel_values"].shape[:2])
(1, 3)
Source code in models/smarttext/src/smarttext/image_processing_smarttext.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
class SmartTextImageProcessor(BaseImageProcessor):
    """Prepare SmartText scorer and BASNet image tensors.

    Args:
        image_size: Scorer short-side target.
        rgb_mean: Scorer RGB normalization mean.
        rgb_std: Scorer RGB normalization standard deviation.

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

    model_input_names = ["pixel_values", "basnet_pixel_values"]

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

    @classmethod
    def from_config(cls, config: SmartTextConfig) -> "SmartTextImageProcessor":
        """Build an image processor from SmartText configuration."""
        return cls(
            image_size=config.image_size,
            rgb_mean=config.rgb_mean,
            rgb_std=config.rgb_std,
        )

    def preprocess(
        self,
        images: ImageInput | Sequence[ImageInput],
        *,
        return_tensors: Literal["pt"] = "pt",
        target_min_side: int | None = None,
        rgb_mean: Sequence[float] | None = None,
        rgb_std: Sequence[float] | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> BatchFeature:
        """Preprocess images for the SmartText scorer.

        Args:
            images: RGB image or image batch.
            return_tensors: Tensor framework. Only ``pt`` is supported.
            target_min_side: Optional short-side target override.
            rgb_mean: Optional RGB mean override.
            rgb_std: Optional RGB std override.
            kwargs: Ignored compatibility kwargs.

        Returns:
            Batch feature with ``pixel_values`` and ``image_sizes``.

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

        mean = np.asarray(rgb_mean or self.rgb_mean, dtype=np.float32)
        std = np.asarray(rgb_std or self.rgb_std, dtype=np.float32)
        tensors = []
        sizes = []
        for image in _ensure_pil_batch(images):
            width, height = image.size
            sizes.append((height, width))
            scale = (target_min_side or self.image_size) / min(height, width)
            resized_h = max(32, int(round(height * scale / 32.0) * 32))
            resized_w = max(32, int(round(width * scale / 32.0) * 32))
            resized = image.convert("RGB").resize(
                (resized_w, resized_h), Image.Resampling.BILINEAR
            )
            array = np.asarray(resized, dtype=np.float32) / 256.0
            tensors.append(torch.from_numpy(((array - mean) / std).transpose(2, 0, 1)))
        return BatchFeature(
            {
                "pixel_values": torch.stack(tensors).float(),
                "image_sizes": torch.tensor(sizes, dtype=torch.long),
            }
        )

    def preprocess_basnet(
        self,
        images: ImageInput | Sequence[ImageInput],
        *,
        return_tensors: Literal["pt"] = "pt",
    ) -> BatchFeature:
        """Preprocess images for BASNet saliency prediction.

        Args:
            images: RGB image or image batch.
            return_tensors: Tensor framework. Only ``pt`` is supported.

        Returns:
            Batch feature with ``basnet_pixel_values`` shaped ``(B, 3, 256, 256)``.
        """
        basnet = BASNetImageProcessor(
            input_size=256,
            rgb_mean=self.rgb_mean,
            rgb_std=self.rgb_std,
        ).preprocess(images, return_tensors=return_tensors)
        return BatchFeature(
            {
                "basnet_pixel_values": basnet["pixel_values"],
                "image_sizes": basnet["image_sizes"],
            }
        )

__init__

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

Initialize image processor settings.

Source code in models/smarttext/src/smarttext/image_processing_smarttext.py
37
38
39
40
41
42
43
44
45
46
47
48
def __init__(
    self,
    image_size: int = 256,
    rgb_mean: Sequence[float] = (0.485, 0.456, 0.406),
    rgb_std: Sequence[float] = (0.229, 0.224, 0.225),
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize image processor settings."""
    super().__init__(**kwargs)
    self.image_size = int(image_size)
    self.rgb_mean = tuple(float(value) for value in rgb_mean)
    self.rgb_std = tuple(float(value) for value in rgb_std)

from_config classmethod

from_config(
    config: SmartTextConfig,
) -> "SmartTextImageProcessor"

Build an image processor from SmartText configuration.

Source code in models/smarttext/src/smarttext/image_processing_smarttext.py
50
51
52
53
54
55
56
57
@classmethod
def from_config(cls, config: SmartTextConfig) -> "SmartTextImageProcessor":
    """Build an image processor from SmartText configuration."""
    return cls(
        image_size=config.image_size,
        rgb_mean=config.rgb_mean,
        rgb_std=config.rgb_std,
    )

preprocess

preprocess(
    images: ImageInput | Sequence[ImageInput],
    *,
    return_tensors: Literal["pt"] = "pt",
    target_min_side: int | None = None,
    rgb_mean: Sequence[float] | None = None,
    rgb_std: Sequence[float] | None = None,
    **kwargs: str | int | float | bool | None,
) -> BatchFeature

Preprocess images for the SmartText scorer.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput]

RGB image or image batch.

required
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

'pt'
target_min_side int | None

Optional short-side target override.

None
rgb_mean Sequence[float] | None

Optional RGB mean override.

None
rgb_std Sequence[float] | None

Optional RGB std override.

None
kwargs str | int | float | bool | None

Ignored compatibility kwargs.

{}

Returns:

Type Description
BatchFeature

Batch feature with pixel_values and image_sizes.

Raises:

Type Description
ValueError

If return_tensors is not pt.

Source code in models/smarttext/src/smarttext/image_processing_smarttext.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
def preprocess(
    self,
    images: ImageInput | Sequence[ImageInput],
    *,
    return_tensors: Literal["pt"] = "pt",
    target_min_side: int | None = None,
    rgb_mean: Sequence[float] | None = None,
    rgb_std: Sequence[float] | None = None,
    **kwargs: str | int | float | bool | None,
) -> BatchFeature:
    """Preprocess images for the SmartText scorer.

    Args:
        images: RGB image or image batch.
        return_tensors: Tensor framework. Only ``pt`` is supported.
        target_min_side: Optional short-side target override.
        rgb_mean: Optional RGB mean override.
        rgb_std: Optional RGB std override.
        kwargs: Ignored compatibility kwargs.

    Returns:
        Batch feature with ``pixel_values`` and ``image_sizes``.

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

    mean = np.asarray(rgb_mean or self.rgb_mean, dtype=np.float32)
    std = np.asarray(rgb_std or self.rgb_std, dtype=np.float32)
    tensors = []
    sizes = []
    for image in _ensure_pil_batch(images):
        width, height = image.size
        sizes.append((height, width))
        scale = (target_min_side or self.image_size) / min(height, width)
        resized_h = max(32, int(round(height * scale / 32.0) * 32))
        resized_w = max(32, int(round(width * scale / 32.0) * 32))
        resized = image.convert("RGB").resize(
            (resized_w, resized_h), Image.Resampling.BILINEAR
        )
        array = np.asarray(resized, dtype=np.float32) / 256.0
        tensors.append(torch.from_numpy(((array - mean) / std).transpose(2, 0, 1)))
    return BatchFeature(
        {
            "pixel_values": torch.stack(tensors).float(),
            "image_sizes": torch.tensor(sizes, dtype=torch.long),
        }
    )

preprocess_basnet

preprocess_basnet(
    images: ImageInput | Sequence[ImageInput],
    *,
    return_tensors: Literal["pt"] = "pt",
) -> BatchFeature

Preprocess images for BASNet saliency prediction.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput]

RGB image or image batch.

required
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

'pt'

Returns:

Type Description
BatchFeature

Batch feature with basnet_pixel_values shaped (B, 3, 256, 256).

Source code in models/smarttext/src/smarttext/image_processing_smarttext.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def preprocess_basnet(
    self,
    images: ImageInput | Sequence[ImageInput],
    *,
    return_tensors: Literal["pt"] = "pt",
) -> BatchFeature:
    """Preprocess images for BASNet saliency prediction.

    Args:
        images: RGB image or image batch.
        return_tensors: Tensor framework. Only ``pt`` is supported.

    Returns:
        Batch feature with ``basnet_pixel_values`` shaped ``(B, 3, 256, 256)``.
    """
    basnet = BASNetImageProcessor(
        input_size=256,
        rgb_mean=self.rgb_mean,
        rgb_std=self.rgb_std,
    ).preprocess(images, return_tensors=return_tensors)
    return BatchFeature(
        {
            "basnet_pixel_values": basnet["pixel_values"],
            "image_sizes": basnet["image_sizes"],
        }
    )

SmartTextBASNet

Bases: BASNetModel

SmartText saliency component backed by the shared BASNet model.

Source code in models/smarttext/src/smarttext/modeling_basnet.py
17
18
19
20
21
22
23
24
class SmartTextBASNet(BASNetModel):
    """SmartText saliency component backed by the shared BASNet model."""

    config_class = SmartTextConfig

    def __init__(self, config: SmartTextConfig) -> None:
        """Initialize the SmartText saliency component."""
        super().__init__(cast(BASNetConfig, config))

__init__

__init__(config: SmartTextConfig) -> None

Initialize the SmartText saliency component.

Source code in models/smarttext/src/smarttext/modeling_basnet.py
22
23
24
def __init__(self, config: SmartTextConfig) -> None:
    """Initialize the SmartText saliency component."""
    super().__init__(cast(BASNetConfig, config))

SmartTextScorer

Bases: PreTrainedModel

Reference-compatible SMT candidate scorer.

The module names match the original smtModel.py for build_smt_model(scale="multi", alignsize=9, reddim=8, model="shufflenetv2", downsample=4) so SMT.pth loads directly.

Parameters:

Name Type Description Default
config SmartTextConfig

SmartText configuration.

required

Examples:

>>> config = SmartTextConfig()
>>> model = SmartTextScorer(config)
>>> "Feat_ext.feature3.0.0.weight" in model.state_dict()
True
Source code in models/smarttext/src/smarttext/modeling_smarttext.py
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
class SmartTextScorer(PreTrainedModel):
    """Reference-compatible SMT candidate scorer.

    The module names match the original ``smtModel.py`` for
    ``build_smt_model(scale="multi", alignsize=9, reddim=8,
    model="shufflenetv2", downsample=4)`` so ``SMT.pth`` loads directly.

    Args:
        config: SmartText configuration.

    Examples:
        >>> config = SmartTextConfig()
        >>> model = SmartTextScorer(config)
        >>> "Feat_ext.feature3.0.0.weight" in model.state_dict()
        True
    """

    config_class = SmartTextConfig
    main_input_name = "pixel_values"
    _tied_weights_keys: list[str] = []

    def __init__(self, config: SmartTextConfig) -> None:
        """Initialize scorer scorer modules."""
        super().__init__(config)
        self.all_tied_weights_keys: dict[str, str] = {}
        if config.scorer_scale != "multi" or config.scorer_backbone != "shufflenetv2":
            raise ValueError(
                "Only the released multi-scale ShuffleNetV2 SMT scorer is supported"
            )

        self.Feat_ext = _ShuffleNetV2Base()
        self.DimRed = nn.Conv2d(812, config.reduction_dim, kernel_size=1, padding=0)
        self.downsample2 = nn.UpsamplingBilinear2d(scale_factor=1.0 / 2.0)
        self.upsample2 = nn.UpsamplingBilinear2d(scale_factor=2.0)
        spatial_scale = 1.0 / 2**config.downsample
        self.RoIAlign = SmartTextRoIAlignAvg(
            config.align_size + 1, config.align_size + 1, spatial_scale
        )
        self.RoDAlign = SmartTextRoDAlignAvg(
            config.align_size + 1, config.align_size + 1, spatial_scale
        )
        self.FC_layers = _fc_layers(config.reduction_dim * 2, config.align_size)

    def forward(
        self,
        pixel_values: Float[torch.Tensor, "batch channels height width"],
        boxes: Float[torch.Tensor, "candidates 5"],
        return_dict: bool | None = None,
    ) -> SmartTextScorerOutput | tuple[Float[torch.Tensor, "candidates"]]:
        """Score candidate text regions.

        Args:
            pixel_values: RGB scorer tensor shaped ``(B, 3, H, W)``.
            boxes: RoI rows shaped ``(N, 5)`` with batch index in column zero.
            return_dict: Whether to return a ``ModelOutput``.

        Returns:
            Candidate scores as a ``SmartTextScorerOutput`` or tuple.
        """
        return_dict = (
            return_dict if return_dict is not None else self.config.use_return_dict
        )
        f3, f4, f5 = self.Feat_ext(pixel_values)
        cat_feat = torch.cat((self.downsample2(f3), f4, 0.5 * self.upsample2(f5)), 1)
        red_feat = self.DimRed(cat_feat)
        roi_feat = self.RoIAlign(red_feat, boxes)
        rod_feat = self.RoDAlign(red_feat, boxes)
        prediction = self.FC_layers(torch.cat((roi_feat, rod_feat), 1)).flatten()
        if not return_dict:
            return (prediction,)
        return SmartTextScorerOutput(scores=prediction)

__init__

__init__(config: SmartTextConfig) -> None

Initialize scorer scorer modules.

Source code in models/smarttext/src/smarttext/modeling_smarttext.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def __init__(self, config: SmartTextConfig) -> None:
    """Initialize scorer scorer modules."""
    super().__init__(config)
    self.all_tied_weights_keys: dict[str, str] = {}
    if config.scorer_scale != "multi" or config.scorer_backbone != "shufflenetv2":
        raise ValueError(
            "Only the released multi-scale ShuffleNetV2 SMT scorer is supported"
        )

    self.Feat_ext = _ShuffleNetV2Base()
    self.DimRed = nn.Conv2d(812, config.reduction_dim, kernel_size=1, padding=0)
    self.downsample2 = nn.UpsamplingBilinear2d(scale_factor=1.0 / 2.0)
    self.upsample2 = nn.UpsamplingBilinear2d(scale_factor=2.0)
    spatial_scale = 1.0 / 2**config.downsample
    self.RoIAlign = SmartTextRoIAlignAvg(
        config.align_size + 1, config.align_size + 1, spatial_scale
    )
    self.RoDAlign = SmartTextRoDAlignAvg(
        config.align_size + 1, config.align_size + 1, spatial_scale
    )
    self.FC_layers = _fc_layers(config.reduction_dim * 2, config.align_size)

forward

forward(
    pixel_values: Float[
        Tensor, "batch channels height width"
    ],
    boxes: Float[Tensor, "candidates 5"],
    return_dict: bool | None = None,
) -> (
    SmartTextScorerOutput
    | tuple[Float[torch.Tensor, "candidates"]]
)

Score candidate text regions.

Parameters:

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

RGB scorer tensor shaped (B, 3, H, W).

required
boxes Float[Tensor, 'candidates 5']

RoI rows shaped (N, 5) with batch index in column zero.

required
return_dict bool | None

Whether to return a ModelOutput.

None

Returns:

Type Description
SmartTextScorerOutput | tuple[Float[Tensor, 'candidates']]

Candidate scores as a SmartTextScorerOutput or tuple.

Source code in models/smarttext/src/smarttext/modeling_smarttext.py
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
def forward(
    self,
    pixel_values: Float[torch.Tensor, "batch channels height width"],
    boxes: Float[torch.Tensor, "candidates 5"],
    return_dict: bool | None = None,
) -> SmartTextScorerOutput | tuple[Float[torch.Tensor, "candidates"]]:
    """Score candidate text regions.

    Args:
        pixel_values: RGB scorer tensor shaped ``(B, 3, H, W)``.
        boxes: RoI rows shaped ``(N, 5)`` with batch index in column zero.
        return_dict: Whether to return a ``ModelOutput``.

    Returns:
        Candidate scores as a ``SmartTextScorerOutput`` or tuple.
    """
    return_dict = (
        return_dict if return_dict is not None else self.config.use_return_dict
    )
    f3, f4, f5 = self.Feat_ext(pixel_values)
    cat_feat = torch.cat((self.downsample2(f3), f4, 0.5 * self.upsample2(f5)), 1)
    red_feat = self.DimRed(cat_feat)
    roi_feat = self.RoIAlign(red_feat, boxes)
    rod_feat = self.RoDAlign(red_feat, boxes)
    prediction = self.FC_layers(torch.cat((roi_feat, rod_feat), 1)).flatten()
    if not return_dict:
        return (prediction,)
    return SmartTextScorerOutput(scores=prediction)

SmartTextScorerOutput dataclass

Bases: ModelOutput

Output of SmartTextScorer.forward.

Source code in models/smarttext/src/smarttext/modeling_smarttext.py
18
19
20
21
22
@dataclass
class SmartTextScorerOutput(ModelOutput):
    """Output of ``SmartTextScorer.forward``."""

    scores: Float[torch.Tensor, "candidates"]

SmartTextPipeline

Bases: LayoutGenerationPipeline

Transformers-side SmartText pipeline.

Parameters:

Name Type Description Default
scorer SmartTextScorer

Candidate scoring model.

required
saliency_model SmartTextBASNet

BASNet saliency model.

required
processor SmartTextProcessor | None

Input/output processor.

None
config SmartTextConfig | None

Optional root pipeline config.

None
device str | device | None

Optional runtime device.

None

Examples:

>>> config = SmartTextConfig(align_size=3, reduction_dim=4, grid_num=16, max_font_size=20)
>>> pipe = SmartTextPipeline(SmartTextScorer(config), SmartTextBASNet(config), config=config)
>>> pipe.config.model_type
'smarttext'
Source code in models/smarttext/src/smarttext/pipeline_smarttext.py
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
406
407
class SmartTextPipeline(LayoutGenerationPipeline):
    """Transformers-side SmartText pipeline.

    Args:
        scorer: Candidate scoring model.
        saliency_model: BASNet saliency model.
        processor: Input/output processor.
        config: Optional root pipeline config.
        device: Optional runtime device.

    Examples:
        >>> config = SmartTextConfig(align_size=3, reduction_dim=4, grid_num=16, max_font_size=20)
        >>> pipe = SmartTextPipeline(SmartTextScorer(config), SmartTextBASNet(config), config=config)
        >>> pipe.config.model_type
        'smarttext'
    """

    config_class: ClassVar[type[PretrainedConfig]] = SmartTextConfig
    component_specs: ClassVar[dict[str, PipelineComponentSpec]] = {
        "scorer": PipelineComponentSpec(
            attribute_name="scorer",
            loader=_load_scorer_component,
            marker_file="config.json",
            config_subfolder_attribute="scorer_subfolder",
        ),
        "saliency_model": PipelineComponentSpec(
            attribute_name="saliency_model",
            loader=_load_saliency_component,
            marker_file="config.json",
            config_subfolder_attribute="saliency_subfolder",
        ),
        "processor": PipelineComponentSpec(
            attribute_name="processor",
            loader=_load_processor_component,
            marker_file="processor_config.json",
            save_with_is_main_process=False,
            config_subfolder_attribute="processor_subfolder",
        ),
    }

    config: SmartTextConfig
    scorer: SmartTextScorer
    saliency_model: SmartTextBASNet
    processor: SmartTextProcessor

    def __init__(
        self,
        scorer: SmartTextScorer,
        saliency_model: SmartTextBASNet,
        processor: SmartTextProcessor | None = None,
        config: SmartTextConfig | None = None,
        device: str | torch.device | None = None,
    ) -> None:
        """Initialize SmartText pipeline."""
        super().__init__(config or scorer.config)
        self.config = config or scorer.config
        self.scorer = scorer
        self.saliency_model = saliency_model
        self.processor = processor or SmartTextProcessor(config=self.config)
        self.scorer.eval()
        self.saliency_model.eval()
        if device is not None:
            self.to(device)

    @classmethod
    def _from_pretrained_components(
        cls,
        *,
        config: PretrainedConfig,
        components: Mapping[str, PipelineComponent | None],
    ) -> "SmartTextPipeline":
        """Build a SmartText pipeline from loaded components."""
        return cls(
            config=cast(SmartTextConfig, config),
            scorer=cast(SmartTextScorer, components["scorer"]),
            saliency_model=cast(SmartTextBASNet, components["saliency_model"]),
            processor=cast(SmartTextProcessor, components["processor"]),
        )

    @torch.no_grad()
    def __call__(  # ty: ignore[invalid-method-override]
        self,
        images: ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch channels height width"]
        | None = None,
        *,
        content: Mapping[
            str,
            ImageInput
            | Sequence[ImageInput]
            | str
            | Sequence[str]
            | Float[torch.Tensor, "batch height width"]
            | Sequence[CandidateBoxRow]
            | Sequence[Sequence[CandidateBoxRow]],
        ]
        | None = None,
        prompt: str | Sequence[str] | None = None,
        text: str | Sequence[str] | None = None,
        batch_size: int = 1,
        seed: int | None = None,
        generator: torch.Generator | None = None,
        condition_type: ConditionType | str = ConditionType.content_image,
        labels: Int[torch.Tensor, "batch elements"]
        | Sequence[Sequence[int]]
        | Sequence[int]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Sequence[Sequence[Sequence[float]]]
        | Sequence[Sequence[float]]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Sequence[Sequence[bool]]
        | Sequence[bool]
        | None = None,
        num_elements: int | Sequence[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        output_type: OutputType | Literal["dataclass", "dict"] = OutputType.dataclass,
        return_intermediates: bool = False,
        font: str | Path | ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
        ratio_list: Sequence[float] | None = None,
        text_spacing: int | None = None,
        candi_res: int | None = None,
        saliency: ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch height width"]
        | None = None,
        candidate_boxes: Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]]
        | None = None,
        return_text_lines: bool = False,
        score_normalization: Literal["mos", "raw"] = "mos",
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | dict[int, str]
            | dict[
                str,
                Shaped[torch.Tensor, "..."]
                | str
                | list[int]
                | list[SmartTextCandidate]
                | None,
            ]
            | None,
        ]
    ):
        """Generate text placement boxes for content images.

        Args:
            images: RGB image or image batch.
            content: Optional content carrier.
            prompt: Prompt text payload.
            text: Alias for prompt.
            batch_size: Expected batch size for validation.
            seed: Optional seed used only when ``generator`` is absent.
            generator: Explicit torch generator; wins over ``seed``.
            condition_type: Must normalize to ``content_image``.
            labels: Unsupported v1 compatibility argument.
            bbox: Unsupported v1 compatibility argument.
            mask: Unsupported v1 compatibility argument.
            num_elements: Unsupported v1 compatibility argument.
            box_format: Public v1 compatibility argument.
            normalized: Public v1 compatibility argument.
            canvas_size: Optional source canvas size override.
            num_inference_steps: Unused v1 compatibility argument.
            output_type: ``dataclass`` or ``dict``.
            return_intermediates: Whether to include intermediate payloads.
            font: TrueType font path or PIL font object.
            ratio_list: Optional per-line font ratios.
            text_spacing: Optional text-spacing override.
            candi_res: Optional top-k override.
            saliency: Optional saliency map bypassing BASNet.
            candidate_boxes: Optional reference-style candidates.
            return_text_lines: Return per-line boxes for top candidate.
            score_normalization: ``mos`` or ``raw``.

        Returns:
            Shared layout output.
        """
        del (
            labels,
            bbox,
            mask,
            num_elements,
            box_format,
            normalized,
            canvas_size,
            num_inference_steps,
        )
        normalize_condition_type(condition_type)
        self.prepare_generator(generator=generator, seed=seed, device=self.device)
        if font is None:
            font = ImageFont.load_default()
        effective_config = self.config
        if text_spacing is not None:
            effective_config = copy.copy(self.config)
            effective_config.text_spacing = int(text_spacing)
        encoded = self.processor(
            images,
            content=content,
            prompt=prompt,
            text=text,
            saliency=saliency,
            candidate_boxes=candidate_boxes,
            font=font,
        )
        if len(encoded["images"]) != 1 or batch_size != 1:
            raise ValueError("SmartText currently decodes one image at a time")

        image = encoded["images"][0]
        prompt_text = encoded["prompts"][0]
        resolved_saliency = encoded["saliency"]
        if resolved_saliency is None:
            basnet_values = encoded["basnet_pixel_values"].to(self._runtime_device())
            saliency_out = self.saliency_model(basnet_values)
            resolved_saliency = saliency_out.saliency[0]
        candidates = encoded["candidate_boxes"]
        if candidates is None:
            candidates = generate_candidates(
                image,
                cast(torch.Tensor, resolved_saliency),
                prompt=prompt_text,
                font=font,
                config=effective_config,
                ratio_list=ratio_list,
            )
        pixel_values, boxes, candidates = prepare_scorer_batch(
            image,
            candidates,
            config=effective_config,
        )
        scores = self.scorer(
            pixel_values.to(self._runtime_device()),
            boxes.to(self._runtime_device()),
        ).scores
        top_k = candi_res or self.config.candi_res
        raw_scores = scores.detach().cpu().float().flatten()
        if candidates:
            selected_color_index = max(
                range(len(candidates)), key=lambda index: float(raw_scores[index])
            )
            text_color = choose_text_color(
                image,
                candidates[selected_color_index].bbox_ltrb_px,
                contrast_threshold=self.config.contrast_threshold,
            )
        else:
            text_color = None
        intermediates = None
        if return_intermediates:
            intermediates = {
                "saliency": resolved_saliency,
                "raw_scorer_boxes": boxes.detach().cpu(),
                "prompt": prompt_text,
            }
        return self.processor.decode(
            candidates=candidates,
            scores=scores,
            image_size=image.size,
            output_type=cast(Literal["dataclass", "dict"], str(output_type)),
            return_text_lines=return_text_lines,
            top_k=top_k,
            score_normalization=score_normalization,
            text_color=text_color,
            intermediates=intermediates,
        )

    def _runtime_device(self) -> torch.device:
        return self.device or next(self.scorer.parameters()).device

__init__

__init__(
    scorer: SmartTextScorer,
    saliency_model: SmartTextBASNet,
    processor: SmartTextProcessor | None = None,
    config: SmartTextConfig | None = None,
    device: str | device | None = None,
) -> None

Initialize SmartText pipeline.

Source code in models/smarttext/src/smarttext/pipeline_smarttext.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def __init__(
    self,
    scorer: SmartTextScorer,
    saliency_model: SmartTextBASNet,
    processor: SmartTextProcessor | None = None,
    config: SmartTextConfig | None = None,
    device: str | torch.device | None = None,
) -> None:
    """Initialize SmartText pipeline."""
    super().__init__(config or scorer.config)
    self.config = config or scorer.config
    self.scorer = scorer
    self.saliency_model = saliency_model
    self.processor = processor or SmartTextProcessor(config=self.config)
    self.scorer.eval()
    self.saliency_model.eval()
    if device is not None:
        self.to(device)

__call__

__call__(
    images: ImageInput
    | Sequence[ImageInput]
    | Float[Tensor, "batch channels height width"]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | str
        | Sequence[str]
        | Float[Tensor, "batch height width"]
        | Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    text: str | Sequence[str] | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.content_image,
    labels: Int[Tensor, "batch elements"]
    | Sequence[Sequence[int]]
    | Sequence[int]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Sequence[Sequence[Sequence[float]]]
    | Sequence[Sequence[float]]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    num_elements: int
    | Sequence[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType
    | Literal["dataclass", "dict"] = OutputType.dataclass,
    return_intermediates: bool = False,
    font: str
    | Path
    | FreeTypeFont
    | ImageFont
    | None = None,
    ratio_list: Sequence[float] | None = None,
    text_spacing: int | None = None,
    candi_res: int | None = None,
    saliency: ImageInput
    | Sequence[ImageInput]
    | Float[Tensor, "batch height width"]
    | None = None,
    candidate_boxes: Sequence[CandidateBoxRow]
    | Sequence[Sequence[CandidateBoxRow]]
    | None = None,
    return_text_lines: bool = False,
    score_normalization: Literal["mos", "raw"] = "mos",
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | str
            | list[int]
            | list[SmartTextCandidate]
            | None,
        ]
        | None,
    ]
)

Generate text placement boxes for content images.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput] | Float[Tensor, 'batch channels height width'] | None

RGB image or image batch.

None
content Mapping[str, ImageInput | Sequence[ImageInput] | str | Sequence[str] | Float[Tensor, 'batch height width'] | Sequence[CandidateBoxRow] | Sequence[Sequence[CandidateBoxRow]]] | None

Optional content carrier.

None
prompt str | Sequence[str] | None

Prompt text payload.

None
text str | Sequence[str] | None

Alias for prompt.

None
batch_size int

Expected batch size for validation.

1
seed int | None

Optional seed used only when generator is absent.

None
generator Generator | None

Explicit torch generator; wins over seed.

None
condition_type ConditionType | str

Must normalize to content_image.

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

Unsupported v1 compatibility argument.

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

Unsupported v1 compatibility argument.

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

Unsupported v1 compatibility argument.

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

Unsupported v1 compatibility argument.

None
box_format BoxFormat | str

Public v1 compatibility argument.

xywh
normalized bool

Public v1 compatibility argument.

True
canvas_size tuple[int, int] | None

Optional source canvas size override.

None
num_inference_steps int | None

Unused v1 compatibility argument.

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

dataclass or dict.

dataclass
return_intermediates bool

Whether to include intermediate payloads.

False
font str | Path | FreeTypeFont | ImageFont | None

TrueType font path or PIL font object.

None
ratio_list Sequence[float] | None

Optional per-line font ratios.

None
text_spacing int | None

Optional text-spacing override.

None
candi_res int | None

Optional top-k override.

None
saliency ImageInput | Sequence[ImageInput] | Float[Tensor, 'batch height width'] | None

Optional saliency map bypassing BASNet.

None
candidate_boxes Sequence[CandidateBoxRow] | Sequence[Sequence[CandidateBoxRow]] | None

Optional reference-style candidates.

None
return_text_lines bool

Return per-line boxes for top candidate.

False
score_normalization Literal['mos', 'raw']

mos or raw.

'mos'

Returns:

Type Description
LayoutGenerationOutput | dict[str, Shaped[Tensor, '...'] | dict[int, str] | dict[str, Shaped[Tensor, '...'] | str | list[int] | list[SmartTextCandidate] | None] | None]

Shared layout output.

Source code in models/smarttext/src/smarttext/pipeline_smarttext.py
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
@torch.no_grad()
def __call__(  # ty: ignore[invalid-method-override]
    self,
    images: ImageInput
    | Sequence[ImageInput]
    | Float[torch.Tensor, "batch channels height width"]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | str
        | Sequence[str]
        | Float[torch.Tensor, "batch height width"]
        | Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    text: str | Sequence[str] | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: torch.Generator | None = None,
    condition_type: ConditionType | str = ConditionType.content_image,
    labels: Int[torch.Tensor, "batch elements"]
    | Sequence[Sequence[int]]
    | Sequence[int]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Sequence[Sequence[Sequence[float]]]
    | Sequence[Sequence[float]]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    num_elements: int | Sequence[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType | Literal["dataclass", "dict"] = OutputType.dataclass,
    return_intermediates: bool = False,
    font: str | Path | ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
    ratio_list: Sequence[float] | None = None,
    text_spacing: int | None = None,
    candi_res: int | None = None,
    saliency: ImageInput
    | Sequence[ImageInput]
    | Float[torch.Tensor, "batch height width"]
    | None = None,
    candidate_boxes: Sequence[CandidateBoxRow]
    | Sequence[Sequence[CandidateBoxRow]]
    | None = None,
    return_text_lines: bool = False,
    score_normalization: Literal["mos", "raw"] = "mos",
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | str
            | list[int]
            | list[SmartTextCandidate]
            | None,
        ]
        | None,
    ]
):
    """Generate text placement boxes for content images.

    Args:
        images: RGB image or image batch.
        content: Optional content carrier.
        prompt: Prompt text payload.
        text: Alias for prompt.
        batch_size: Expected batch size for validation.
        seed: Optional seed used only when ``generator`` is absent.
        generator: Explicit torch generator; wins over ``seed``.
        condition_type: Must normalize to ``content_image``.
        labels: Unsupported v1 compatibility argument.
        bbox: Unsupported v1 compatibility argument.
        mask: Unsupported v1 compatibility argument.
        num_elements: Unsupported v1 compatibility argument.
        box_format: Public v1 compatibility argument.
        normalized: Public v1 compatibility argument.
        canvas_size: Optional source canvas size override.
        num_inference_steps: Unused v1 compatibility argument.
        output_type: ``dataclass`` or ``dict``.
        return_intermediates: Whether to include intermediate payloads.
        font: TrueType font path or PIL font object.
        ratio_list: Optional per-line font ratios.
        text_spacing: Optional text-spacing override.
        candi_res: Optional top-k override.
        saliency: Optional saliency map bypassing BASNet.
        candidate_boxes: Optional reference-style candidates.
        return_text_lines: Return per-line boxes for top candidate.
        score_normalization: ``mos`` or ``raw``.

    Returns:
        Shared layout output.
    """
    del (
        labels,
        bbox,
        mask,
        num_elements,
        box_format,
        normalized,
        canvas_size,
        num_inference_steps,
    )
    normalize_condition_type(condition_type)
    self.prepare_generator(generator=generator, seed=seed, device=self.device)
    if font is None:
        font = ImageFont.load_default()
    effective_config = self.config
    if text_spacing is not None:
        effective_config = copy.copy(self.config)
        effective_config.text_spacing = int(text_spacing)
    encoded = self.processor(
        images,
        content=content,
        prompt=prompt,
        text=text,
        saliency=saliency,
        candidate_boxes=candidate_boxes,
        font=font,
    )
    if len(encoded["images"]) != 1 or batch_size != 1:
        raise ValueError("SmartText currently decodes one image at a time")

    image = encoded["images"][0]
    prompt_text = encoded["prompts"][0]
    resolved_saliency = encoded["saliency"]
    if resolved_saliency is None:
        basnet_values = encoded["basnet_pixel_values"].to(self._runtime_device())
        saliency_out = self.saliency_model(basnet_values)
        resolved_saliency = saliency_out.saliency[0]
    candidates = encoded["candidate_boxes"]
    if candidates is None:
        candidates = generate_candidates(
            image,
            cast(torch.Tensor, resolved_saliency),
            prompt=prompt_text,
            font=font,
            config=effective_config,
            ratio_list=ratio_list,
        )
    pixel_values, boxes, candidates = prepare_scorer_batch(
        image,
        candidates,
        config=effective_config,
    )
    scores = self.scorer(
        pixel_values.to(self._runtime_device()),
        boxes.to(self._runtime_device()),
    ).scores
    top_k = candi_res or self.config.candi_res
    raw_scores = scores.detach().cpu().float().flatten()
    if candidates:
        selected_color_index = max(
            range(len(candidates)), key=lambda index: float(raw_scores[index])
        )
        text_color = choose_text_color(
            image,
            candidates[selected_color_index].bbox_ltrb_px,
            contrast_threshold=self.config.contrast_threshold,
        )
    else:
        text_color = None
    intermediates = None
    if return_intermediates:
        intermediates = {
            "saliency": resolved_saliency,
            "raw_scorer_boxes": boxes.detach().cpu(),
            "prompt": prompt_text,
        }
    return self.processor.decode(
        candidates=candidates,
        scores=scores,
        image_size=image.size,
        output_type=cast(Literal["dataclass", "dict"], str(output_type)),
        return_text_lines=return_text_lines,
        top_k=top_k,
        score_normalization=score_normalization,
        text_color=text_color,
        intermediates=intermediates,
    )

SmartTextProcessor

Bases: ProcessorMixin

Normalize SmartText content payloads and decode candidate scores.

Parameters:

Name Type Description Default
image_processor SmartTextImageProcessor | None

Image processor for RGB and BASNet tensors.

None
config SmartTextConfig

SmartText configuration.

required

Examples:

>>> processor = SmartTextProcessor(config=SmartTextConfig())
>>> processor.id2label
{0: 'text'}
Source code in models/smarttext/src/smarttext/processing_smarttext.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 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
class SmartTextProcessor(ProcessorMixin):
    """Normalize SmartText content payloads and decode candidate scores.

    Args:
        image_processor: Image processor for RGB and BASNet tensors.
        config: SmartText configuration.

    Examples:
        >>> processor = SmartTextProcessor(config=SmartTextConfig())
        >>> processor.id2label
        {0: 'text'}
    """

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

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

    def save_pretrained(
        self,
        save_directory: str | Path,
        push_to_hub: bool = False,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Save processor metadata and image-processor config.

        Args:
            save_directory: Directory receiving ``processor_config.json`` and
                ``preprocessor_config.json``.
            push_to_hub: Accepted for ``ProcessorMixin`` compatibility; Hub
                upload is handled outside this helper.
            kwargs: Accepted for ``ProcessorMixin`` compatibility.
        """
        del push_to_hub, kwargs
        root = Path(save_directory)
        root.mkdir(parents=True, exist_ok=True)
        payload = {
            "processor_class": self.__class__.__name__,
            "id2label": self.id2label,
            "config": self.config.to_dict(),
        }
        (root / self.config_name).write_text(
            json.dumps(payload, indent=2, sort_keys=True),
            encoding="utf-8",
        )
        self.image_processor.save_pretrained(root)

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

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

        Returns:
            Loaded SmartText processor.
        """
        del cache_dir, force_download, local_files_only, token, revision, kwargs
        root = Path(pretrained_model_name_or_path)
        if subfolder is not None:
            root = root / subfolder
        payload = json.loads((root / cls.config_name).read_text(encoding="utf-8"))
        config = SmartTextConfig.from_dict(payload.get("config", {}))
        image_processor = SmartTextImageProcessor.from_pretrained(root)
        return cls(
            image_processor=image_processor,
            config=config,
            id2label=payload.get("id2label"),
        )

    def __call__(
        self,
        images: ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch channels height width"]
        | None = None,
        *,
        content: Mapping[
            str,
            ImageInput
            | Sequence[ImageInput]
            | str
            | Sequence[str]
            | Float[torch.Tensor, "batch height width"]
            | Sequence[CandidateBoxRow]
            | Sequence[Sequence[CandidateBoxRow]],
        ]
        | None = None,
        prompt: str | Sequence[str] | None = None,
        text: str | Sequence[str] | None = None,
        saliency: ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch height width"]
        | None = None,
        candidate_boxes: Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]]
        | None = None,
        font: str | Path | ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
        return_tensors: Literal["pt"] = "pt",
        **kwargs: str | int | float | bool | None,
    ) -> BatchEncoding:
        """Encode SmartText public inputs.

        Args:
            images: Image or image batch.
            content: Optional content carrier with ``image``, ``texts``,
                ``saliency``, ``canvas_size``, and ``metadata`` fields.
            prompt: Prompt text payload.
            text: Alias for prompt text.
            saliency: Optional saliency map.
            candidate_boxes: Optional reference-style candidate rows.
            font: Font path or PIL font object.
            return_tensors: Tensor framework. Only ``pt`` is supported.
            kwargs: Ignored forward-compatibility kwargs.

        Returns:
            Batch encoding containing normalized payloads.
        """
        del kwargs
        if return_tensors != "pt":
            raise ValueError("SmartTextProcessor only supports return_tensors='pt'")

        content = dict(content or {})
        resolved_images = images
        if resolved_images is None:
            resolved_images = content.get("image")
        if resolved_images is None:
            resolved_images = content.get("images")
        if resolved_images is None:
            raise ValueError("SmartText requires an image/content payload")

        image_rows = _ensure_image_list(
            cast(
                ImageInput
                | Sequence[ImageInput]
                | Float[torch.Tensor, "batch channels height width"],
                resolved_images,
            )
        )
        prompt_rows = _resolve_prompt_rows(
            prompt=prompt,
            text=text,
            content=content,
            batch_size=len(image_rows),
        )
        encoded = self.image_processor.preprocess(
            image_rows, return_tensors=return_tensors
        )
        basnet = self.image_processor.preprocess_basnet(
            image_rows, return_tensors=return_tensors
        )
        saliency_payload = saliency if saliency is not None else content.get("saliency")
        candidates = _decode_candidate_payload(candidate_boxes)
        encoded.update(
            {
                "basnet_pixel_values": basnet["basnet_pixel_values"],
                "images": image_rows,
                "prompts": prompt_rows,
                "font": font,
                "saliency": saliency_payload,
                "candidate_boxes": candidates,
            }
        )
        return BatchEncoding(encoded)

    def decode(
        self,
        *,
        candidates: Sequence[SmartTextCandidate],
        scores: Float[torch.Tensor, "candidates"],
        image_size: tuple[int, int],
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_text_lines: bool = False,
        top_k: int = 3,
        score_normalization: Literal["mos", "raw"] = "mos",
        text_color: str | None = None,
        intermediates: dict[
            str,
            Shaped[torch.Tensor, "..."]
            | str
            | list[SmartTextCandidate]
            | list[int]
            | None,
        ]
        | None = None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | dict[int, str]
            | dict[
                str,
                Shaped[torch.Tensor, "..."]
                | str
                | list[SmartTextCandidate]
                | list[int]
                | None,
            ]
            | None,
        ]
    ):
        """Decode sorted candidates into the shared layout schema.

        Args:
            candidates: Candidate metadata.
            scores: Raw scorer outputs.
            image_size: Source image size as ``(width, height)``.
            output_type: Return dataclass or dict.
            return_text_lines: Return per-line boxes instead of top-level boxes.
            top_k: Number of top candidates to return.
            score_normalization: ``mos`` or ``raw`` score mode.
            text_color: Optional selected text color.
            intermediates: Optional extra intermediate payload.

        Returns:
            Shared ``LayoutGenerationOutput`` or dictionary.
        """
        if not candidates:
            raise ValueError("SmartText cannot decode an empty candidate list")

        raw_scores = scores.detach().cpu().float().flatten()
        order = sorted(
            range(len(candidates)),
            key=lambda index: float(raw_scores[index]),
            reverse=True,
        )
        selected = [candidates[index] for index in order[:top_k]]
        selected_indexes = order[:top_k]
        if return_text_lines:
            rows = [line.bbox_ltrb_px for line in selected[0].lines]
            selected_scores = raw_scores.new_full(
                (len(rows),),
                float(raw_scores[selected_indexes[0]].item()),
            )
        else:
            rows = [candidate.bbox_ltrb_px for candidate in selected]
            selected_scores = raw_scores[selected_indexes]
        bbox_ltrb = torch.tensor(rows, dtype=torch.float32).unsqueeze(0)
        bbox = normalize_boxes(bbox_ltrb, canvas_size=image_size, box_format="ltrb")
        labels = torch.zeros((1, bbox.shape[1]), dtype=torch.long)
        mask = torch.ones((1, bbox.shape[1]), dtype=torch.bool)
        public_scores = selected_scores
        if score_normalization == "mos":
            public_scores = public_scores * self.config.mos_std + self.config.mos_mean
        elif score_normalization != "raw":
            raise ValueError(f"Unsupported score_normalization: {score_normalization}")

        merged_intermediates = dict(intermediates or {})
        merged_intermediates.update(
            {
                "candidates": list(candidates),
                "selected_indexes": selected_indexes,
                "score_normalization": score_normalization,
            }
        )
        if text_color is not None:
            merged_intermediates["text_color"] = text_color
        output = LayoutGenerationOutput(
            bbox=bbox,
            labels=labels,
            mask=mask,
            id2label=dict(self.id2label),
            scores=public_scores.unsqueeze(0),
            intermediates=merged_intermediates,
        )
        if output_type == "dict":
            return dict(output)
        if output_type == "dataclass":
            return output
        raise ValueError(f"Unsupported output_type: {output_type}")

__init__

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

Initialize processor.

Source code in models/smarttext/src/smarttext/processing_smarttext.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def __init__(
    self,
    *,
    image_processor: SmartTextImageProcessor | None = None,
    config: SmartTextConfig,
    id2label: Mapping[int | str, str] | None = None,
) -> None:
    """Initialize processor."""
    self.config = config
    self.image_processor = image_processor or SmartTextImageProcessor.from_config(
        self.config
    )
    label_source = (
        id2label
        if id2label is not None
        else cast(dict[int, str], self.config.id2label)
    )
    self.id2label = {int(k): v for k, v in label_source.items()}
    self.chat_template = None

save_pretrained

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

Save processor metadata and image-processor config.

Parameters:

Name Type Description Default
save_directory str | Path

Directory receiving processor_config.json and preprocessor_config.json.

required
push_to_hub bool

Accepted for ProcessorMixin compatibility; Hub upload is handled outside this helper.

False
kwargs str | int | float | bool | None

Accepted for ProcessorMixin compatibility.

{}
Source code in models/smarttext/src/smarttext/processing_smarttext.py
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
def save_pretrained(
    self,
    save_directory: str | Path,
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Save processor metadata and image-processor config.

    Args:
        save_directory: Directory receiving ``processor_config.json`` and
            ``preprocessor_config.json``.
        push_to_hub: Accepted for ``ProcessorMixin`` compatibility; Hub
            upload is handled outside this helper.
        kwargs: Accepted for ``ProcessorMixin`` compatibility.
    """
    del push_to_hub, kwargs
    root = Path(save_directory)
    root.mkdir(parents=True, exist_ok=True)
    payload = {
        "processor_class": self.__class__.__name__,
        "id2label": self.id2label,
        "config": self.config.to_dict(),
    }
    (root / self.config_name).write_text(
        json.dumps(payload, indent=2, sort_keys=True),
        encoding="utf-8",
    )
    self.image_processor.save_pretrained(root)

from_pretrained classmethod

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

Load processor metadata from a local checkpoint 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 ProcessorMixin compatibility.

None
force_download bool

Accepted for ProcessorMixin compatibility.

False
local_files_only bool

Accepted for API compatibility.

False
token str | bool | None

Accepted for ProcessorMixin compatibility.

None
revision str

Accepted for ProcessorMixin compatibility.

'main'
subfolder str | None

Optional processor subfolder.

None
kwargs str | int | float | bool | None

Ignored compatibility kwargs.

{}

Returns:

Type Description
Self

Loaded SmartText processor.

Source code in models/smarttext/src/smarttext/processing_smarttext.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    subfolder: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> Self:
    """Load processor metadata from a local checkpoint directory.

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

    Returns:
        Loaded SmartText processor.
    """
    del cache_dir, force_download, local_files_only, token, revision, kwargs
    root = Path(pretrained_model_name_or_path)
    if subfolder is not None:
        root = root / subfolder
    payload = json.loads((root / cls.config_name).read_text(encoding="utf-8"))
    config = SmartTextConfig.from_dict(payload.get("config", {}))
    image_processor = SmartTextImageProcessor.from_pretrained(root)
    return cls(
        image_processor=image_processor,
        config=config,
        id2label=payload.get("id2label"),
    )

__call__

__call__(
    images: ImageInput
    | Sequence[ImageInput]
    | Float[Tensor, "batch channels height width"]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | str
        | Sequence[str]
        | Float[Tensor, "batch height width"]
        | Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    text: str | Sequence[str] | None = None,
    saliency: ImageInput
    | Sequence[ImageInput]
    | Float[Tensor, "batch height width"]
    | None = None,
    candidate_boxes: Sequence[CandidateBoxRow]
    | Sequence[Sequence[CandidateBoxRow]]
    | None = None,
    font: str
    | Path
    | FreeTypeFont
    | ImageFont
    | None = None,
    return_tensors: Literal["pt"] = "pt",
    **kwargs: str | int | float | bool | None,
) -> BatchEncoding

Encode SmartText public inputs.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput] | Float[Tensor, 'batch channels height width'] | None

Image or image batch.

None
content Mapping[str, ImageInput | Sequence[ImageInput] | str | Sequence[str] | Float[Tensor, 'batch height width'] | Sequence[CandidateBoxRow] | Sequence[Sequence[CandidateBoxRow]]] | None

Optional content carrier with image, texts, saliency, canvas_size, and metadata fields.

None
prompt str | Sequence[str] | None

Prompt text payload.

None
text str | Sequence[str] | None

Alias for prompt text.

None
saliency ImageInput | Sequence[ImageInput] | Float[Tensor, 'batch height width'] | None

Optional saliency map.

None
candidate_boxes Sequence[CandidateBoxRow] | Sequence[Sequence[CandidateBoxRow]] | None

Optional reference-style candidate rows.

None
font str | Path | FreeTypeFont | ImageFont | None

Font path or PIL font object.

None
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

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

Ignored forward-compatibility kwargs.

{}

Returns:

Type Description
BatchEncoding

Batch encoding containing normalized payloads.

Source code in models/smarttext/src/smarttext/processing_smarttext.py
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
def __call__(
    self,
    images: ImageInput
    | Sequence[ImageInput]
    | Float[torch.Tensor, "batch channels height width"]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | str
        | Sequence[str]
        | Float[torch.Tensor, "batch height width"]
        | Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    text: str | Sequence[str] | None = None,
    saliency: ImageInput
    | Sequence[ImageInput]
    | Float[torch.Tensor, "batch height width"]
    | None = None,
    candidate_boxes: Sequence[CandidateBoxRow]
    | Sequence[Sequence[CandidateBoxRow]]
    | None = None,
    font: str | Path | ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
    return_tensors: Literal["pt"] = "pt",
    **kwargs: str | int | float | bool | None,
) -> BatchEncoding:
    """Encode SmartText public inputs.

    Args:
        images: Image or image batch.
        content: Optional content carrier with ``image``, ``texts``,
            ``saliency``, ``canvas_size``, and ``metadata`` fields.
        prompt: Prompt text payload.
        text: Alias for prompt text.
        saliency: Optional saliency map.
        candidate_boxes: Optional reference-style candidate rows.
        font: Font path or PIL font object.
        return_tensors: Tensor framework. Only ``pt`` is supported.
        kwargs: Ignored forward-compatibility kwargs.

    Returns:
        Batch encoding containing normalized payloads.
    """
    del kwargs
    if return_tensors != "pt":
        raise ValueError("SmartTextProcessor only supports return_tensors='pt'")

    content = dict(content or {})
    resolved_images = images
    if resolved_images is None:
        resolved_images = content.get("image")
    if resolved_images is None:
        resolved_images = content.get("images")
    if resolved_images is None:
        raise ValueError("SmartText requires an image/content payload")

    image_rows = _ensure_image_list(
        cast(
            ImageInput
            | Sequence[ImageInput]
            | Float[torch.Tensor, "batch channels height width"],
            resolved_images,
        )
    )
    prompt_rows = _resolve_prompt_rows(
        prompt=prompt,
        text=text,
        content=content,
        batch_size=len(image_rows),
    )
    encoded = self.image_processor.preprocess(
        image_rows, return_tensors=return_tensors
    )
    basnet = self.image_processor.preprocess_basnet(
        image_rows, return_tensors=return_tensors
    )
    saliency_payload = saliency if saliency is not None else content.get("saliency")
    candidates = _decode_candidate_payload(candidate_boxes)
    encoded.update(
        {
            "basnet_pixel_values": basnet["basnet_pixel_values"],
            "images": image_rows,
            "prompts": prompt_rows,
            "font": font,
            "saliency": saliency_payload,
            "candidate_boxes": candidates,
        }
    )
    return BatchEncoding(encoded)

decode

decode(
    *,
    candidates: Sequence[SmartTextCandidate],
    scores: Float[Tensor, "candidates"],
    image_size: tuple[int, int],
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_text_lines: bool = False,
    top_k: int = 3,
    score_normalization: Literal["mos", "raw"] = "mos",
    text_color: str | None = None,
    intermediates: dict[
        str,
        Shaped[Tensor, "..."]
        | str
        | list[SmartTextCandidate]
        | list[int]
        | None,
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | str
            | list[SmartTextCandidate]
            | list[int]
            | None,
        ]
        | None,
    ]
)

Decode sorted candidates into the shared layout schema.

Parameters:

Name Type Description Default
candidates Sequence[SmartTextCandidate]

Candidate metadata.

required
scores Float[Tensor, 'candidates']

Raw scorer outputs.

required
image_size tuple[int, int]

Source image size as (width, height).

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

Return dataclass or dict.

'dataclass'
return_text_lines bool

Return per-line boxes instead of top-level boxes.

False
top_k int

Number of top candidates to return.

3
score_normalization Literal['mos', 'raw']

mos or raw score mode.

'mos'
text_color str | None

Optional selected text color.

None
intermediates dict[str, Shaped[Tensor, '...'] | str | list[SmartTextCandidate] | list[int] | None] | None

Optional extra intermediate payload.

None

Returns:

Type Description
LayoutGenerationOutput | dict[str, Shaped[Tensor, '...'] | dict[int, str] | dict[str, Shaped[Tensor, '...'] | str | list[SmartTextCandidate] | list[int] | None] | None]

Shared LayoutGenerationOutput or dictionary.

Source code in models/smarttext/src/smarttext/processing_smarttext.py
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
def decode(
    self,
    *,
    candidates: Sequence[SmartTextCandidate],
    scores: Float[torch.Tensor, "candidates"],
    image_size: tuple[int, int],
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_text_lines: bool = False,
    top_k: int = 3,
    score_normalization: Literal["mos", "raw"] = "mos",
    text_color: str | None = None,
    intermediates: dict[
        str,
        Shaped[torch.Tensor, "..."]
        | str
        | list[SmartTextCandidate]
        | list[int]
        | None,
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | str
            | list[SmartTextCandidate]
            | list[int]
            | None,
        ]
        | None,
    ]
):
    """Decode sorted candidates into the shared layout schema.

    Args:
        candidates: Candidate metadata.
        scores: Raw scorer outputs.
        image_size: Source image size as ``(width, height)``.
        output_type: Return dataclass or dict.
        return_text_lines: Return per-line boxes instead of top-level boxes.
        top_k: Number of top candidates to return.
        score_normalization: ``mos`` or ``raw`` score mode.
        text_color: Optional selected text color.
        intermediates: Optional extra intermediate payload.

    Returns:
        Shared ``LayoutGenerationOutput`` or dictionary.
    """
    if not candidates:
        raise ValueError("SmartText cannot decode an empty candidate list")

    raw_scores = scores.detach().cpu().float().flatten()
    order = sorted(
        range(len(candidates)),
        key=lambda index: float(raw_scores[index]),
        reverse=True,
    )
    selected = [candidates[index] for index in order[:top_k]]
    selected_indexes = order[:top_k]
    if return_text_lines:
        rows = [line.bbox_ltrb_px for line in selected[0].lines]
        selected_scores = raw_scores.new_full(
            (len(rows),),
            float(raw_scores[selected_indexes[0]].item()),
        )
    else:
        rows = [candidate.bbox_ltrb_px for candidate in selected]
        selected_scores = raw_scores[selected_indexes]
    bbox_ltrb = torch.tensor(rows, dtype=torch.float32).unsqueeze(0)
    bbox = normalize_boxes(bbox_ltrb, canvas_size=image_size, box_format="ltrb")
    labels = torch.zeros((1, bbox.shape[1]), dtype=torch.long)
    mask = torch.ones((1, bbox.shape[1]), dtype=torch.bool)
    public_scores = selected_scores
    if score_normalization == "mos":
        public_scores = public_scores * self.config.mos_std + self.config.mos_mean
    elif score_normalization != "raw":
        raise ValueError(f"Unsupported score_normalization: {score_normalization}")

    merged_intermediates = dict(intermediates or {})
    merged_intermediates.update(
        {
            "candidates": list(candidates),
            "selected_indexes": selected_indexes,
            "score_normalization": score_normalization,
        }
    )
    if text_color is not None:
        merged_intermediates["text_color"] = text_color
    output = LayoutGenerationOutput(
        bbox=bbox,
        labels=labels,
        mask=mask,
        id2label=dict(self.id2label),
        scores=public_scores.unsqueeze(0),
        intermediates=merged_intermediates,
    )
    if output_type == "dict":
        return dict(output)
    if output_type == "dataclass":
        return output
    raise ValueError(f"Unsupported output_type: {output_type}")

candidate_generation

Candidate generation helpers for SmartText text placement.

CandidateBoxRow

Bases: TypedDict

JSON-compatible candidate row.

Source code in models/smarttext/src/smarttext/candidate_generation.py
21
22
23
24
25
26
27
28
29
30
31
class CandidateBoxRow(TypedDict, total=False):
    """JSON-compatible candidate row."""

    idx: int
    xl: int
    yl: int
    xr: int
    yr: int
    tl_cnt: int
    fsz: int
    fontstr: str

SmartTextLine dataclass

One rendered prompt line within a candidate region.

Source code in models/smarttext/src/smarttext/candidate_generation.py
34
35
36
37
38
39
40
@dataclass(frozen=True)
class SmartTextLine:
    """One rendered prompt line within a candidate region."""

    text: str
    font_size: int
    bbox_ltrb_px: tuple[int, int, int, int]

SmartTextCandidate dataclass

A candidate text block and its line-level boxes.

Source code in models/smarttext/src/smarttext/candidate_generation.py
43
44
45
46
47
48
49
@dataclass(frozen=True)
class SmartTextCandidate:
    """A candidate text block and its line-level boxes."""

    index: int
    bbox_ltrb_px: tuple[int, int, int, int]
    lines: tuple[SmartTextLine, ...]

split_prompt_lines

split_prompt_lines(
    prompt: str, ratio_list: Sequence[float]
) -> tuple[str, ...]

Split prompt text into non-empty lines.

Parameters:

Name Type Description Default
prompt str

User text payload.

required
ratio_list Sequence[float]

Font-size ratio list. The argument is accepted here so tests can verify prompt/ratio length behavior at one public boundary.

required

Returns:

Type Description
tuple[str, ...]

Non-empty prompt lines, preserving internal spaces.

Raises:

Type Description
ValueError

If no non-empty line remains.

Examples:

>>> split_prompt_lines("A\\nB", (1.0, 0.8))
('A', 'B')
Source code in models/smarttext/src/smarttext/candidate_generation.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def split_prompt_lines(prompt: str, ratio_list: Sequence[float]) -> tuple[str, ...]:
    r"""Split prompt text into non-empty lines.

    Args:
        prompt: User text payload.
        ratio_list: Font-size ratio list. The argument is accepted here so tests
            can verify prompt/ratio length behavior at one public boundary.

    Returns:
        Non-empty prompt lines, preserving internal spaces.

    Raises:
        ValueError: If no non-empty line remains.

    Examples:
        >>> split_prompt_lines("A\\nB", (1.0, 0.8))
        ('A', 'B')
    """
    del ratio_list
    lines = tuple(line for line in prompt.splitlines() if line.strip())
    if not lines:
        raise ValueError("SmartText requires at least one prompt line")

    return lines

generate_candidates

generate_candidates(
    image: Image,
    saliency: Shaped[ndarray, "..."]
    | Shaped[Tensor, "..."],
    *,
    prompt: str,
    font: str | Path | FreeTypeFont | ImageFont,
    config: SmartTextConfig,
    ratio_list: Sequence[float] | None = None,
) -> list[SmartTextCandidate]

Generate deterministic candidate text boxes from image, saliency, and text.

Parameters:

Name Type Description Default
image Image

Source RGB image.

required
saliency Shaped[ndarray, '...'] | Shaped[Tensor, '...']

Saliency map in image space.

required
prompt str

Text payload split on newlines.

required
font str | Path | FreeTypeFont | ImageFont

TrueType font path or loaded PIL font object.

required
config SmartTextConfig

SmartText configuration.

required
ratio_list Sequence[float] | None

Optional per-line font-size ratios.

None

Returns:

Type Description
list[SmartTextCandidate]

Candidate text regions sorted in deterministic search order.

Examples:

>>> img = Image.new("RGB", (64, 64), "white")
>>> candidates = generate_candidates(
...     img,
...     np.zeros((64, 64), dtype=np.float32),
...     prompt="Hi",
...     font=ImageFont.load_default(),
...     config=SmartTextConfig(grid_num=16, max_font_size=20),
... )
>>> bool(candidates)
True
Source code in models/smarttext/src/smarttext/candidate_generation.py
 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
def generate_candidates(
    image: Image.Image,
    saliency: Shaped[np.ndarray, "..."] | Shaped[torch.Tensor, "..."],
    *,
    prompt: str,
    font: str | Path | ImageFont.FreeTypeFont | ImageFont.ImageFont,
    config: SmartTextConfig,
    ratio_list: Sequence[float] | None = None,
) -> list[SmartTextCandidate]:
    """Generate deterministic candidate text boxes from image, saliency, and text.

    Args:
        image: Source RGB image.
        saliency: Saliency map in image space.
        prompt: Text payload split on newlines.
        font: TrueType font path or loaded PIL font object.
        config: SmartText configuration.
        ratio_list: Optional per-line font-size ratios.

    Returns:
        Candidate text regions sorted in deterministic search order.

    Examples:
        >>> img = Image.new("RGB", (64, 64), "white")
        >>> candidates = generate_candidates(
        ...     img,
        ...     np.zeros((64, 64), dtype=np.float32),
        ...     prompt="Hi",
        ...     font=ImageFont.load_default(),
        ...     config=SmartTextConfig(grid_num=16, max_font_size=20),
        ... )
        >>> bool(candidates)
        True
    """
    saliency_np = _saliency_to_numpy(saliency, image.size)
    lines = split_prompt_lines(prompt, ratio_list or config.ratio_list)
    ratios = _expand_ratios(ratio_list or config.ratio_list, len(lines))
    search_map, grid_rsz, grid_csz = _build_search_map(saliency_np, config)
    width, height = image.size
    min_text_area = height * width / config.max_text_area_coef
    max_text_area = height * width / config.min_text_area_coef
    draw = ImageDraw.Draw(image.copy())

    candidates: list[SmartTextCandidate] = []
    index = 0
    for font_size in range(
        config.min_font_size,
        config.max_font_size + 1,
        config.font_inc_unit,
    ):
        line_sizes = [
            _text_size(
                draw,
                text,
                _resolve_font(font, max(1, int(font_size * ratio))),
                config.text_spacing,
            )
            for text, ratio in zip(lines, ratios, strict=True)
        ]
        block_width = max((line_width for line_width, _ in line_sizes), default=0)
        block_height = sum(line_height for _, line_height in line_sizes)
        block_height += config.text_spacing * max(0, len(line_sizes) - 1)
        area = block_width * block_height
        if (
            area > max_text_area
            or area < min_text_area
            or block_width >= width
            or block_height >= height
        ):
            continue
        kernel = (
            max(1, int(block_height / grid_rsz)),
            max(1, int(block_width / grid_csz)),
        )
        for row, col in _top_non_overlapping(search_map, kernel, k=1):
            top = row * grid_rsz
            left = col * grid_csz
            right = left + block_width
            bottom = top + block_height
            if right >= width or bottom >= height:
                continue
            line_rows: list[SmartTextLine] = []
            cursor_top = top
            for text, ratio, (line_width, line_height) in zip(
                lines,
                ratios,
                line_sizes,
                strict=True,
            ):
                line_font_size = max(1, int(font_size * ratio))
                line_rows.append(
                    SmartTextLine(
                        text=text,
                        font_size=line_font_size,
                        bbox_ltrb_px=(
                            left,
                            cursor_top,
                            left + line_width,
                            cursor_top + line_height,
                        ),
                    )
                )
                cursor_top += line_height + config.text_spacing
            candidates.append(
                SmartTextCandidate(
                    index=index,
                    bbox_ltrb_px=(left, top, right, bottom),
                    lines=tuple(line_rows),
                )
            )
            index += 1
    return candidates

prepare_scorer_batch

prepare_scorer_batch(
    image: Image,
    candidates: Sequence[SmartTextCandidate],
    *,
    config: SmartTextConfig,
) -> tuple[
    Float[torch.Tensor, "batch channels height width"],
    Float[torch.Tensor, "candidates 5"],
    list[SmartTextCandidate],
]

Prepare scorer image tensor and RoI/RoD boxes.

Parameters:

Name Type Description Default
image Image

Source image.

required
candidates Sequence[SmartTextCandidate]

Candidate boxes.

required
config SmartTextConfig

SmartText configuration.

required

Returns:

Type Description
Float[Tensor, 'batch channels height width']

pixel_values shaped (1, 3, H, W), boxes shaped (N, 5) with

Float[Tensor, 'candidates 5']

batch index in column zero, and the candidate list.

Source code in models/smarttext/src/smarttext/candidate_generation.py
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
def prepare_scorer_batch(
    image: Image.Image,
    candidates: Sequence[SmartTextCandidate],
    *,
    config: SmartTextConfig,
) -> tuple[
    Float[torch.Tensor, "batch channels height width"],
    Float[torch.Tensor, "candidates 5"],
    list[SmartTextCandidate],
]:
    """Prepare scorer image tensor and RoI/RoD boxes.

    Args:
        image: Source image.
        candidates: Candidate boxes.
        config: SmartText configuration.

    Returns:
        ``pixel_values`` shaped ``(1, 3, H, W)``, boxes shaped ``(N, 5)`` with
        batch index in column zero, and the candidate list.
    """
    if config.uses_expanded_region:
        return _prepare_expanded_scorer_batch(image, candidates, config=config)
    pixel_values = _preprocess_scorer_image(image, config)
    width, height = image.size
    scale_x = pixel_values.shape[-1] / width
    scale_y = pixel_values.shape[-2] / height
    box_rows = []
    for candidate in candidates:
        left, top, right, bottom = candidate.bbox_ltrb_px
        box_rows.append(
            [
                0.0,
                float(left) * scale_x,
                float(top) * scale_y,
                float(right) * scale_x,
                float(bottom) * scale_y,
            ]
        )
    boxes = torch.tensor(box_rows, dtype=torch.float32)
    return pixel_values, boxes, list(candidates)

candidate_to_reference_json

candidate_to_reference_json(
    candidate: SmartTextCandidate,
) -> list[CandidateBoxRow]

Convert a candidate to the reference JSON row format.

Source code in models/smarttext/src/smarttext/candidate_generation.py
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
def candidate_to_reference_json(candidate: SmartTextCandidate) -> list[CandidateBoxRow]:
    """Convert a candidate to the reference JSON row format."""
    left, top, right, bottom = candidate.bbox_ltrb_px
    rows: list[CandidateBoxRow] = [
        {
            "idx": candidate.index,
            "xl": top,
            "yl": left,
            "xr": bottom,
            "yr": right,
            "tl_cnt": len(candidate.lines),
        }
    ]
    for line in candidate.lines:
        l_left, l_top, l_right, l_bottom = line.bbox_ltrb_px
        rows.append(
            {
                "xl": l_top,
                "yl": l_left,
                "xr": l_bottom,
                "yr": l_right,
                "fsz": line.font_size,
                "fontstr": line.text,
            }
        )
    return rows

candidate_from_reference_json

candidate_from_reference_json(
    row: Sequence[CandidateBoxRow],
) -> SmartTextCandidate

Convert one reference JSON candidate row to typed metadata.

Source code in models/smarttext/src/smarttext/candidate_generation.py
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
def candidate_from_reference_json(
    row: Sequence[CandidateBoxRow],
) -> SmartTextCandidate:
    """Convert one reference JSON candidate row to typed metadata."""
    head = row[0]
    lines = []
    for line in row[1:]:
        lines.append(
            SmartTextLine(
                text=str(line["fontstr"]),
                font_size=int(cast(int | float | str, line["fsz"])),
                bbox_ltrb_px=(
                    int(cast(int | float | str, line["yl"])),
                    int(cast(int | float | str, line["xl"])),
                    int(cast(int | float | str, line["yr"])),
                    int(cast(int | float | str, line["xr"])),
                ),
            )
        )
    return SmartTextCandidate(
        index=int(cast(int | float | str, head["idx"])),
        bbox_ltrb_px=(
            int(cast(int | float | str, head["yl"])),
            int(cast(int | float | str, head["xl"])),
            int(cast(int | float | str, head["yr"])),
            int(cast(int | float | str, head["xr"])),
        ),
        lines=tuple(lines),
    )

color

Text color helpers ported from the SmartText demo path.

SmartTextColorCandidate

Bases: TypedDict

Foreground color candidate and contrast score.

Source code in models/smarttext/src/smarttext/color.py
20
21
22
23
24
class SmartTextColorCandidate(TypedDict):
    """Foreground color candidate and contrast score."""

    color: Shaped[np.ndarray, "channels"] | list[float] | list[int]
    contrast_rate: float

dominant_colors

dominant_colors(
    image: Shaped[ndarray, "height width channels"],
    clusters: int,
) -> list[Shaped[np.ndarray, "channels"]]

Return reference-sorted KMeans dominant colors.

Parameters:

Name Type Description Default
image Shaped[ndarray, 'height width channels']

RGB image array.

required
clusters int

Number of KMeans clusters.

required

Returns:

Type Description
list[Shaped[ndarray, 'channels']]

Cluster centers sorted by RGB tuple, matching

list[Shaped[ndarray, 'channels']]

the original cal_color.py::cal_domcolor.

Source code in models/smarttext/src/smarttext/color.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def dominant_colors(
    image: Shaped[np.ndarray, "height width channels"], clusters: int
) -> list[Shaped[np.ndarray, "channels"]]:
    """Return reference-sorted KMeans dominant colors.

    Args:
        image: RGB image array.
        clusters: Number of KMeans clusters.

    Returns:
        Cluster centers sorted by RGB tuple, matching
        the original ``cal_color.py::cal_domcolor``.
    """
    pixels = image.reshape((image.shape[0] * image.shape[1], image.shape[2]))
    estimator = KMeans(n_clusters=clusters, max_iter=300, n_init=2)
    with threadpool_limits(limits=1):
        estimator.fit(pixels)
    return sorted(estimator.cluster_centers_, key=lambda row: (row[0], row[1], row[2]))

rgb_distance

rgb_distance(
    rgb: Shaped[ndarray, "channels"]
    | list[float]
    | list[int],
) -> float

Return the channel-spread distance.

Source code in models/smarttext/src/smarttext/color.py
47
48
49
50
51
def rgb_distance(
    rgb: Shaped[np.ndarray, "channels"] | list[float] | list[int],
) -> float:
    """Return the channel-spread distance."""
    return abs(rgb[0] - rgb[1]) + abs(rgb[0] - rgb[2]) + abs(rgb[2] - rgb[1])

rgb_to_hex

rgb_to_hex(
    rgb: Shaped[ndarray, "channels"]
    | list[float]
    | list[int],
) -> str

Convert an RGB row to the uppercase hex form.

Source code in models/smarttext/src/smarttext/color.py
54
55
56
57
58
59
def rgb_to_hex(rgb: Shaped[np.ndarray, "channels"] | list[float] | list[int]) -> str:
    """Convert an RGB row to the uppercase hex form."""
    color = "#"
    for channel in rgb:
        color += str(hex(int(channel)))[-2:].replace("x", "0").upper()
    return color

luminance

luminance(rgb: list[float]) -> float

Return WCAG relative luminance using the reference formula.

Source code in models/smarttext/src/smarttext/color.py
62
63
64
65
66
67
68
69
70
def luminance(rgb: list[float]) -> float:
    """Return WCAG relative luminance using the reference formula."""
    values = list(rgb)
    for index in range(len(values)):
        if values[index] <= 0.03928:
            values[index] = values[index] / 12.92
        else:
            values[index] = pow(((values[index] + 0.055) / 1.055), 2.4)
    return 0.2126 * values[0] + 0.7152 * values[1] + 0.0722 * values[2]

contrast_rate

contrast_rate(
    rgb_a: Shaped[ndarray, "channels"]
    | list[float]
    | list[int],
    rgb_b: Shaped[ndarray, "channels"]
    | list[float]
    | list[int],
) -> float

Return reference-rounded contrast ratio between two RGB colors.

Source code in models/smarttext/src/smarttext/color.py
73
74
75
76
77
78
79
80
81
82
83
84
def contrast_rate(
    rgb_a: Shaped[np.ndarray, "channels"] | list[float] | list[int],
    rgb_b: Shaped[np.ndarray, "channels"] | list[float] | list[int],
) -> float:
    """Return reference-rounded contrast ratio between two RGB colors."""
    l1 = luminance([rgb_a[0] / 255, rgb_a[1] / 255, rgb_a[2] / 255])
    l2 = luminance([rgb_b[0] / 255, rgb_b[1] / 255, rgb_b[2] / 255])
    if l1 >= l2:
        ratio = (l1 + 0.05) / (l2 + 0.05)
    else:
        ratio = (l2 + 0.05) / (l1 + 0.05)
    return round(ratio * 100) / 100

best_color_candidates

best_color_candidates(
    image: Image | Shaped[ndarray, "height width channels"],
    crop: Shaped[
        ndarray, "crop_height crop_width channels"
    ],
    *,
    contrast_threshold: float,
    random_seed: int | None = 0,
) -> list[SmartTextColorCandidate]

Return reference-ordered color candidates for a text region.

Parameters:

Name Type Description Default
image Image | Shaped[ndarray, 'height width channels']

Source RGB image.

required
crop Shaped[ndarray, 'crop_height crop_width channels']

Candidate crop from image.

required
contrast_threshold float

Minimum contrast ratio accepted before fallback.

required
random_seed int | None

Seed used to make the reference KMeans path deterministic.

0

Returns:

Type Description
list[SmartTextColorCandidate]

Candidate dictionaries with color and contrast_rate keys.

Source code in models/smarttext/src/smarttext/color.py
 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
def best_color_candidates(
    image: Image.Image | Shaped[np.ndarray, "height width channels"],
    crop: Shaped[np.ndarray, "crop_height crop_width channels"],
    *,
    contrast_threshold: float,
    random_seed: int | None = 0,
) -> list[SmartTextColorCandidate]:
    """Return reference-ordered color candidates for a text region.

    Args:
        image: Source RGB image.
        crop: Candidate crop from ``image``.
        contrast_threshold: Minimum contrast ratio accepted before fallback.
        random_seed: Seed used to make the reference KMeans path deterministic.

    Returns:
        Candidate dictionaries with ``color`` and ``contrast_rate`` keys.
    """
    state = np.random.get_state() if random_seed is not None else None
    if random_seed is not None:
        np.random.seed(random_seed)
    try:
        return _best_color_candidates_unseeded(
            image, crop, contrast_threshold=contrast_threshold
        )
    finally:
        if state is not None:
            np.random.set_state(state)

choose_text_color

choose_text_color(
    image: Image | Shaped[ndarray, "height width channels"],
    crop_bbox_ltrb_px: tuple[int, int, int, int],
    *,
    contrast_threshold: float,
) -> str

Choose the SmartText foreground for a candidate region.

Parameters:

Name Type Description Default
image Image | Shaped[ndarray, 'height width channels']

Source RGB image.

required
crop_bbox_ltrb_px tuple[int, int, int, int]

Candidate box as (left, top, right, bottom).

required
contrast_threshold float

Threshold above which white text is preferred.

required

Returns:

Type Description
str

Hex foreground color in the uppercase form.

Examples:

>>> choose_text_color(Image.new("RGB", (8, 8), "black"), (0, 0, 8, 8), contrast_threshold=5)
'#FFFFFF'
Source code in models/smarttext/src/smarttext/color.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def choose_text_color(
    image: Image.Image | Shaped[np.ndarray, "height width channels"],
    crop_bbox_ltrb_px: tuple[int, int, int, int],
    *,
    contrast_threshold: float,
) -> str:
    """Choose the SmartText foreground for a candidate region.

    Args:
        image: Source RGB image.
        crop_bbox_ltrb_px: Candidate box as ``(left, top, right, bottom)``.
        contrast_threshold: Threshold above which white text is preferred.

    Returns:
        Hex foreground color in the uppercase form.

    Examples:
        >>> choose_text_color(Image.new("RGB", (8, 8), "black"), (0, 0, 8, 8), contrast_threshold=5)
        '#FFFFFF'
    """
    array = np.asarray(
        image.convert("RGB") if isinstance(image, Image.Image) else image
    )
    left, top, right, bottom = crop_bbox_ltrb_px
    crop = array[top:bottom, left:right]
    if crop.size == 0:
        return "#000000"
    color = best_color_candidates(array, crop, contrast_threshold=contrast_threshold)[
        0
    ]["color"]
    return rgb_to_hex(color)

configuration_smarttext

Configuration objects for SmartText text placement.

The defaults mirror the original test_opt.yml and the scorer settings used by the original smtModel.py::build_smt_model.

SmartTextRegionMode

Bases: StrEnum

Supported SmartText region scoring modes.

Source code in models/smarttext/src/smarttext/configuration_smarttext.py
19
20
21
22
23
class SmartTextRegionMode(StrEnum):
    """Supported SmartText region scoring modes."""

    RoD = "RoD"
    RoE = "RoE"

SmartTextBackbone

Bases: StrEnum

Backbone names supported by the original SmartText scorer.

Source code in models/smarttext/src/smarttext/configuration_smarttext.py
26
27
28
29
30
31
32
class SmartTextBackbone(StrEnum):
    """Backbone names supported by the original SmartText scorer."""

    shufflenetv2 = "shufflenetv2"
    mobilenetv2 = "mobilenetv2"
    vgg16 = "vgg16"
    resnet50 = "resnet50"

SmartTextConfig

Bases: PretrainedConfig

Configuration for SmartText scorer, saliency model, and pipeline.

Parameters:

Name Type Description Default
id2label Mapping[int | str, str] | None

Public label mapping. Defaults to one text label.

None
scorer_scale str

Original scorer scale mode.

'multi'
scorer_backbone SmartTextBackbone | str

Original scorer backbone.

shufflenetv2
align_size int

RoI/RoD pooled spatial size.

9
reduction_dim int

Reduced feature channels before scoring.

8
downsample int

Scorer feature-map downsampling factor.

4
model_type_name SmartTextRegionMode | str

Original RoE/RoD region mode.

RoE
image_size int

Short-side normalization target for scorer preprocessing.

256
ratio_list Sequence[float]

Per-line font-size ratios.

(1.0, 0.8)
text_spacing int

Pixel spacing between prompt lines.

20
exp_prop int

Original expanded-region coefficient.

6
grid_num int

Candidate search grid count.

120
saliency_coef float

Saliency suppression coefficient.

2.6
max_text_area_coef float

Maximum candidate area divisor.

17.0
min_text_area_coef float

Minimum candidate area divisor.

7.0
min_font_size int

Minimum candidate font size.

10
max_font_size int

Maximum candidate font size.

500
font_inc_unit int

Candidate font-size step.

5
candi_res int

Number of selected candidates.

3
contrast_threshold float

Foreground/background contrast threshold.

5.0
mos_mean float

MOS score mean used by the original demo.

2.95
mos_std float

MOS score standard deviation.

0.8
rgb_mean Sequence[float]

RGB normalization mean for scorer inputs.

(0.485, 0.456, 0.406)
rgb_std Sequence[float]

RGB normalization std for scorer inputs.

(0.229, 0.224, 0.225)
scorer_subfolder str

Pipeline scorer subfolder.

'scorer'
saliency_subfolder str

Pipeline saliency-model subfolder.

'saliency_model'
processor_subfolder str

Pipeline processor subfolder.

'processor'
original_options Mapping[str, SmartTextMetadataValue] | None

Raw reference option values preserved for audit.

None
conversion_report Mapping[str, SmartTextMetadataValue | list[str]] | None

Conversion metadata persisted in configs.

None
kwargs str | int | float | bool | None

Extra PretrainedConfig fields.

{}

Examples:

>>> config = SmartTextConfig()
>>> config.id2label
{0: 'text'}
Source code in models/smarttext/src/smarttext/configuration_smarttext.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
class SmartTextConfig(PretrainedConfig):
    """Configuration for SmartText scorer, saliency model, and pipeline.

    Args:
        id2label: Public label mapping. Defaults to one ``text`` label.
        scorer_scale: Original scorer scale mode.
        scorer_backbone: Original scorer backbone.
        align_size: RoI/RoD pooled spatial size.
        reduction_dim: Reduced feature channels before scoring.
        downsample: Scorer feature-map downsampling factor.
        model_type_name: Original ``RoE``/``RoD`` region mode.
        image_size: Short-side normalization target for scorer preprocessing.
        ratio_list: Per-line font-size ratios.
        text_spacing: Pixel spacing between prompt lines.
        exp_prop: Original expanded-region coefficient.
        grid_num: Candidate search grid count.
        saliency_coef: Saliency suppression coefficient.
        max_text_area_coef: Maximum candidate area divisor.
        min_text_area_coef: Minimum candidate area divisor.
        min_font_size: Minimum candidate font size.
        max_font_size: Maximum candidate font size.
        font_inc_unit: Candidate font-size step.
        candi_res: Number of selected candidates.
        contrast_threshold: Foreground/background contrast threshold.
        mos_mean: MOS score mean used by the original demo.
        mos_std: MOS score standard deviation.
        rgb_mean: RGB normalization mean for scorer inputs.
        rgb_std: RGB normalization std for scorer inputs.
        scorer_subfolder: Pipeline scorer subfolder.
        saliency_subfolder: Pipeline saliency-model subfolder.
        processor_subfolder: Pipeline processor subfolder.
        original_options: Raw reference option values preserved for audit.
        conversion_report: Conversion metadata persisted in configs.
        kwargs: Extra ``PretrainedConfig`` fields.

    Examples:
        >>> config = SmartTextConfig()
        >>> config.id2label
        {0: 'text'}
    """

    model_type = "smarttext"

    def __init__(
        self,
        *,
        id2label: Mapping[int | str, str] | None = None,
        scorer_scale: str = "multi",
        scorer_backbone: SmartTextBackbone | str = SmartTextBackbone.shufflenetv2,
        align_size: int = 9,
        reduction_dim: int = 8,
        downsample: int = 4,
        model_type_name: SmartTextRegionMode | str = SmartTextRegionMode.RoE,
        image_size: int = 256,
        ratio_list: Sequence[float] = (1.0, 0.8),
        text_spacing: int = 20,
        exp_prop: int = 6,
        grid_num: int = 120,
        saliency_coef: float = 2.6,
        max_text_area_coef: float = 17.0,
        min_text_area_coef: float = 7.0,
        min_font_size: int = 10,
        max_font_size: int = 500,
        font_inc_unit: int = 5,
        candi_res: int = 3,
        contrast_threshold: float = 5.0,
        mos_mean: float = 2.95,
        mos_std: float = 0.8,
        rgb_mean: Sequence[float] = (0.485, 0.456, 0.406),
        rgb_std: Sequence[float] = (0.229, 0.224, 0.225),
        scorer_subfolder: str = "scorer",
        saliency_subfolder: str = "saliency_model",
        processor_subfolder: str = "processor",
        original_options: Mapping[str, SmartTextMetadataValue] | None = None,
        conversion_report: Mapping[str, SmartTextMetadataValue | list[str]]
        | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize SmartText configuration."""
        raw_id2label = id2label or DEFAULT_ID2LABEL
        normalized_id2label = {int(key): value for key, value in raw_id2label.items()}
        super().__init__(id2label=normalized_id2label, **kwargs)  # ty: ignore[invalid-argument-type]

        self.id2label = normalized_id2label
        self.label2id = {value: key for key, value in self.id2label.items()}

        self.scorer_scale = scorer_scale
        self.scorer_backbone = SmartTextBackbone(scorer_backbone).value
        self.align_size = int(align_size)
        self.reduction_dim = int(reduction_dim)
        self.downsample = int(downsample)

        self.model_type_name = SmartTextRegionMode(model_type_name).value
        self.image_size = int(image_size)
        self.ratio_list = tuple(float(value) for value in ratio_list)
        self.text_spacing = int(text_spacing)
        self.exp_prop = int(exp_prop)

        self.grid_num = int(grid_num)
        self.saliency_coef = float(saliency_coef)
        self.max_text_area_coef = float(max_text_area_coef)
        self.min_text_area_coef = float(min_text_area_coef)
        self.min_font_size = int(min_font_size)
        self.max_font_size = int(max_font_size)
        self.font_inc_unit = int(font_inc_unit)
        self.candi_res = int(candi_res)
        self.contrast_threshold = float(contrast_threshold)
        self.mos_mean = float(mos_mean)
        self.mos_std = float(mos_std)
        self.rgb_mean = tuple(float(value) for value in rgb_mean)
        self.rgb_std = tuple(float(value) for value in rgb_std)

        self.scorer_subfolder = scorer_subfolder
        self.saliency_subfolder = saliency_subfolder
        self.processor_subfolder = processor_subfolder

        self.original_options = dict(original_options or {})
        self.conversion_report = dict(conversion_report or {})

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

    @property
    def uses_expanded_region(self) -> bool:
        """Return whether the original ``RoE`` expanded-region mode is active."""
        return self.model_type_name == SmartTextRegionMode.RoE.value

    @property
    def scorer_input_channels(self) -> int:
        """Return the RGB scorer input channel count."""
        return 3

num_labels property

num_labels: int

Return the number of public semantic labels.

uses_expanded_region property

uses_expanded_region: bool

Return whether the original RoE expanded-region mode is active.

scorer_input_channels property

scorer_input_channels: int

Return the RGB scorer input channel count.

__init__

__init__(
    *,
    id2label: Mapping[int | str, str] | None = None,
    scorer_scale: str = "multi",
    scorer_backbone: SmartTextBackbone
    | str = SmartTextBackbone.shufflenetv2,
    align_size: int = 9,
    reduction_dim: int = 8,
    downsample: int = 4,
    model_type_name: SmartTextRegionMode
    | str = SmartTextRegionMode.RoE,
    image_size: int = 256,
    ratio_list: Sequence[float] = (1.0, 0.8),
    text_spacing: int = 20,
    exp_prop: int = 6,
    grid_num: int = 120,
    saliency_coef: float = 2.6,
    max_text_area_coef: float = 17.0,
    min_text_area_coef: float = 7.0,
    min_font_size: int = 10,
    max_font_size: int = 500,
    font_inc_unit: int = 5,
    candi_res: int = 3,
    contrast_threshold: float = 5.0,
    mos_mean: float = 2.95,
    mos_std: float = 0.8,
    rgb_mean: Sequence[float] = (0.485, 0.456, 0.406),
    rgb_std: Sequence[float] = (0.229, 0.224, 0.225),
    scorer_subfolder: str = "scorer",
    saliency_subfolder: str = "saliency_model",
    processor_subfolder: str = "processor",
    original_options: Mapping[str, SmartTextMetadataValue]
    | None = None,
    conversion_report: Mapping[
        str, SmartTextMetadataValue | list[str]
    ]
    | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize SmartText configuration.

Source code in models/smarttext/src/smarttext/configuration_smarttext.py
 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
def __init__(
    self,
    *,
    id2label: Mapping[int | str, str] | None = None,
    scorer_scale: str = "multi",
    scorer_backbone: SmartTextBackbone | str = SmartTextBackbone.shufflenetv2,
    align_size: int = 9,
    reduction_dim: int = 8,
    downsample: int = 4,
    model_type_name: SmartTextRegionMode | str = SmartTextRegionMode.RoE,
    image_size: int = 256,
    ratio_list: Sequence[float] = (1.0, 0.8),
    text_spacing: int = 20,
    exp_prop: int = 6,
    grid_num: int = 120,
    saliency_coef: float = 2.6,
    max_text_area_coef: float = 17.0,
    min_text_area_coef: float = 7.0,
    min_font_size: int = 10,
    max_font_size: int = 500,
    font_inc_unit: int = 5,
    candi_res: int = 3,
    contrast_threshold: float = 5.0,
    mos_mean: float = 2.95,
    mos_std: float = 0.8,
    rgb_mean: Sequence[float] = (0.485, 0.456, 0.406),
    rgb_std: Sequence[float] = (0.229, 0.224, 0.225),
    scorer_subfolder: str = "scorer",
    saliency_subfolder: str = "saliency_model",
    processor_subfolder: str = "processor",
    original_options: Mapping[str, SmartTextMetadataValue] | None = None,
    conversion_report: Mapping[str, SmartTextMetadataValue | list[str]]
    | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize SmartText configuration."""
    raw_id2label = id2label or DEFAULT_ID2LABEL
    normalized_id2label = {int(key): value for key, value in raw_id2label.items()}
    super().__init__(id2label=normalized_id2label, **kwargs)  # ty: ignore[invalid-argument-type]

    self.id2label = normalized_id2label
    self.label2id = {value: key for key, value in self.id2label.items()}

    self.scorer_scale = scorer_scale
    self.scorer_backbone = SmartTextBackbone(scorer_backbone).value
    self.align_size = int(align_size)
    self.reduction_dim = int(reduction_dim)
    self.downsample = int(downsample)

    self.model_type_name = SmartTextRegionMode(model_type_name).value
    self.image_size = int(image_size)
    self.ratio_list = tuple(float(value) for value in ratio_list)
    self.text_spacing = int(text_spacing)
    self.exp_prop = int(exp_prop)

    self.grid_num = int(grid_num)
    self.saliency_coef = float(saliency_coef)
    self.max_text_area_coef = float(max_text_area_coef)
    self.min_text_area_coef = float(min_text_area_coef)
    self.min_font_size = int(min_font_size)
    self.max_font_size = int(max_font_size)
    self.font_inc_unit = int(font_inc_unit)
    self.candi_res = int(candi_res)
    self.contrast_threshold = float(contrast_threshold)
    self.mos_mean = float(mos_mean)
    self.mos_std = float(mos_std)
    self.rgb_mean = tuple(float(value) for value in rgb_mean)
    self.rgb_std = tuple(float(value) for value in rgb_std)

    self.scorer_subfolder = scorer_subfolder
    self.saliency_subfolder = saliency_subfolder
    self.processor_subfolder = processor_subfolder

    self.original_options = dict(original_options or {})
    self.conversion_report = dict(conversion_report or {})

conversion

Checkpoint conversion helpers for SmartText.

strip_module_prefix

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

Remove DataParallel module. prefixes.

Parameters:

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

Raw PyTorch state dict.

required

Returns:

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

State dict with prefixes removed.

Examples:

>>> strip_module_prefix({"module.a": torch.tensor(1)})["a"].item()
1
Source code in models/smarttext/src/smarttext/conversion.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def strip_module_prefix(
    state_dict: Mapping[str, Shaped[torch.Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Remove ``DataParallel`` ``module.`` prefixes.

    Args:
        state_dict: Raw PyTorch state dict.

    Returns:
        State dict with prefixes removed.

    Examples:
        >>> strip_module_prefix({"module.a": torch.tensor(1)})["a"].item()
        1
    """
    return {key.removeprefix("module."): value for key, value in state_dict.items()}

file_sha256

file_sha256(path: Path) -> str

Compute SHA256 for a local file.

Source code in models/smarttext/src/smarttext/conversion.py
42
43
44
45
46
47
48
def file_sha256(path: Path) -> str:
    """Compute SHA256 for a local file."""
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for block in iter(lambda: handle.read(1024 * 1024), b""):
            digest.update(block)
    return digest.hexdigest()

convert_original_checkpoints

convert_original_checkpoints(
    *,
    smt_checkpoint: Path,
    basnet_checkpoint: Path,
    output_dir: Path,
    config: SmartTextConfig,
) -> dict[str, str | int | list[str]]

Convert raw SmartText checkpoints into a pipeline directory.

Parameters:

Name Type Description Default
smt_checkpoint Path

Raw SMT scorer checkpoint.

required
basnet_checkpoint Path

Raw BASNet checkpoint.

required
output_dir Path

Output pipeline directory.

required
config SmartTextConfig

SmartText config.

required

Returns:

Type Description
dict[str, str | int | list[str]]

Conversion report dictionary.

Raises:

Type Description
RuntimeError

If converted keys do not strictly match the target models.

Source code in models/smarttext/src/smarttext/conversion.py
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def convert_original_checkpoints(
    *,
    smt_checkpoint: Path,
    basnet_checkpoint: Path,
    output_dir: Path,
    config: SmartTextConfig,
) -> dict[str, str | int | list[str]]:
    """Convert raw SmartText checkpoints into a pipeline directory.

    Args:
        smt_checkpoint: Raw SMT scorer checkpoint.
        basnet_checkpoint: Raw BASNet checkpoint.
        output_dir: Output pipeline directory.
        config: SmartText config.

    Returns:
        Conversion report dictionary.

    Raises:
        RuntimeError: If converted keys do not strictly match the target models.
    """
    scorer = SmartTextScorer(config)
    saliency_model = SmartTextBASNet(config)
    smt_state = strip_module_prefix(torch.load(smt_checkpoint, map_location="cpu"))
    basnet_state = strip_module_prefix(
        torch.load(basnet_checkpoint, map_location="cpu")
    )
    scorer_missing, scorer_unexpected = scorer.load_state_dict(smt_state, strict=False)
    basnet_missing, basnet_unexpected = saliency_model.load_state_dict(
        basnet_state, strict=False
    )
    report = {
        "smt_checkpoint": str(smt_checkpoint),
        "basnet_checkpoint": str(basnet_checkpoint),
        "smt_sha256": file_sha256(smt_checkpoint),
        "basnet_sha256": file_sha256(basnet_checkpoint),
        "smt_source_key_count": len(smt_state),
        "basnet_source_key_count": len(basnet_state),
        "scorer_missing_keys": list(scorer_missing),
        "scorer_unexpected_keys": list(scorer_unexpected),
        "basnet_missing_keys": list(basnet_missing),
        "basnet_unexpected_keys": list(basnet_unexpected),
        "roi_rod_alignment": "PyTorch port of vendor RoI/RoD forward kernels; strict parity should be checked against compiled vendor references",
    }
    if scorer_missing or scorer_unexpected or basnet_missing or basnet_unexpected:
        raise RuntimeError(json.dumps(report, indent=2, sort_keys=True))

    processor = SmartTextProcessor(
        image_processor=SmartTextImageProcessor.from_config(config),
        config=config,
    )
    pipeline = SmartTextPipeline(
        scorer=scorer,
        saliency_model=saliency_model,
        processor=processor,
        config=config,
    )
    pipeline.save_pretrained(output_dir)
    (output_dir / CONVERSION_REPORT).write_text(
        json.dumps(report, indent=2, sort_keys=True),
        encoding="utf-8",
    )
    return report

image_processing_smarttext

Image processor for SmartText RGB and BASNet inputs.

SmartTextImageProcessor

Bases: BaseImageProcessor

Prepare SmartText scorer and BASNet image tensors.

Parameters:

Name Type Description Default
image_size int

Scorer short-side target.

256
rgb_mean Sequence[float]

Scorer RGB normalization mean.

(0.485, 0.456, 0.406)
rgb_std Sequence[float]

Scorer RGB normalization standard deviation.

(0.229, 0.224, 0.225)

Examples:

>>> processor = SmartTextImageProcessor()
>>> batch = processor.preprocess(Image.new("RGB", (32, 32)))
>>> tuple(batch["pixel_values"].shape[:2])
(1, 3)
Source code in models/smarttext/src/smarttext/image_processing_smarttext.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
class SmartTextImageProcessor(BaseImageProcessor):
    """Prepare SmartText scorer and BASNet image tensors.

    Args:
        image_size: Scorer short-side target.
        rgb_mean: Scorer RGB normalization mean.
        rgb_std: Scorer RGB normalization standard deviation.

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

    model_input_names = ["pixel_values", "basnet_pixel_values"]

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

    @classmethod
    def from_config(cls, config: SmartTextConfig) -> "SmartTextImageProcessor":
        """Build an image processor from SmartText configuration."""
        return cls(
            image_size=config.image_size,
            rgb_mean=config.rgb_mean,
            rgb_std=config.rgb_std,
        )

    def preprocess(
        self,
        images: ImageInput | Sequence[ImageInput],
        *,
        return_tensors: Literal["pt"] = "pt",
        target_min_side: int | None = None,
        rgb_mean: Sequence[float] | None = None,
        rgb_std: Sequence[float] | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> BatchFeature:
        """Preprocess images for the SmartText scorer.

        Args:
            images: RGB image or image batch.
            return_tensors: Tensor framework. Only ``pt`` is supported.
            target_min_side: Optional short-side target override.
            rgb_mean: Optional RGB mean override.
            rgb_std: Optional RGB std override.
            kwargs: Ignored compatibility kwargs.

        Returns:
            Batch feature with ``pixel_values`` and ``image_sizes``.

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

        mean = np.asarray(rgb_mean or self.rgb_mean, dtype=np.float32)
        std = np.asarray(rgb_std or self.rgb_std, dtype=np.float32)
        tensors = []
        sizes = []
        for image in _ensure_pil_batch(images):
            width, height = image.size
            sizes.append((height, width))
            scale = (target_min_side or self.image_size) / min(height, width)
            resized_h = max(32, int(round(height * scale / 32.0) * 32))
            resized_w = max(32, int(round(width * scale / 32.0) * 32))
            resized = image.convert("RGB").resize(
                (resized_w, resized_h), Image.Resampling.BILINEAR
            )
            array = np.asarray(resized, dtype=np.float32) / 256.0
            tensors.append(torch.from_numpy(((array - mean) / std).transpose(2, 0, 1)))
        return BatchFeature(
            {
                "pixel_values": torch.stack(tensors).float(),
                "image_sizes": torch.tensor(sizes, dtype=torch.long),
            }
        )

    def preprocess_basnet(
        self,
        images: ImageInput | Sequence[ImageInput],
        *,
        return_tensors: Literal["pt"] = "pt",
    ) -> BatchFeature:
        """Preprocess images for BASNet saliency prediction.

        Args:
            images: RGB image or image batch.
            return_tensors: Tensor framework. Only ``pt`` is supported.

        Returns:
            Batch feature with ``basnet_pixel_values`` shaped ``(B, 3, 256, 256)``.
        """
        basnet = BASNetImageProcessor(
            input_size=256,
            rgb_mean=self.rgb_mean,
            rgb_std=self.rgb_std,
        ).preprocess(images, return_tensors=return_tensors)
        return BatchFeature(
            {
                "basnet_pixel_values": basnet["pixel_values"],
                "image_sizes": basnet["image_sizes"],
            }
        )

__init__

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

Initialize image processor settings.

Source code in models/smarttext/src/smarttext/image_processing_smarttext.py
37
38
39
40
41
42
43
44
45
46
47
48
def __init__(
    self,
    image_size: int = 256,
    rgb_mean: Sequence[float] = (0.485, 0.456, 0.406),
    rgb_std: Sequence[float] = (0.229, 0.224, 0.225),
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize image processor settings."""
    super().__init__(**kwargs)
    self.image_size = int(image_size)
    self.rgb_mean = tuple(float(value) for value in rgb_mean)
    self.rgb_std = tuple(float(value) for value in rgb_std)

from_config classmethod

from_config(
    config: SmartTextConfig,
) -> "SmartTextImageProcessor"

Build an image processor from SmartText configuration.

Source code in models/smarttext/src/smarttext/image_processing_smarttext.py
50
51
52
53
54
55
56
57
@classmethod
def from_config(cls, config: SmartTextConfig) -> "SmartTextImageProcessor":
    """Build an image processor from SmartText configuration."""
    return cls(
        image_size=config.image_size,
        rgb_mean=config.rgb_mean,
        rgb_std=config.rgb_std,
    )

preprocess

preprocess(
    images: ImageInput | Sequence[ImageInput],
    *,
    return_tensors: Literal["pt"] = "pt",
    target_min_side: int | None = None,
    rgb_mean: Sequence[float] | None = None,
    rgb_std: Sequence[float] | None = None,
    **kwargs: str | int | float | bool | None,
) -> BatchFeature

Preprocess images for the SmartText scorer.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput]

RGB image or image batch.

required
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

'pt'
target_min_side int | None

Optional short-side target override.

None
rgb_mean Sequence[float] | None

Optional RGB mean override.

None
rgb_std Sequence[float] | None

Optional RGB std override.

None
kwargs str | int | float | bool | None

Ignored compatibility kwargs.

{}

Returns:

Type Description
BatchFeature

Batch feature with pixel_values and image_sizes.

Raises:

Type Description
ValueError

If return_tensors is not pt.

Source code in models/smarttext/src/smarttext/image_processing_smarttext.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
def preprocess(
    self,
    images: ImageInput | Sequence[ImageInput],
    *,
    return_tensors: Literal["pt"] = "pt",
    target_min_side: int | None = None,
    rgb_mean: Sequence[float] | None = None,
    rgb_std: Sequence[float] | None = None,
    **kwargs: str | int | float | bool | None,
) -> BatchFeature:
    """Preprocess images for the SmartText scorer.

    Args:
        images: RGB image or image batch.
        return_tensors: Tensor framework. Only ``pt`` is supported.
        target_min_side: Optional short-side target override.
        rgb_mean: Optional RGB mean override.
        rgb_std: Optional RGB std override.
        kwargs: Ignored compatibility kwargs.

    Returns:
        Batch feature with ``pixel_values`` and ``image_sizes``.

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

    mean = np.asarray(rgb_mean or self.rgb_mean, dtype=np.float32)
    std = np.asarray(rgb_std or self.rgb_std, dtype=np.float32)
    tensors = []
    sizes = []
    for image in _ensure_pil_batch(images):
        width, height = image.size
        sizes.append((height, width))
        scale = (target_min_side or self.image_size) / min(height, width)
        resized_h = max(32, int(round(height * scale / 32.0) * 32))
        resized_w = max(32, int(round(width * scale / 32.0) * 32))
        resized = image.convert("RGB").resize(
            (resized_w, resized_h), Image.Resampling.BILINEAR
        )
        array = np.asarray(resized, dtype=np.float32) / 256.0
        tensors.append(torch.from_numpy(((array - mean) / std).transpose(2, 0, 1)))
    return BatchFeature(
        {
            "pixel_values": torch.stack(tensors).float(),
            "image_sizes": torch.tensor(sizes, dtype=torch.long),
        }
    )

preprocess_basnet

preprocess_basnet(
    images: ImageInput | Sequence[ImageInput],
    *,
    return_tensors: Literal["pt"] = "pt",
) -> BatchFeature

Preprocess images for BASNet saliency prediction.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput]

RGB image or image batch.

required
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

'pt'

Returns:

Type Description
BatchFeature

Batch feature with basnet_pixel_values shaped (B, 3, 256, 256).

Source code in models/smarttext/src/smarttext/image_processing_smarttext.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def preprocess_basnet(
    self,
    images: ImageInput | Sequence[ImageInput],
    *,
    return_tensors: Literal["pt"] = "pt",
) -> BatchFeature:
    """Preprocess images for BASNet saliency prediction.

    Args:
        images: RGB image or image batch.
        return_tensors: Tensor framework. Only ``pt`` is supported.

    Returns:
        Batch feature with ``basnet_pixel_values`` shaped ``(B, 3, 256, 256)``.
    """
    basnet = BASNetImageProcessor(
        input_size=256,
        rgb_mean=self.rgb_mean,
        rgb_std=self.rgb_std,
    ).preprocess(images, return_tensors=return_tensors)
    return BatchFeature(
        {
            "basnet_pixel_values": basnet["pixel_values"],
            "image_sizes": basnet["image_sizes"],
        }
    )

model_card

Model-card helpers for SmartText.

build_smarttext_model_card

build_smarttext_model_card(
    *,
    hub_id: str = "creative-graphic-design/smarttext-smt",
    parity_results: dict[str, str | int | float | bool]
    | None = None,
) -> str

Render a SmartText Hub model card.

Parameters:

Name Type Description Default
hub_id str

Target Hub repository id.

'creative-graphic-design/smarttext-smt'
parity_results dict[str, str | int | float | bool] | None

Optional parity result payload.

None

Returns:

Type Description
str

Markdown model-card text.

Source code in models/smarttext/src/smarttext/model_card.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def build_smarttext_model_card(
    *,
    hub_id: str = "creative-graphic-design/smarttext-smt",
    parity_results: dict[str, str | int | float | bool] | None = None,
) -> str:
    """Render a SmartText Hub model card.

    Args:
        hub_id: Target Hub repository id.
        parity_results: Optional parity result payload.

    Returns:
        Markdown model-card text.
    """
    card = build_layout_model_card(
        model_id=hub_id,
        model_name="SmartText",
        dataset_ids=[],
        license="other",
        library_name="transformers",
        pipeline_tag="other",
        tags=[
            "smarttext",
            "text-placement",
            "poster-generation",
            "content-image",
            "layout-generation",
        ],
        model_details="SmartText places text regions on natural images using saliency, candidate generation, and candidate scoring.",
        intended_uses="Content-aware text placement research and reproducibility.",
        limitations="Converted weight redistribution and upstream license provenance require review before publication.",
        how_to_use=(
            "from smarttext import SmartTextPipeline\n"
            f"pipe = SmartTextPipeline.from_pretrained({hub_id!r})\n"
            "output = pipe(image, prompt='Title', condition_type='content_image')"
        ),
        training_data="Original SmartText training data is not distributed in this repository.",
        parity_metrics=[],
        citation_bibtex="@article{li2021smarttext, title={Harmonious Textual Layout Generation over Natural Images via Deep Aesthetics Learning}}",
        original_implementation_url="https://github.com/chenqi008/SmartText",
        model_summary="SmartText content-image text placement checkpoint.",
        results_summary=f"Parity results: {dict(parity_results or {})}",
    )
    return card.text

modeling_basnet

SmartText BASNet compatibility exports.

SmartTextBASNet

Bases: BASNetModel

SmartText saliency component backed by the shared BASNet model.

Source code in models/smarttext/src/smarttext/modeling_basnet.py
17
18
19
20
21
22
23
24
class SmartTextBASNet(BASNetModel):
    """SmartText saliency component backed by the shared BASNet model."""

    config_class = SmartTextConfig

    def __init__(self, config: SmartTextConfig) -> None:
        """Initialize the SmartText saliency component."""
        super().__init__(cast(BASNetConfig, config))

__init__

__init__(config: SmartTextConfig) -> None

Initialize the SmartText saliency component.

Source code in models/smarttext/src/smarttext/modeling_basnet.py
22
23
24
def __init__(self, config: SmartTextConfig) -> None:
    """Initialize the SmartText saliency component."""
    super().__init__(cast(BASNetConfig, config))

normalize_saliency

normalize_saliency(
    pred: Float[Tensor, "... height width"],
) -> Float[torch.Tensor, "... height width"]

Normalize saliency maps independently over each spatial map.

Source code in models/basnet/src/basnet/modeling_basnet.py
149
150
151
152
153
154
155
def normalize_saliency(
    pred: Float[torch.Tensor, "... height width"],
) -> Float[torch.Tensor, "... height width"]:
    """Normalize saliency maps independently over each spatial map."""
    min_value = pred.amin(dim=(-2, -1), keepdim=True)
    max_value = pred.amax(dim=(-2, -1), keepdim=True)
    return (pred - min_value) / (max_value - min_value)

modeling_smarttext

SmartText scorer model ported from the original SMT architecture.

SmartTextScorerOutput dataclass

Bases: ModelOutput

Output of SmartTextScorer.forward.

Source code in models/smarttext/src/smarttext/modeling_smarttext.py
18
19
20
21
22
@dataclass
class SmartTextScorerOutput(ModelOutput):
    """Output of ``SmartTextScorer.forward``."""

    scores: Float[torch.Tensor, "candidates"]

SmartTextRoIAlignAvg

Bases: _AlignBase

PyTorch port of the reference RoIAlignAvg forward kernel.

Source code in models/smarttext/src/smarttext/modeling_smarttext.py
241
242
243
244
245
246
247
248
249
250
251
class SmartTextRoIAlignAvg(_AlignBase):
    """PyTorch port of the reference ``RoIAlignAvg`` forward kernel."""

    def forward(
        self,
        features: Float[torch.Tensor, "batch channels height width"],
        rois: Float[torch.Tensor, "candidates 5"],
    ) -> Float[torch.Tensor, "candidates channels aligned_height aligned_width"]:
        """Align RoI features and average adjacent samples."""
        sampled = self._sample(features, rois, "roi")
        return F.avg_pool2d(sampled, kernel_size=2, stride=1)

forward

forward(
    features: Float[Tensor, "batch channels height width"],
    rois: Float[Tensor, "candidates 5"],
) -> Float[
    torch.Tensor,
    "candidates channels aligned_height aligned_width",
]

Align RoI features and average adjacent samples.

Source code in models/smarttext/src/smarttext/modeling_smarttext.py
244
245
246
247
248
249
250
251
def forward(
    self,
    features: Float[torch.Tensor, "batch channels height width"],
    rois: Float[torch.Tensor, "candidates 5"],
) -> Float[torch.Tensor, "candidates channels aligned_height aligned_width"]:
    """Align RoI features and average adjacent samples."""
    sampled = self._sample(features, rois, "roi")
    return F.avg_pool2d(sampled, kernel_size=2, stride=1)

SmartTextRoDAlignAvg

Bases: _AlignBase

PyTorch port of the reference RoDAlignAvg forward kernel.

Source code in models/smarttext/src/smarttext/modeling_smarttext.py
254
255
256
257
258
259
260
261
262
263
264
class SmartTextRoDAlignAvg(_AlignBase):
    """PyTorch port of the reference ``RoDAlignAvg`` forward kernel."""

    def forward(
        self,
        features: Float[torch.Tensor, "batch channels height width"],
        rois: Float[torch.Tensor, "candidates 5"],
    ) -> Float[torch.Tensor, "candidates channels aligned_height aligned_width"]:
        """Align outside-region features and average adjacent samples."""
        sampled = self._sample(features, rois, "rod")
        return F.avg_pool2d(sampled, kernel_size=2, stride=1)

forward

forward(
    features: Float[Tensor, "batch channels height width"],
    rois: Float[Tensor, "candidates 5"],
) -> Float[
    torch.Tensor,
    "candidates channels aligned_height aligned_width",
]

Align outside-region features and average adjacent samples.

Source code in models/smarttext/src/smarttext/modeling_smarttext.py
257
258
259
260
261
262
263
264
def forward(
    self,
    features: Float[torch.Tensor, "batch channels height width"],
    rois: Float[torch.Tensor, "candidates 5"],
) -> Float[torch.Tensor, "candidates channels aligned_height aligned_width"]:
    """Align outside-region features and average adjacent samples."""
    sampled = self._sample(features, rois, "rod")
    return F.avg_pool2d(sampled, kernel_size=2, stride=1)

SmartTextScorer

Bases: PreTrainedModel

Reference-compatible SMT candidate scorer.

The module names match the original smtModel.py for build_smt_model(scale="multi", alignsize=9, reddim=8, model="shufflenetv2", downsample=4) so SMT.pth loads directly.

Parameters:

Name Type Description Default
config SmartTextConfig

SmartText configuration.

required

Examples:

>>> config = SmartTextConfig()
>>> model = SmartTextScorer(config)
>>> "Feat_ext.feature3.0.0.weight" in model.state_dict()
True
Source code in models/smarttext/src/smarttext/modeling_smarttext.py
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
class SmartTextScorer(PreTrainedModel):
    """Reference-compatible SMT candidate scorer.

    The module names match the original ``smtModel.py`` for
    ``build_smt_model(scale="multi", alignsize=9, reddim=8,
    model="shufflenetv2", downsample=4)`` so ``SMT.pth`` loads directly.

    Args:
        config: SmartText configuration.

    Examples:
        >>> config = SmartTextConfig()
        >>> model = SmartTextScorer(config)
        >>> "Feat_ext.feature3.0.0.weight" in model.state_dict()
        True
    """

    config_class = SmartTextConfig
    main_input_name = "pixel_values"
    _tied_weights_keys: list[str] = []

    def __init__(self, config: SmartTextConfig) -> None:
        """Initialize scorer scorer modules."""
        super().__init__(config)
        self.all_tied_weights_keys: dict[str, str] = {}
        if config.scorer_scale != "multi" or config.scorer_backbone != "shufflenetv2":
            raise ValueError(
                "Only the released multi-scale ShuffleNetV2 SMT scorer is supported"
            )

        self.Feat_ext = _ShuffleNetV2Base()
        self.DimRed = nn.Conv2d(812, config.reduction_dim, kernel_size=1, padding=0)
        self.downsample2 = nn.UpsamplingBilinear2d(scale_factor=1.0 / 2.0)
        self.upsample2 = nn.UpsamplingBilinear2d(scale_factor=2.0)
        spatial_scale = 1.0 / 2**config.downsample
        self.RoIAlign = SmartTextRoIAlignAvg(
            config.align_size + 1, config.align_size + 1, spatial_scale
        )
        self.RoDAlign = SmartTextRoDAlignAvg(
            config.align_size + 1, config.align_size + 1, spatial_scale
        )
        self.FC_layers = _fc_layers(config.reduction_dim * 2, config.align_size)

    def forward(
        self,
        pixel_values: Float[torch.Tensor, "batch channels height width"],
        boxes: Float[torch.Tensor, "candidates 5"],
        return_dict: bool | None = None,
    ) -> SmartTextScorerOutput | tuple[Float[torch.Tensor, "candidates"]]:
        """Score candidate text regions.

        Args:
            pixel_values: RGB scorer tensor shaped ``(B, 3, H, W)``.
            boxes: RoI rows shaped ``(N, 5)`` with batch index in column zero.
            return_dict: Whether to return a ``ModelOutput``.

        Returns:
            Candidate scores as a ``SmartTextScorerOutput`` or tuple.
        """
        return_dict = (
            return_dict if return_dict is not None else self.config.use_return_dict
        )
        f3, f4, f5 = self.Feat_ext(pixel_values)
        cat_feat = torch.cat((self.downsample2(f3), f4, 0.5 * self.upsample2(f5)), 1)
        red_feat = self.DimRed(cat_feat)
        roi_feat = self.RoIAlign(red_feat, boxes)
        rod_feat = self.RoDAlign(red_feat, boxes)
        prediction = self.FC_layers(torch.cat((roi_feat, rod_feat), 1)).flatten()
        if not return_dict:
            return (prediction,)
        return SmartTextScorerOutput(scores=prediction)

__init__

__init__(config: SmartTextConfig) -> None

Initialize scorer scorer modules.

Source code in models/smarttext/src/smarttext/modeling_smarttext.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def __init__(self, config: SmartTextConfig) -> None:
    """Initialize scorer scorer modules."""
    super().__init__(config)
    self.all_tied_weights_keys: dict[str, str] = {}
    if config.scorer_scale != "multi" or config.scorer_backbone != "shufflenetv2":
        raise ValueError(
            "Only the released multi-scale ShuffleNetV2 SMT scorer is supported"
        )

    self.Feat_ext = _ShuffleNetV2Base()
    self.DimRed = nn.Conv2d(812, config.reduction_dim, kernel_size=1, padding=0)
    self.downsample2 = nn.UpsamplingBilinear2d(scale_factor=1.0 / 2.0)
    self.upsample2 = nn.UpsamplingBilinear2d(scale_factor=2.0)
    spatial_scale = 1.0 / 2**config.downsample
    self.RoIAlign = SmartTextRoIAlignAvg(
        config.align_size + 1, config.align_size + 1, spatial_scale
    )
    self.RoDAlign = SmartTextRoDAlignAvg(
        config.align_size + 1, config.align_size + 1, spatial_scale
    )
    self.FC_layers = _fc_layers(config.reduction_dim * 2, config.align_size)

forward

forward(
    pixel_values: Float[
        Tensor, "batch channels height width"
    ],
    boxes: Float[Tensor, "candidates 5"],
    return_dict: bool | None = None,
) -> (
    SmartTextScorerOutput
    | tuple[Float[torch.Tensor, "candidates"]]
)

Score candidate text regions.

Parameters:

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

RGB scorer tensor shaped (B, 3, H, W).

required
boxes Float[Tensor, 'candidates 5']

RoI rows shaped (N, 5) with batch index in column zero.

required
return_dict bool | None

Whether to return a ModelOutput.

None

Returns:

Type Description
SmartTextScorerOutput | tuple[Float[Tensor, 'candidates']]

Candidate scores as a SmartTextScorerOutput or tuple.

Source code in models/smarttext/src/smarttext/modeling_smarttext.py
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
def forward(
    self,
    pixel_values: Float[torch.Tensor, "batch channels height width"],
    boxes: Float[torch.Tensor, "candidates 5"],
    return_dict: bool | None = None,
) -> SmartTextScorerOutput | tuple[Float[torch.Tensor, "candidates"]]:
    """Score candidate text regions.

    Args:
        pixel_values: RGB scorer tensor shaped ``(B, 3, H, W)``.
        boxes: RoI rows shaped ``(N, 5)`` with batch index in column zero.
        return_dict: Whether to return a ``ModelOutput``.

    Returns:
        Candidate scores as a ``SmartTextScorerOutput`` or tuple.
    """
    return_dict = (
        return_dict if return_dict is not None else self.config.use_return_dict
    )
    f3, f4, f5 = self.Feat_ext(pixel_values)
    cat_feat = torch.cat((self.downsample2(f3), f4, 0.5 * self.upsample2(f5)), 1)
    red_feat = self.DimRed(cat_feat)
    roi_feat = self.RoIAlign(red_feat, boxes)
    rod_feat = self.RoDAlign(red_feat, boxes)
    prediction = self.FC_layers(torch.cat((roi_feat, rod_feat), 1)).flatten()
    if not return_dict:
        return (prediction,)
    return SmartTextScorerOutput(scores=prediction)

pipeline_smarttext

Pipeline interface for SmartText content-image text placement.

OutputType

Bases: StrEnum

Supported SmartText pipeline output containers.

Source code in models/smarttext/src/smarttext/pipeline_smarttext.py
44
45
46
47
48
class OutputType(StrEnum):
    """Supported SmartText pipeline output containers."""

    dataclass = auto()
    dict = auto()

SmartTextPipeline

Bases: LayoutGenerationPipeline

Transformers-side SmartText pipeline.

Parameters:

Name Type Description Default
scorer SmartTextScorer

Candidate scoring model.

required
saliency_model SmartTextBASNet

BASNet saliency model.

required
processor SmartTextProcessor | None

Input/output processor.

None
config SmartTextConfig | None

Optional root pipeline config.

None
device str | device | None

Optional runtime device.

None

Examples:

>>> config = SmartTextConfig(align_size=3, reduction_dim=4, grid_num=16, max_font_size=20)
>>> pipe = SmartTextPipeline(SmartTextScorer(config), SmartTextBASNet(config), config=config)
>>> pipe.config.model_type
'smarttext'
Source code in models/smarttext/src/smarttext/pipeline_smarttext.py
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
406
407
class SmartTextPipeline(LayoutGenerationPipeline):
    """Transformers-side SmartText pipeline.

    Args:
        scorer: Candidate scoring model.
        saliency_model: BASNet saliency model.
        processor: Input/output processor.
        config: Optional root pipeline config.
        device: Optional runtime device.

    Examples:
        >>> config = SmartTextConfig(align_size=3, reduction_dim=4, grid_num=16, max_font_size=20)
        >>> pipe = SmartTextPipeline(SmartTextScorer(config), SmartTextBASNet(config), config=config)
        >>> pipe.config.model_type
        'smarttext'
    """

    config_class: ClassVar[type[PretrainedConfig]] = SmartTextConfig
    component_specs: ClassVar[dict[str, PipelineComponentSpec]] = {
        "scorer": PipelineComponentSpec(
            attribute_name="scorer",
            loader=_load_scorer_component,
            marker_file="config.json",
            config_subfolder_attribute="scorer_subfolder",
        ),
        "saliency_model": PipelineComponentSpec(
            attribute_name="saliency_model",
            loader=_load_saliency_component,
            marker_file="config.json",
            config_subfolder_attribute="saliency_subfolder",
        ),
        "processor": PipelineComponentSpec(
            attribute_name="processor",
            loader=_load_processor_component,
            marker_file="processor_config.json",
            save_with_is_main_process=False,
            config_subfolder_attribute="processor_subfolder",
        ),
    }

    config: SmartTextConfig
    scorer: SmartTextScorer
    saliency_model: SmartTextBASNet
    processor: SmartTextProcessor

    def __init__(
        self,
        scorer: SmartTextScorer,
        saliency_model: SmartTextBASNet,
        processor: SmartTextProcessor | None = None,
        config: SmartTextConfig | None = None,
        device: str | torch.device | None = None,
    ) -> None:
        """Initialize SmartText pipeline."""
        super().__init__(config or scorer.config)
        self.config = config or scorer.config
        self.scorer = scorer
        self.saliency_model = saliency_model
        self.processor = processor or SmartTextProcessor(config=self.config)
        self.scorer.eval()
        self.saliency_model.eval()
        if device is not None:
            self.to(device)

    @classmethod
    def _from_pretrained_components(
        cls,
        *,
        config: PretrainedConfig,
        components: Mapping[str, PipelineComponent | None],
    ) -> "SmartTextPipeline":
        """Build a SmartText pipeline from loaded components."""
        return cls(
            config=cast(SmartTextConfig, config),
            scorer=cast(SmartTextScorer, components["scorer"]),
            saliency_model=cast(SmartTextBASNet, components["saliency_model"]),
            processor=cast(SmartTextProcessor, components["processor"]),
        )

    @torch.no_grad()
    def __call__(  # ty: ignore[invalid-method-override]
        self,
        images: ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch channels height width"]
        | None = None,
        *,
        content: Mapping[
            str,
            ImageInput
            | Sequence[ImageInput]
            | str
            | Sequence[str]
            | Float[torch.Tensor, "batch height width"]
            | Sequence[CandidateBoxRow]
            | Sequence[Sequence[CandidateBoxRow]],
        ]
        | None = None,
        prompt: str | Sequence[str] | None = None,
        text: str | Sequence[str] | None = None,
        batch_size: int = 1,
        seed: int | None = None,
        generator: torch.Generator | None = None,
        condition_type: ConditionType | str = ConditionType.content_image,
        labels: Int[torch.Tensor, "batch elements"]
        | Sequence[Sequence[int]]
        | Sequence[int]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Sequence[Sequence[Sequence[float]]]
        | Sequence[Sequence[float]]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Sequence[Sequence[bool]]
        | Sequence[bool]
        | None = None,
        num_elements: int | Sequence[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        output_type: OutputType | Literal["dataclass", "dict"] = OutputType.dataclass,
        return_intermediates: bool = False,
        font: str | Path | ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
        ratio_list: Sequence[float] | None = None,
        text_spacing: int | None = None,
        candi_res: int | None = None,
        saliency: ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch height width"]
        | None = None,
        candidate_boxes: Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]]
        | None = None,
        return_text_lines: bool = False,
        score_normalization: Literal["mos", "raw"] = "mos",
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | dict[int, str]
            | dict[
                str,
                Shaped[torch.Tensor, "..."]
                | str
                | list[int]
                | list[SmartTextCandidate]
                | None,
            ]
            | None,
        ]
    ):
        """Generate text placement boxes for content images.

        Args:
            images: RGB image or image batch.
            content: Optional content carrier.
            prompt: Prompt text payload.
            text: Alias for prompt.
            batch_size: Expected batch size for validation.
            seed: Optional seed used only when ``generator`` is absent.
            generator: Explicit torch generator; wins over ``seed``.
            condition_type: Must normalize to ``content_image``.
            labels: Unsupported v1 compatibility argument.
            bbox: Unsupported v1 compatibility argument.
            mask: Unsupported v1 compatibility argument.
            num_elements: Unsupported v1 compatibility argument.
            box_format: Public v1 compatibility argument.
            normalized: Public v1 compatibility argument.
            canvas_size: Optional source canvas size override.
            num_inference_steps: Unused v1 compatibility argument.
            output_type: ``dataclass`` or ``dict``.
            return_intermediates: Whether to include intermediate payloads.
            font: TrueType font path or PIL font object.
            ratio_list: Optional per-line font ratios.
            text_spacing: Optional text-spacing override.
            candi_res: Optional top-k override.
            saliency: Optional saliency map bypassing BASNet.
            candidate_boxes: Optional reference-style candidates.
            return_text_lines: Return per-line boxes for top candidate.
            score_normalization: ``mos`` or ``raw``.

        Returns:
            Shared layout output.
        """
        del (
            labels,
            bbox,
            mask,
            num_elements,
            box_format,
            normalized,
            canvas_size,
            num_inference_steps,
        )
        normalize_condition_type(condition_type)
        self.prepare_generator(generator=generator, seed=seed, device=self.device)
        if font is None:
            font = ImageFont.load_default()
        effective_config = self.config
        if text_spacing is not None:
            effective_config = copy.copy(self.config)
            effective_config.text_spacing = int(text_spacing)
        encoded = self.processor(
            images,
            content=content,
            prompt=prompt,
            text=text,
            saliency=saliency,
            candidate_boxes=candidate_boxes,
            font=font,
        )
        if len(encoded["images"]) != 1 or batch_size != 1:
            raise ValueError("SmartText currently decodes one image at a time")

        image = encoded["images"][0]
        prompt_text = encoded["prompts"][0]
        resolved_saliency = encoded["saliency"]
        if resolved_saliency is None:
            basnet_values = encoded["basnet_pixel_values"].to(self._runtime_device())
            saliency_out = self.saliency_model(basnet_values)
            resolved_saliency = saliency_out.saliency[0]
        candidates = encoded["candidate_boxes"]
        if candidates is None:
            candidates = generate_candidates(
                image,
                cast(torch.Tensor, resolved_saliency),
                prompt=prompt_text,
                font=font,
                config=effective_config,
                ratio_list=ratio_list,
            )
        pixel_values, boxes, candidates = prepare_scorer_batch(
            image,
            candidates,
            config=effective_config,
        )
        scores = self.scorer(
            pixel_values.to(self._runtime_device()),
            boxes.to(self._runtime_device()),
        ).scores
        top_k = candi_res or self.config.candi_res
        raw_scores = scores.detach().cpu().float().flatten()
        if candidates:
            selected_color_index = max(
                range(len(candidates)), key=lambda index: float(raw_scores[index])
            )
            text_color = choose_text_color(
                image,
                candidates[selected_color_index].bbox_ltrb_px,
                contrast_threshold=self.config.contrast_threshold,
            )
        else:
            text_color = None
        intermediates = None
        if return_intermediates:
            intermediates = {
                "saliency": resolved_saliency,
                "raw_scorer_boxes": boxes.detach().cpu(),
                "prompt": prompt_text,
            }
        return self.processor.decode(
            candidates=candidates,
            scores=scores,
            image_size=image.size,
            output_type=cast(Literal["dataclass", "dict"], str(output_type)),
            return_text_lines=return_text_lines,
            top_k=top_k,
            score_normalization=score_normalization,
            text_color=text_color,
            intermediates=intermediates,
        )

    def _runtime_device(self) -> torch.device:
        return self.device or next(self.scorer.parameters()).device

__init__

__init__(
    scorer: SmartTextScorer,
    saliency_model: SmartTextBASNet,
    processor: SmartTextProcessor | None = None,
    config: SmartTextConfig | None = None,
    device: str | device | None = None,
) -> None

Initialize SmartText pipeline.

Source code in models/smarttext/src/smarttext/pipeline_smarttext.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def __init__(
    self,
    scorer: SmartTextScorer,
    saliency_model: SmartTextBASNet,
    processor: SmartTextProcessor | None = None,
    config: SmartTextConfig | None = None,
    device: str | torch.device | None = None,
) -> None:
    """Initialize SmartText pipeline."""
    super().__init__(config or scorer.config)
    self.config = config or scorer.config
    self.scorer = scorer
    self.saliency_model = saliency_model
    self.processor = processor or SmartTextProcessor(config=self.config)
    self.scorer.eval()
    self.saliency_model.eval()
    if device is not None:
        self.to(device)

__call__

__call__(
    images: ImageInput
    | Sequence[ImageInput]
    | Float[Tensor, "batch channels height width"]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | str
        | Sequence[str]
        | Float[Tensor, "batch height width"]
        | Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    text: str | Sequence[str] | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.content_image,
    labels: Int[Tensor, "batch elements"]
    | Sequence[Sequence[int]]
    | Sequence[int]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Sequence[Sequence[Sequence[float]]]
    | Sequence[Sequence[float]]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    num_elements: int
    | Sequence[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType
    | Literal["dataclass", "dict"] = OutputType.dataclass,
    return_intermediates: bool = False,
    font: str
    | Path
    | FreeTypeFont
    | ImageFont
    | None = None,
    ratio_list: Sequence[float] | None = None,
    text_spacing: int | None = None,
    candi_res: int | None = None,
    saliency: ImageInput
    | Sequence[ImageInput]
    | Float[Tensor, "batch height width"]
    | None = None,
    candidate_boxes: Sequence[CandidateBoxRow]
    | Sequence[Sequence[CandidateBoxRow]]
    | None = None,
    return_text_lines: bool = False,
    score_normalization: Literal["mos", "raw"] = "mos",
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | str
            | list[int]
            | list[SmartTextCandidate]
            | None,
        ]
        | None,
    ]
)

Generate text placement boxes for content images.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput] | Float[Tensor, 'batch channels height width'] | None

RGB image or image batch.

None
content Mapping[str, ImageInput | Sequence[ImageInput] | str | Sequence[str] | Float[Tensor, 'batch height width'] | Sequence[CandidateBoxRow] | Sequence[Sequence[CandidateBoxRow]]] | None

Optional content carrier.

None
prompt str | Sequence[str] | None

Prompt text payload.

None
text str | Sequence[str] | None

Alias for prompt.

None
batch_size int

Expected batch size for validation.

1
seed int | None

Optional seed used only when generator is absent.

None
generator Generator | None

Explicit torch generator; wins over seed.

None
condition_type ConditionType | str

Must normalize to content_image.

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

Unsupported v1 compatibility argument.

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

Unsupported v1 compatibility argument.

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

Unsupported v1 compatibility argument.

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

Unsupported v1 compatibility argument.

None
box_format BoxFormat | str

Public v1 compatibility argument.

xywh
normalized bool

Public v1 compatibility argument.

True
canvas_size tuple[int, int] | None

Optional source canvas size override.

None
num_inference_steps int | None

Unused v1 compatibility argument.

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

dataclass or dict.

dataclass
return_intermediates bool

Whether to include intermediate payloads.

False
font str | Path | FreeTypeFont | ImageFont | None

TrueType font path or PIL font object.

None
ratio_list Sequence[float] | None

Optional per-line font ratios.

None
text_spacing int | None

Optional text-spacing override.

None
candi_res int | None

Optional top-k override.

None
saliency ImageInput | Sequence[ImageInput] | Float[Tensor, 'batch height width'] | None

Optional saliency map bypassing BASNet.

None
candidate_boxes Sequence[CandidateBoxRow] | Sequence[Sequence[CandidateBoxRow]] | None

Optional reference-style candidates.

None
return_text_lines bool

Return per-line boxes for top candidate.

False
score_normalization Literal['mos', 'raw']

mos or raw.

'mos'

Returns:

Type Description
LayoutGenerationOutput | dict[str, Shaped[Tensor, '...'] | dict[int, str] | dict[str, Shaped[Tensor, '...'] | str | list[int] | list[SmartTextCandidate] | None] | None]

Shared layout output.

Source code in models/smarttext/src/smarttext/pipeline_smarttext.py
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
@torch.no_grad()
def __call__(  # ty: ignore[invalid-method-override]
    self,
    images: ImageInput
    | Sequence[ImageInput]
    | Float[torch.Tensor, "batch channels height width"]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | str
        | Sequence[str]
        | Float[torch.Tensor, "batch height width"]
        | Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    text: str | Sequence[str] | None = None,
    batch_size: int = 1,
    seed: int | None = None,
    generator: torch.Generator | None = None,
    condition_type: ConditionType | str = ConditionType.content_image,
    labels: Int[torch.Tensor, "batch elements"]
    | Sequence[Sequence[int]]
    | Sequence[int]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Sequence[Sequence[Sequence[float]]]
    | Sequence[Sequence[float]]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | None = None,
    num_elements: int | Sequence[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType | Literal["dataclass", "dict"] = OutputType.dataclass,
    return_intermediates: bool = False,
    font: str | Path | ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
    ratio_list: Sequence[float] | None = None,
    text_spacing: int | None = None,
    candi_res: int | None = None,
    saliency: ImageInput
    | Sequence[ImageInput]
    | Float[torch.Tensor, "batch height width"]
    | None = None,
    candidate_boxes: Sequence[CandidateBoxRow]
    | Sequence[Sequence[CandidateBoxRow]]
    | None = None,
    return_text_lines: bool = False,
    score_normalization: Literal["mos", "raw"] = "mos",
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | str
            | list[int]
            | list[SmartTextCandidate]
            | None,
        ]
        | None,
    ]
):
    """Generate text placement boxes for content images.

    Args:
        images: RGB image or image batch.
        content: Optional content carrier.
        prompt: Prompt text payload.
        text: Alias for prompt.
        batch_size: Expected batch size for validation.
        seed: Optional seed used only when ``generator`` is absent.
        generator: Explicit torch generator; wins over ``seed``.
        condition_type: Must normalize to ``content_image``.
        labels: Unsupported v1 compatibility argument.
        bbox: Unsupported v1 compatibility argument.
        mask: Unsupported v1 compatibility argument.
        num_elements: Unsupported v1 compatibility argument.
        box_format: Public v1 compatibility argument.
        normalized: Public v1 compatibility argument.
        canvas_size: Optional source canvas size override.
        num_inference_steps: Unused v1 compatibility argument.
        output_type: ``dataclass`` or ``dict``.
        return_intermediates: Whether to include intermediate payloads.
        font: TrueType font path or PIL font object.
        ratio_list: Optional per-line font ratios.
        text_spacing: Optional text-spacing override.
        candi_res: Optional top-k override.
        saliency: Optional saliency map bypassing BASNet.
        candidate_boxes: Optional reference-style candidates.
        return_text_lines: Return per-line boxes for top candidate.
        score_normalization: ``mos`` or ``raw``.

    Returns:
        Shared layout output.
    """
    del (
        labels,
        bbox,
        mask,
        num_elements,
        box_format,
        normalized,
        canvas_size,
        num_inference_steps,
    )
    normalize_condition_type(condition_type)
    self.prepare_generator(generator=generator, seed=seed, device=self.device)
    if font is None:
        font = ImageFont.load_default()
    effective_config = self.config
    if text_spacing is not None:
        effective_config = copy.copy(self.config)
        effective_config.text_spacing = int(text_spacing)
    encoded = self.processor(
        images,
        content=content,
        prompt=prompt,
        text=text,
        saliency=saliency,
        candidate_boxes=candidate_boxes,
        font=font,
    )
    if len(encoded["images"]) != 1 or batch_size != 1:
        raise ValueError("SmartText currently decodes one image at a time")

    image = encoded["images"][0]
    prompt_text = encoded["prompts"][0]
    resolved_saliency = encoded["saliency"]
    if resolved_saliency is None:
        basnet_values = encoded["basnet_pixel_values"].to(self._runtime_device())
        saliency_out = self.saliency_model(basnet_values)
        resolved_saliency = saliency_out.saliency[0]
    candidates = encoded["candidate_boxes"]
    if candidates is None:
        candidates = generate_candidates(
            image,
            cast(torch.Tensor, resolved_saliency),
            prompt=prompt_text,
            font=font,
            config=effective_config,
            ratio_list=ratio_list,
        )
    pixel_values, boxes, candidates = prepare_scorer_batch(
        image,
        candidates,
        config=effective_config,
    )
    scores = self.scorer(
        pixel_values.to(self._runtime_device()),
        boxes.to(self._runtime_device()),
    ).scores
    top_k = candi_res or self.config.candi_res
    raw_scores = scores.detach().cpu().float().flatten()
    if candidates:
        selected_color_index = max(
            range(len(candidates)), key=lambda index: float(raw_scores[index])
        )
        text_color = choose_text_color(
            image,
            candidates[selected_color_index].bbox_ltrb_px,
            contrast_threshold=self.config.contrast_threshold,
        )
    else:
        text_color = None
    intermediates = None
    if return_intermediates:
        intermediates = {
            "saliency": resolved_saliency,
            "raw_scorer_boxes": boxes.detach().cpu(),
            "prompt": prompt_text,
        }
    return self.processor.decode(
        candidates=candidates,
        scores=scores,
        image_size=image.size,
        output_type=cast(Literal["dataclass", "dict"], str(output_type)),
        return_text_lines=return_text_lines,
        top_k=top_k,
        score_normalization=score_normalization,
        text_color=text_color,
        intermediates=intermediates,
    )

normalize_condition_type

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

Normalize SmartText condition aliases.

Parameters:

Name Type Description Default
condition_type ConditionType | str | None

Canonical condition, alias, or None.

required

Returns:

Type Description
ConditionType

ConditionType.content_image.

Raises:

Type Description
NotImplementedError

If the requested mode is unsupported by SmartText.

Source code in models/smarttext/src/smarttext/pipeline_smarttext.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
def normalize_condition_type(
    condition_type: ConditionType | str | None,
) -> ConditionType:
    """Normalize SmartText condition aliases.

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

    Returns:
        ``ConditionType.content_image``.

    Raises:
        NotImplementedError: If the requested mode is unsupported by SmartText.
    """
    canonical = (
        ConditionType.content_image
        if condition_type is None
        else normalize_shared_condition_type(condition_type)
    )
    if canonical is not ConditionType.content_image:
        raise NotImplementedError(
            "SmartText requires condition_type='content_image' with image/content and text payloads"
        )

    return canonical

processing_smarttext

Processor for SmartText content-image inputs and layout decoding.

SmartTextProcessor

Bases: ProcessorMixin

Normalize SmartText content payloads and decode candidate scores.

Parameters:

Name Type Description Default
image_processor SmartTextImageProcessor | None

Image processor for RGB and BASNet tensors.

None
config SmartTextConfig

SmartText configuration.

required

Examples:

>>> processor = SmartTextProcessor(config=SmartTextConfig())
>>> processor.id2label
{0: 'text'}
Source code in models/smarttext/src/smarttext/processing_smarttext.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 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
class SmartTextProcessor(ProcessorMixin):
    """Normalize SmartText content payloads and decode candidate scores.

    Args:
        image_processor: Image processor for RGB and BASNet tensors.
        config: SmartText configuration.

    Examples:
        >>> processor = SmartTextProcessor(config=SmartTextConfig())
        >>> processor.id2label
        {0: 'text'}
    """

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

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

    def save_pretrained(
        self,
        save_directory: str | Path,
        push_to_hub: bool = False,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Save processor metadata and image-processor config.

        Args:
            save_directory: Directory receiving ``processor_config.json`` and
                ``preprocessor_config.json``.
            push_to_hub: Accepted for ``ProcessorMixin`` compatibility; Hub
                upload is handled outside this helper.
            kwargs: Accepted for ``ProcessorMixin`` compatibility.
        """
        del push_to_hub, kwargs
        root = Path(save_directory)
        root.mkdir(parents=True, exist_ok=True)
        payload = {
            "processor_class": self.__class__.__name__,
            "id2label": self.id2label,
            "config": self.config.to_dict(),
        }
        (root / self.config_name).write_text(
            json.dumps(payload, indent=2, sort_keys=True),
            encoding="utf-8",
        )
        self.image_processor.save_pretrained(root)

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

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

        Returns:
            Loaded SmartText processor.
        """
        del cache_dir, force_download, local_files_only, token, revision, kwargs
        root = Path(pretrained_model_name_or_path)
        if subfolder is not None:
            root = root / subfolder
        payload = json.loads((root / cls.config_name).read_text(encoding="utf-8"))
        config = SmartTextConfig.from_dict(payload.get("config", {}))
        image_processor = SmartTextImageProcessor.from_pretrained(root)
        return cls(
            image_processor=image_processor,
            config=config,
            id2label=payload.get("id2label"),
        )

    def __call__(
        self,
        images: ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch channels height width"]
        | None = None,
        *,
        content: Mapping[
            str,
            ImageInput
            | Sequence[ImageInput]
            | str
            | Sequence[str]
            | Float[torch.Tensor, "batch height width"]
            | Sequence[CandidateBoxRow]
            | Sequence[Sequence[CandidateBoxRow]],
        ]
        | None = None,
        prompt: str | Sequence[str] | None = None,
        text: str | Sequence[str] | None = None,
        saliency: ImageInput
        | Sequence[ImageInput]
        | Float[torch.Tensor, "batch height width"]
        | None = None,
        candidate_boxes: Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]]
        | None = None,
        font: str | Path | ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
        return_tensors: Literal["pt"] = "pt",
        **kwargs: str | int | float | bool | None,
    ) -> BatchEncoding:
        """Encode SmartText public inputs.

        Args:
            images: Image or image batch.
            content: Optional content carrier with ``image``, ``texts``,
                ``saliency``, ``canvas_size``, and ``metadata`` fields.
            prompt: Prompt text payload.
            text: Alias for prompt text.
            saliency: Optional saliency map.
            candidate_boxes: Optional reference-style candidate rows.
            font: Font path or PIL font object.
            return_tensors: Tensor framework. Only ``pt`` is supported.
            kwargs: Ignored forward-compatibility kwargs.

        Returns:
            Batch encoding containing normalized payloads.
        """
        del kwargs
        if return_tensors != "pt":
            raise ValueError("SmartTextProcessor only supports return_tensors='pt'")

        content = dict(content or {})
        resolved_images = images
        if resolved_images is None:
            resolved_images = content.get("image")
        if resolved_images is None:
            resolved_images = content.get("images")
        if resolved_images is None:
            raise ValueError("SmartText requires an image/content payload")

        image_rows = _ensure_image_list(
            cast(
                ImageInput
                | Sequence[ImageInput]
                | Float[torch.Tensor, "batch channels height width"],
                resolved_images,
            )
        )
        prompt_rows = _resolve_prompt_rows(
            prompt=prompt,
            text=text,
            content=content,
            batch_size=len(image_rows),
        )
        encoded = self.image_processor.preprocess(
            image_rows, return_tensors=return_tensors
        )
        basnet = self.image_processor.preprocess_basnet(
            image_rows, return_tensors=return_tensors
        )
        saliency_payload = saliency if saliency is not None else content.get("saliency")
        candidates = _decode_candidate_payload(candidate_boxes)
        encoded.update(
            {
                "basnet_pixel_values": basnet["basnet_pixel_values"],
                "images": image_rows,
                "prompts": prompt_rows,
                "font": font,
                "saliency": saliency_payload,
                "candidate_boxes": candidates,
            }
        )
        return BatchEncoding(encoded)

    def decode(
        self,
        *,
        candidates: Sequence[SmartTextCandidate],
        scores: Float[torch.Tensor, "candidates"],
        image_size: tuple[int, int],
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_text_lines: bool = False,
        top_k: int = 3,
        score_normalization: Literal["mos", "raw"] = "mos",
        text_color: str | None = None,
        intermediates: dict[
            str,
            Shaped[torch.Tensor, "..."]
            | str
            | list[SmartTextCandidate]
            | list[int]
            | None,
        ]
        | None = None,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | dict[int, str]
            | dict[
                str,
                Shaped[torch.Tensor, "..."]
                | str
                | list[SmartTextCandidate]
                | list[int]
                | None,
            ]
            | None,
        ]
    ):
        """Decode sorted candidates into the shared layout schema.

        Args:
            candidates: Candidate metadata.
            scores: Raw scorer outputs.
            image_size: Source image size as ``(width, height)``.
            output_type: Return dataclass or dict.
            return_text_lines: Return per-line boxes instead of top-level boxes.
            top_k: Number of top candidates to return.
            score_normalization: ``mos`` or ``raw`` score mode.
            text_color: Optional selected text color.
            intermediates: Optional extra intermediate payload.

        Returns:
            Shared ``LayoutGenerationOutput`` or dictionary.
        """
        if not candidates:
            raise ValueError("SmartText cannot decode an empty candidate list")

        raw_scores = scores.detach().cpu().float().flatten()
        order = sorted(
            range(len(candidates)),
            key=lambda index: float(raw_scores[index]),
            reverse=True,
        )
        selected = [candidates[index] for index in order[:top_k]]
        selected_indexes = order[:top_k]
        if return_text_lines:
            rows = [line.bbox_ltrb_px for line in selected[0].lines]
            selected_scores = raw_scores.new_full(
                (len(rows),),
                float(raw_scores[selected_indexes[0]].item()),
            )
        else:
            rows = [candidate.bbox_ltrb_px for candidate in selected]
            selected_scores = raw_scores[selected_indexes]
        bbox_ltrb = torch.tensor(rows, dtype=torch.float32).unsqueeze(0)
        bbox = normalize_boxes(bbox_ltrb, canvas_size=image_size, box_format="ltrb")
        labels = torch.zeros((1, bbox.shape[1]), dtype=torch.long)
        mask = torch.ones((1, bbox.shape[1]), dtype=torch.bool)
        public_scores = selected_scores
        if score_normalization == "mos":
            public_scores = public_scores * self.config.mos_std + self.config.mos_mean
        elif score_normalization != "raw":
            raise ValueError(f"Unsupported score_normalization: {score_normalization}")

        merged_intermediates = dict(intermediates or {})
        merged_intermediates.update(
            {
                "candidates": list(candidates),
                "selected_indexes": selected_indexes,
                "score_normalization": score_normalization,
            }
        )
        if text_color is not None:
            merged_intermediates["text_color"] = text_color
        output = LayoutGenerationOutput(
            bbox=bbox,
            labels=labels,
            mask=mask,
            id2label=dict(self.id2label),
            scores=public_scores.unsqueeze(0),
            intermediates=merged_intermediates,
        )
        if output_type == "dict":
            return dict(output)
        if output_type == "dataclass":
            return output
        raise ValueError(f"Unsupported output_type: {output_type}")

__init__

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

Initialize processor.

Source code in models/smarttext/src/smarttext/processing_smarttext.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def __init__(
    self,
    *,
    image_processor: SmartTextImageProcessor | None = None,
    config: SmartTextConfig,
    id2label: Mapping[int | str, str] | None = None,
) -> None:
    """Initialize processor."""
    self.config = config
    self.image_processor = image_processor or SmartTextImageProcessor.from_config(
        self.config
    )
    label_source = (
        id2label
        if id2label is not None
        else cast(dict[int, str], self.config.id2label)
    )
    self.id2label = {int(k): v for k, v in label_source.items()}
    self.chat_template = None

save_pretrained

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

Save processor metadata and image-processor config.

Parameters:

Name Type Description Default
save_directory str | Path

Directory receiving processor_config.json and preprocessor_config.json.

required
push_to_hub bool

Accepted for ProcessorMixin compatibility; Hub upload is handled outside this helper.

False
kwargs str | int | float | bool | None

Accepted for ProcessorMixin compatibility.

{}
Source code in models/smarttext/src/smarttext/processing_smarttext.py
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
def save_pretrained(
    self,
    save_directory: str | Path,
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Save processor metadata and image-processor config.

    Args:
        save_directory: Directory receiving ``processor_config.json`` and
            ``preprocessor_config.json``.
        push_to_hub: Accepted for ``ProcessorMixin`` compatibility; Hub
            upload is handled outside this helper.
        kwargs: Accepted for ``ProcessorMixin`` compatibility.
    """
    del push_to_hub, kwargs
    root = Path(save_directory)
    root.mkdir(parents=True, exist_ok=True)
    payload = {
        "processor_class": self.__class__.__name__,
        "id2label": self.id2label,
        "config": self.config.to_dict(),
    }
    (root / self.config_name).write_text(
        json.dumps(payload, indent=2, sort_keys=True),
        encoding="utf-8",
    )
    self.image_processor.save_pretrained(root)

from_pretrained classmethod

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

Load processor metadata from a local checkpoint 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 ProcessorMixin compatibility.

None
force_download bool

Accepted for ProcessorMixin compatibility.

False
local_files_only bool

Accepted for API compatibility.

False
token str | bool | None

Accepted for ProcessorMixin compatibility.

None
revision str

Accepted for ProcessorMixin compatibility.

'main'
subfolder str | None

Optional processor subfolder.

None
kwargs str | int | float | bool | None

Ignored compatibility kwargs.

{}

Returns:

Type Description
Self

Loaded SmartText processor.

Source code in models/smarttext/src/smarttext/processing_smarttext.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    subfolder: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> Self:
    """Load processor metadata from a local checkpoint directory.

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

    Returns:
        Loaded SmartText processor.
    """
    del cache_dir, force_download, local_files_only, token, revision, kwargs
    root = Path(pretrained_model_name_or_path)
    if subfolder is not None:
        root = root / subfolder
    payload = json.loads((root / cls.config_name).read_text(encoding="utf-8"))
    config = SmartTextConfig.from_dict(payload.get("config", {}))
    image_processor = SmartTextImageProcessor.from_pretrained(root)
    return cls(
        image_processor=image_processor,
        config=config,
        id2label=payload.get("id2label"),
    )

__call__

__call__(
    images: ImageInput
    | Sequence[ImageInput]
    | Float[Tensor, "batch channels height width"]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | str
        | Sequence[str]
        | Float[Tensor, "batch height width"]
        | Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    text: str | Sequence[str] | None = None,
    saliency: ImageInput
    | Sequence[ImageInput]
    | Float[Tensor, "batch height width"]
    | None = None,
    candidate_boxes: Sequence[CandidateBoxRow]
    | Sequence[Sequence[CandidateBoxRow]]
    | None = None,
    font: str
    | Path
    | FreeTypeFont
    | ImageFont
    | None = None,
    return_tensors: Literal["pt"] = "pt",
    **kwargs: str | int | float | bool | None,
) -> BatchEncoding

Encode SmartText public inputs.

Parameters:

Name Type Description Default
images ImageInput | Sequence[ImageInput] | Float[Tensor, 'batch channels height width'] | None

Image or image batch.

None
content Mapping[str, ImageInput | Sequence[ImageInput] | str | Sequence[str] | Float[Tensor, 'batch height width'] | Sequence[CandidateBoxRow] | Sequence[Sequence[CandidateBoxRow]]] | None

Optional content carrier with image, texts, saliency, canvas_size, and metadata fields.

None
prompt str | Sequence[str] | None

Prompt text payload.

None
text str | Sequence[str] | None

Alias for prompt text.

None
saliency ImageInput | Sequence[ImageInput] | Float[Tensor, 'batch height width'] | None

Optional saliency map.

None
candidate_boxes Sequence[CandidateBoxRow] | Sequence[Sequence[CandidateBoxRow]] | None

Optional reference-style candidate rows.

None
font str | Path | FreeTypeFont | ImageFont | None

Font path or PIL font object.

None
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

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

Ignored forward-compatibility kwargs.

{}

Returns:

Type Description
BatchEncoding

Batch encoding containing normalized payloads.

Source code in models/smarttext/src/smarttext/processing_smarttext.py
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
def __call__(
    self,
    images: ImageInput
    | Sequence[ImageInput]
    | Float[torch.Tensor, "batch channels height width"]
    | None = None,
    *,
    content: Mapping[
        str,
        ImageInput
        | Sequence[ImageInput]
        | str
        | Sequence[str]
        | Float[torch.Tensor, "batch height width"]
        | Sequence[CandidateBoxRow]
        | Sequence[Sequence[CandidateBoxRow]],
    ]
    | None = None,
    prompt: str | Sequence[str] | None = None,
    text: str | Sequence[str] | None = None,
    saliency: ImageInput
    | Sequence[ImageInput]
    | Float[torch.Tensor, "batch height width"]
    | None = None,
    candidate_boxes: Sequence[CandidateBoxRow]
    | Sequence[Sequence[CandidateBoxRow]]
    | None = None,
    font: str | Path | ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
    return_tensors: Literal["pt"] = "pt",
    **kwargs: str | int | float | bool | None,
) -> BatchEncoding:
    """Encode SmartText public inputs.

    Args:
        images: Image or image batch.
        content: Optional content carrier with ``image``, ``texts``,
            ``saliency``, ``canvas_size``, and ``metadata`` fields.
        prompt: Prompt text payload.
        text: Alias for prompt text.
        saliency: Optional saliency map.
        candidate_boxes: Optional reference-style candidate rows.
        font: Font path or PIL font object.
        return_tensors: Tensor framework. Only ``pt`` is supported.
        kwargs: Ignored forward-compatibility kwargs.

    Returns:
        Batch encoding containing normalized payloads.
    """
    del kwargs
    if return_tensors != "pt":
        raise ValueError("SmartTextProcessor only supports return_tensors='pt'")

    content = dict(content or {})
    resolved_images = images
    if resolved_images is None:
        resolved_images = content.get("image")
    if resolved_images is None:
        resolved_images = content.get("images")
    if resolved_images is None:
        raise ValueError("SmartText requires an image/content payload")

    image_rows = _ensure_image_list(
        cast(
            ImageInput
            | Sequence[ImageInput]
            | Float[torch.Tensor, "batch channels height width"],
            resolved_images,
        )
    )
    prompt_rows = _resolve_prompt_rows(
        prompt=prompt,
        text=text,
        content=content,
        batch_size=len(image_rows),
    )
    encoded = self.image_processor.preprocess(
        image_rows, return_tensors=return_tensors
    )
    basnet = self.image_processor.preprocess_basnet(
        image_rows, return_tensors=return_tensors
    )
    saliency_payload = saliency if saliency is not None else content.get("saliency")
    candidates = _decode_candidate_payload(candidate_boxes)
    encoded.update(
        {
            "basnet_pixel_values": basnet["basnet_pixel_values"],
            "images": image_rows,
            "prompts": prompt_rows,
            "font": font,
            "saliency": saliency_payload,
            "candidate_boxes": candidates,
        }
    )
    return BatchEncoding(encoded)

decode

decode(
    *,
    candidates: Sequence[SmartTextCandidate],
    scores: Float[Tensor, "candidates"],
    image_size: tuple[int, int],
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_text_lines: bool = False,
    top_k: int = 3,
    score_normalization: Literal["mos", "raw"] = "mos",
    text_color: str | None = None,
    intermediates: dict[
        str,
        Shaped[Tensor, "..."]
        | str
        | list[SmartTextCandidate]
        | list[int]
        | None,
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | str
            | list[SmartTextCandidate]
            | list[int]
            | None,
        ]
        | None,
    ]
)

Decode sorted candidates into the shared layout schema.

Parameters:

Name Type Description Default
candidates Sequence[SmartTextCandidate]

Candidate metadata.

required
scores Float[Tensor, 'candidates']

Raw scorer outputs.

required
image_size tuple[int, int]

Source image size as (width, height).

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

Return dataclass or dict.

'dataclass'
return_text_lines bool

Return per-line boxes instead of top-level boxes.

False
top_k int

Number of top candidates to return.

3
score_normalization Literal['mos', 'raw']

mos or raw score mode.

'mos'
text_color str | None

Optional selected text color.

None
intermediates dict[str, Shaped[Tensor, '...'] | str | list[SmartTextCandidate] | list[int] | None] | None

Optional extra intermediate payload.

None

Returns:

Type Description
LayoutGenerationOutput | dict[str, Shaped[Tensor, '...'] | dict[int, str] | dict[str, Shaped[Tensor, '...'] | str | list[SmartTextCandidate] | list[int] | None] | None]

Shared LayoutGenerationOutput or dictionary.

Source code in models/smarttext/src/smarttext/processing_smarttext.py
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
def decode(
    self,
    *,
    candidates: Sequence[SmartTextCandidate],
    scores: Float[torch.Tensor, "candidates"],
    image_size: tuple[int, int],
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_text_lines: bool = False,
    top_k: int = 3,
    score_normalization: Literal["mos", "raw"] = "mos",
    text_color: str | None = None,
    intermediates: dict[
        str,
        Shaped[torch.Tensor, "..."]
        | str
        | list[SmartTextCandidate]
        | list[int]
        | None,
    ]
    | None = None,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Shaped[torch.Tensor, "..."]
        | dict[int, str]
        | dict[
            str,
            Shaped[torch.Tensor, "..."]
            | str
            | list[SmartTextCandidate]
            | list[int]
            | None,
        ]
        | None,
    ]
):
    """Decode sorted candidates into the shared layout schema.

    Args:
        candidates: Candidate metadata.
        scores: Raw scorer outputs.
        image_size: Source image size as ``(width, height)``.
        output_type: Return dataclass or dict.
        return_text_lines: Return per-line boxes instead of top-level boxes.
        top_k: Number of top candidates to return.
        score_normalization: ``mos`` or ``raw`` score mode.
        text_color: Optional selected text color.
        intermediates: Optional extra intermediate payload.

    Returns:
        Shared ``LayoutGenerationOutput`` or dictionary.
    """
    if not candidates:
        raise ValueError("SmartText cannot decode an empty candidate list")

    raw_scores = scores.detach().cpu().float().flatten()
    order = sorted(
        range(len(candidates)),
        key=lambda index: float(raw_scores[index]),
        reverse=True,
    )
    selected = [candidates[index] for index in order[:top_k]]
    selected_indexes = order[:top_k]
    if return_text_lines:
        rows = [line.bbox_ltrb_px for line in selected[0].lines]
        selected_scores = raw_scores.new_full(
            (len(rows),),
            float(raw_scores[selected_indexes[0]].item()),
        )
    else:
        rows = [candidate.bbox_ltrb_px for candidate in selected]
        selected_scores = raw_scores[selected_indexes]
    bbox_ltrb = torch.tensor(rows, dtype=torch.float32).unsqueeze(0)
    bbox = normalize_boxes(bbox_ltrb, canvas_size=image_size, box_format="ltrb")
    labels = torch.zeros((1, bbox.shape[1]), dtype=torch.long)
    mask = torch.ones((1, bbox.shape[1]), dtype=torch.bool)
    public_scores = selected_scores
    if score_normalization == "mos":
        public_scores = public_scores * self.config.mos_std + self.config.mos_mean
    elif score_normalization != "raw":
        raise ValueError(f"Unsupported score_normalization: {score_normalization}")

    merged_intermediates = dict(intermediates or {})
    merged_intermediates.update(
        {
            "candidates": list(candidates),
            "selected_indexes": selected_indexes,
            "score_normalization": score_normalization,
        }
    )
    if text_color is not None:
        merged_intermediates["text_color"] = text_color
    output = LayoutGenerationOutput(
        bbox=bbox,
        labels=labels,
        mask=mask,
        id2label=dict(self.id2label),
        scores=public_scores.unsqueeze(0),
        intermediates=merged_intermediates,
    )
    if output_type == "dict":
        return dict(output)
    if output_type == "dataclass":
        return output
    raise ValueError(f"Unsupported output_type: {output_type}")