#1484·rf-detr

Clarify and align container-level p semantics for nested Albumentations transforms

Author: MNakahara-ishidatecCreated Sep 18, 2026Updated Sep 18, 2026
Labelsbugquestion

Search before asking

  • I have searched the RF-DETR issues and found no similar bug report.

Bug

Problem

RF-DETR currently ignores the container-level p value for Albumentations OneOf and Sequential transforms when they are defined in aug_config.

This differs from the standard Albumentations API, where the container-level p controls whether the whole container is applied.

This report concerns OneOf and Sequential. SomeOf appears to preserve its container-level p through the current implementation, so it is not included in this report.

For example:

python
A.OneOf(
    [
        A.HorizontalFlip(p=0.6),
        A.VerticalFlip(p=0.4),
    ],
    p=0.5,
)

means that the complete OneOf container is applied with 50% probability.

However, when the equivalent configuration is passed to RF-DETR, the container-level p is ignored and the container always runs.

Current behavior

RF-DETR recursively builds nested Albumentations transforms, but for OneOf and Sequential it explicitly removes any configured container-level p and forces the container probability to 1.0.

The current implementation is effectively equivalent to:

python
other_params = {
    k: v for k, v in params.items()
    if k not in ("transforms", "p")
}
other_params["p"] = 1.0

As a result:

  • Child transform p values are respected.
  • Explicit container-level p values are ignored.
  • OneOf and Sequential are effectively always applied because their container probability is forced to 1.0.

Relevant implementation:

The current behavior is also covered by tests:

The documentation currently describes this behavior as:

The container itself always fires.

See:

Expected behavior

For user-provided aug_config, RF-DETR should ideally preserve standard Albumentations semantics, unless there is a documented reason to intentionally diverge from them.

For example:

yaml
OneOf:
  p: 0.5
  transforms:
    - HorizontalFlip:
        p: 0.6
    - VerticalFlip:
        p: 0.4

should ideally behave like:

python
A.OneOf(
    [
        A.HorizontalFlip(p=0.6),
        A.VerticalFlip(p=0.4),
    ],
    p=0.5,
)

That means:

  1. The OneOf container runs with 50% probability.
  2. If it runs, one child is selected according to the child probabilities.
  3. Otherwise, none of the child transforms is applied.

The same principle would apply to Sequential:

yaml
Sequential:
  p: 0.5
  transforms:
    - HorizontalFlip:
        p: 1.0
    - Rotate:
        limit: 15
        p: 1.0

This would execute the complete sequence with 50% probability.

Reproduction

Example configuration:

python
aug_config = {
    "OneOf": {
        "p": 0.5,
        "transforms": [
            {"HorizontalFlip": {"p": 0.6}},
            {"VerticalFlip": {"p": 0.4}},
        ],
    }
}

The expected behavior is equivalent to:

python
A.OneOf(
    [
        A.HorizontalFlip(p=0.6),
        A.VerticalFlip(p=0.4),
    ],
    p=0.5,
)

The current behavior is effectively:

python
A.OneOf(
    [
        A.HorizontalFlip(p=0.6),
        A.VerticalFlip(p=0.4),
    ],
    p=1.0,
)

The same issue occurs with Sequential: an explicitly configured container-level p is ignored, so the complete sequence is effectively always applied.

Intent and related context

The current behavior appears intentional at the implementation level because _build_albu_transform() explicitly removes the container-level p and forces p=1.0.

This behavior was introduced as part of the nested Albumentations support in:

That PR added support for nested containers such as:

  • OneOf
  • SomeOf
  • Sequential
  • recursively nested transforms
  • YAML / JSON-style configuration
  • child-transform probability handling

It is unclear, however, whether forcing p=1.0 was intended:

  1. only for RF-DETR's internally generated resize / selection pipelines, or
  2. for all user-provided aug_config values as well.

Could the maintainers clarify whether container-level p is intentionally unsupported for user-provided OneOf and Sequential configurations?

If the divergence is intentional, documenting the reason would help make the behavior clearer. If it is not intentional, preserving explicit container-level p values would make RF-DETR configurations more consistent with the standard Albumentations API.

Current workaround using NoOp

For OneOf, a container-level probability can be approximated by adding NoOp as one of the child transforms.

For example:

yaml
OneOf:
  transforms:
    - HorizontalFlip:
        p: 0.3
    - VerticalFlip:
        p: 0.2
    - NoOp:
        p: 0.5

Because OneOf selects one child according to the relative child weights, this gives approximately:

  • 30%: HorizontalFlip
  • 20%: VerticalFlip
  • 50%: NoOp

This makes OneOf select a no-op branch 50% of the time, so no image transformation is performed in those cases.

These values are interpreted as relative child weights by OneOf; they are not independent probabilities in the same sense as the container-level p.

However, this is only a workaround and is not equivalent to respecting the container-level p.

For example, the standard Albumentations configuration:

python
A.OneOf(
    [
        A.HorizontalFlip(p=0.6),
        A.VerticalFlip(p=0.4),
    ],
    p=0.5,
)

must currently be rewritten approximately as:

yaml
OneOf:
  transforms:
    - HorizontalFlip:
        p: 0.30
    - VerticalFlip:
        p: 0.20
    - NoOp:
        p: 0.50

This has several drawbacks:

  • The container probability must be encoded manually through child weights.
  • Child p values no longer directly match the original Albumentations configuration.
  • Changing the desired container probability requires recalculating the child weights.
  • Existing Albumentations configurations cannot be copied directly.
  • The resulting configuration is less readable.

Sequential cannot use the same workaround directly

Adding NoOp as a child of Sequential does not skip the sequence, because the children of a Sequential container are processed in order.

To approximate a conditional sequence, the sequence must instead be wrapped in another OneOf, for example:

yaml
OneOf:
  transforms:
    - Sequential:
        transforms:
          - HorizontalFlip:
              p: 1.0
          - Rotate:
              limit: 15
              p: 1.0
    - NoOp:
        p: 1.0

This can approximate conditional execution, but it is more verbose and less clear than expressing the intended behavior directly:

yaml
Sequential:
  p: 0.5
  transforms:
    - HorizontalFlip:
        p: 1.0
    - Rotate:
        limit: 15
        p: 1.0

The workaround shows that similar behavior can be represented indirectly, but it does not replace support for an explicit container-level p.

Why this matters

The current behavior creates a semantic mismatch between RF-DETR's augmentation configuration and the standard Albumentations API.

Container-level probabilities are useful for:

  • Applying a group of alternative transforms only occasionally.
  • Applying a complete sequence conditionally.
  • Keeping augmentation configurations readable.
  • Reusing existing Albumentations YAML / JSON configurations.
  • Maintaining equivalent behavior between standalone Albumentations and RF-DETR.

Possible fix

For user-provided configurations, RF-DETR could preserve an explicitly specified container-level p:

python
other_params = {
    k: v for k, v in params.items()
    if k != "transforms"
}
other_params.setdefault("p", 1.0)

This would:

  • preserve the current p=1.0 behavior when p is omitted;
  • respect an explicitly configured container-level p;
  • remain compatible with existing configurations that do not specify container p.

If RF-DETR's internally generated resize configurations must always execute, those configurations could explicitly use p: 1.0 or be handled separately.

User-provided configurations and internally generated mandatory pipelines do not necessarily need to share the same probability policy.

The exact implementation may differ depending on how RF-DETR wants to distinguish user-provided augmentation configs from internally generated resize configs; the code above is only a possible minimal approach.

Backward compatibility

The proposed approach would preserve the current behavior for configurations that do not specify a container-level p:

  • Continue using p=1.0 when container-level p is omitted.
  • Respect p when it is explicitly provided.
  • Keep internally generated mandatory pipelines explicitly configured with p=1.0.
  • Update the related documentation and tests.
  • Add regression coverage for both OneOf and Sequential.

This would minimize the impact on existing configurations while making explicitly configured container-level p values effective.

However, users who currently specify a container-level p and rely on RF-DETR ignoring it would observe a behavior change. Since the current behavior is not consistent with standard Albumentations semantics, this should be considered an intentional compatibility correction or behavior change.

Environment

  • RF-DETR: current develop
  • Albumentations: version not specified
  • Python: version not specified
  • OS: not specified

Expected outcome

The main request is to clarify whether the current forced p=1.0 behavior is intentional for all user-provided aug_config values.

If it is intentional, the reason and the difference from standard Albumentations semantics should be documented clearly.

If it is not intentional, RF-DETR should preserve explicit container-level p values for user-provided OneOf and Sequential configurations, while keeping internally generated mandatory pipelines explicitly configured with p=1.0.

Environment

  • RF-DETR version: 1.10.1
  • Albumentations version: 2.0.8
  • OS: Windows 11
  • Python version: 3.12.10
  • PyTorch version: 2.7.1+cu118
  • torchvision version: 0.22.1+cu118
  • CUDA version used by PyTorch: 11.8
  • cuDNN version: 9.1.0

Minimal Reproducible Example

python3
from importlib.metadata import version
from rfdetr.datasets.transforms import _build_albu_transform

requested_probability = 0.2

transform = _build_albu_transform(
    "OneOf",
    {
        "transforms": [
            {"HorizontalFlip": {"p": 1.0}},
            {"VerticalFlip": {"p": 1.0}},
        ],
        "p": requested_probability,
    },
)

print("RF-DETR:", version("rfdetr"))
print("Requested OneOf p:", requested_probability)
print("Actual OneOf p:", transform.p)

assert transform.p == requested_probability

Actual output:

RF-DETR: 1.10.1
Requested OneOf p: 0.2
Actual OneOf p: 1.0

AssertionError

Additional

No response

Are you willing to submit a PR?

  • Yes, I'd like to help by submitting a PR!