Clarify and align container-level p semantics for nested Albumentations transforms
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:
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:
other_params = {
k: v for k, v in params.items()
if k not in ("transforms", "p")
}
other_params["p"] = 1.0As a result:
- Child transform
pvalues are respected. - Explicit container-level
pvalues are ignored. OneOfandSequentialare effectively always applied because their container probability is forced to1.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:
OneOf:
p: 0.5
transforms:
- HorizontalFlip:
p: 0.6
- VerticalFlip:
p: 0.4should ideally behave like:
A.OneOf(
[
A.HorizontalFlip(p=0.6),
A.VerticalFlip(p=0.4),
],
p=0.5,
)That means:
- The
OneOfcontainer runs with 50% probability. - If it runs, one child is selected according to the child probabilities.
- Otherwise, none of the child transforms is applied.
The same principle would apply to Sequential:
Sequential:
p: 0.5
transforms:
- HorizontalFlip:
p: 1.0
- Rotate:
limit: 15
p: 1.0This would execute the complete sequence with 50% probability.
Reproduction
Example configuration:
aug_config = {
"OneOf": {
"p": 0.5,
"transforms": [
{"HorizontalFlip": {"p": 0.6}},
{"VerticalFlip": {"p": 0.4}},
],
}
}The expected behavior is equivalent to:
A.OneOf(
[
A.HorizontalFlip(p=0.6),
A.VerticalFlip(p=0.4),
],
p=0.5,
)The current behavior is effectively:
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:
OneOfSomeOfSequential- recursively nested transforms
- YAML / JSON-style configuration
- child-transform probability handling
It is unclear, however, whether forcing p=1.0 was intended:
- only for RF-DETR's internally generated resize / selection pipelines, or
- for all user-provided
aug_configvalues 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:
OneOf:
transforms:
- HorizontalFlip:
p: 0.3
- VerticalFlip:
p: 0.2
- NoOp:
p: 0.5Because 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:
A.OneOf(
[
A.HorizontalFlip(p=0.6),
A.VerticalFlip(p=0.4),
],
p=0.5,
)must currently be rewritten approximately as:
OneOf:
transforms:
- HorizontalFlip:
p: 0.30
- VerticalFlip:
p: 0.20
- NoOp:
p: 0.50This has several drawbacks:
- The container probability must be encoded manually through child weights.
- Child
pvalues 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:
OneOf:
transforms:
- Sequential:
transforms:
- HorizontalFlip:
p: 1.0
- Rotate:
limit: 15
p: 1.0
- NoOp:
p: 1.0This can approximate conditional execution, but it is more verbose and less clear than expressing the intended behavior directly:
Sequential:
p: 0.5
transforms:
- HorizontalFlip:
p: 1.0
- Rotate:
limit: 15
p: 1.0The 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:
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.0behavior whenpis 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.0when container-levelpis omitted. - Respect
pwhen 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
OneOfandSequential.
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
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_probabilityActual output:
RF-DETR: 1.10.1
Requested OneOf p: 0.2
Actual OneOf p: 1.0
AssertionErrorAdditional
No response
Are you willing to submit a PR?
- Yes, I'd like to help by submitting a PR!
Source: roboflow/rf-detr