code: def draw_proposals(
      bounds: Bounds,
      originals: ep.Tensor,
      perturbed: ep.Tensor,
      unnormalized_source_directions: ep.Tensor,
      source_directions: ep.Tensor,
      source_norms: ep.Tensor,
      spherical_steps: ep.Tensor,
      source_steps: ep.Tensor,
  ) -> Tuple[ep.Tensor, ep.Tensor]:
      # remember the actual shape
      shape = originals.shape
      assert perturbed.shape == shape
      assert unnormalized_source_directions.shape == shape
      assert source_directions.shape == shape
  
      # flatten everything to (batch, size)
      originals = flatten(originals)
      perturbed = flatten(perturbed)
      unnormalized_source_directions = flatten(
          unnormalized_source_directions
      )
      source_directions = flatten(source_directions)
      N, D = originals.shape
  
      assert source_norms.shape == (N,)
      assert spherical_steps.shape == (N,)
      assert source_steps.shape == (N,)
  
      # draw from an iid Gaussian (we can share this across the whole batch)
      eta = ep.normal(perturbed, (D, 1))
  
      # make orthogonal (source_directions are normalized)
      eta = (
          eta.T - ep.matmul(source_directions, eta) * source_directions
      )
      assert eta.shape == (N, D)
  
      # rescale
      norms = ep.norms.l2(eta, axis=-1)
      assert norms.shape == (N,)
      eta = eta * atleast_kd(
          spherical_steps * source_norms / norms, eta.ndim
      )
  
      # project on the sphere using Pythagoras
      distances = atleast_kd(
          (spherical_steps.square() + 1).sqrt(), eta.ndim
      )
      directions = eta - unnormalized_source_directions
      spherical_candidates = originals + directions / distances
  
      # clip
      min_, max_ = bounds
      spherical_candidates = spherical_candidates.clip(min_, max_)
  
      # step towards the original inputs
      new_source_directions = originals - spherical_candidates
      assert new_source_directions.ndim == 2
      new_source_directions_norms = ep.norms.l2(
          flatten(new_source_directions), axis=-1
      )
  
      # length if spherical_candidates would be exactly on the sphere
      lengths = source_steps * source_norms
  
      # length including correction for numerical deviation from sphere
      lengths = lengths + new_source_directions_norms - source_norms
  
      # make sure the step size is positive
      lengths = ep.maximum(lengths, 0)
  
      # normalize the length
      lengths = lengths / new_source_directions_norms
      lengths = atleast_kd(lengths, new_source_directions.ndim)
  
      candidates = (
          spherical_candidates + lengths * new_source_directions
      )
  
      # clip
      candidates = candidates.clip(min_, max_)
  
      # restore shape
      candidates = candidates.reshape(shape)
      spherical_candidates = spherical_candidates.reshape(shape)
      return candidates, spherical_candidates
  
imports: from typing import Union, Tuple, Optional, Any
  from typing_extensions import Literal
  import numpy
  import eagerpy
  import logging
  from ..devutils import flatten
  from ..devutils import atleast_kd
  from ..types import Bounds
  from ..models import Model
  from ..criteria import Criterion
  from ..distances import l2
  from ..tensorboard import TensorBoard
  from .blended_noise import LinearSearchBlendedUniformNoiseAttack
  from .base import MinimizationAttack
  from .base import T
  from .base import get_criterion
  from .base import get_is_adversarial
  from .base import raise_if_kwargs
  from .base import verify_input_bounds
label_desc: Use image node_id 1 for calling a global function with example usage: draw_proposals(bounds, originals, perturbed, unnormalized_source_directions, source_directions, source_norms, spherical_steps, source_steps) and returns: candidates,  spherical_candidates