Repository: Project-MONAI/MONAI
File: monai/transforms/compose.py (Python)

Code before:
            data = apply_transform(self.transforms[o].inverse, data, self.map_items, self.unpack_items, self.log_stats)
        return data


class SomeOf(Compose):
    """
    ``SomeOf`` provides the ability to randomly sample a fixed number or
    up to a fixed number of transforms from a list of callables.

    If sampling up to the max_samples, each invocation of SomeOf uniformly samples a target number of transforms
    for that invocation.

    At each invocation the set of transforms to apply is uniformly sampled without replacement.

    Args:
        transforms: list of callables.
        map_items: whether to apply transform to each item in the input `data` if `data` is a list or tuple.
            Defaults to `True`.
        unpack_items: whether to unpack input `data` with `*` as parameters for the callable function of transform.
            Defaults to `False`.
        log_stats: whether to log the detailed information of data and applied transform when error happened,
            for NumPy array and PyTorch Tensor, log the data shape and value range,
            for other metadata, log the values directly. Default to `False`.
        min_samples: minimum number of transforms to sample. Defaults to `0`.
        max_samples: maximum number of transforms to sample. Defaults to `3`.
        fixed: whether to sample exactly `max_samples` transforms, or up to it. Defaults to `False`.
    """

    def __init__(
        self,
        transforms: Sequence[Callable] | Callable | None = None,
        map_items: bool = True,
        unpack_items: bool = False,
        log_stats: bool = False,
        min_samples: int = 0,
        max_samples: int = 3,
        fixed: bool = True,
    ) -> None:
        super().__init__(transforms, map_items, unpack_items, log_stats)
        if transforms is None:
            transforms = []
        self.original_transforms = transforms
        self.n_transforms = len(transforms)
        self.max_samples = min(self.n_transforms, max_samples)
        self.fixed = fixed
        self.min_samples = min_samples
        self.random = random.Random(get_seed())

    def __call__(self, data):
        if self.n_transforms != 0:
            sample_size = self.max_samples if self.fixed else self.random.randint(self.min_samples, self.max_samples)

Review comment (refactor):
This arg feels a bit duplicated with `min_samples ` and `max_samples `; perhaps you can directly use an arg `n_transforms` to replace these three. This parameter can accept a single int or sequence, if a single int means exactly applying `n` transforms.

Code after:
        elif weights is None or isinstance(weights, float):
            weights = [1.0 / len(self.transforms)] * len(self.transforms)
        if len(weights) != len(self.transforms):
            raise ValueError(
                "transforms and weights should be same size if both specified as sequences, "
                f"got {len(weights)} and {len(self.transforms)}."
            )
        self.weights = ensure_tuple(self._normalize_probabilities(weights))

    def _normalize_probabilities(self, weights):
        if len(weights) == 0:
            return weights
        weights = np.array(weights)
        if np.any(weights < 0):
            raise ValueError(f"Probabilities must be greater than or equal to zero, got {weights}.")
        if np.all(weights == 0):
            raise ValueError(f"At least one probability must be greater than zero, got {weights}.")
        weights = weights / weights.sum()
        return list(weights)

    def flatten(self):
        transforms = []
        weights = []
        for t, w in zip(self.transforms, self.weights):
            # if nested, probability is the current weight multiplied by the nested weights,
            # and so on recursively
            if isinstance(t, OneOf):
                tr = t.flatten()
                for t_, w_ in zip(tr.transforms, tr.weights):
                    transforms.append(t_)
                    weights.append(w_ * w)
            else:
                transforms.append(t)
                weights.append(w)
        return OneOf(transforms, weights, self.map_items, self.unpack_items)

    def __call__(self, data):
        if len(self.transforms) == 0:
            return data
        index = self.R.multinomial(1, self.weights).argmax()
        _transform = self.transforms[index]
        data = apply_transform(_transform, data, self.map_items, self.unpack_items, self.log_stats)
        # if the data is a mapping (dictionary), append the OneOf transform to the end
        if isinstance(data, monai.data.MetaTensor):
            self.push_transform(data, extra_info={"index": index})
        elif isinstance(data, Mapping):
            for key in data:  # dictionary not change size during iteration
                if isinstance(data[key], monai.data.MetaTensor):
                    self.push_transform(data[key], extra_info={"index": index})
        return data