code: def setitem_array(out_name, array, indices, value):
      """Master function for array assignment.
  
      This function, that is intended to be called by
      `Array.__setitem__`, creates a new dask that assigns values to
      each block that is touched by the indices, leaving other blocks
      unchanged.
  
      Each block that overlaps the indices is assigned from the
      approriate part of the assignment value. The dasks of these value
      parts are included in the output dask dictionary, as are the dasks
      of any 1-d dask array indices. This ensures that the dask array
      assignment value and any dask array indices are not computed until
      the `Array.__setitem__` operation is computed.
  
      The part of the assignment value applies to block is created as a
      "getitem" slice of the full asignment value.
  
      Parameters
      ----------
      out_name : `str`
          The dask variable output name.
      array : dask array
          The dask array that is being assigned to.
      indices : numpy-style indices
          Indices to array defining the elements to be assigned.
      value : dask array
          The assignment value, i.e. the values which will be assigned
          to elements of array.
  
      Returns
      -------
      dsk : `dict`
          A dictionary where the keys are new unique tokens for each
          block of the form
  
              (out_name, dim_index[, dim_index[, ...]])
  
         and the values are either
  
              (key,)
  
          or
  
              (setitem, key, v_key, block_indices)
  
          where key is an existing top-level dask key of array.
  
          The first case occurs when the block represented by key does
          not overlap the indices.
  
          The second case occurs when the block represented by key does
          overlap the indices. setitem is the chunk assignment function;
          v_key is the dask key of the the part of the assignment value
          that corresponds to the block; and block_indices are the
          assigment indices that apply to the block.
  
          The dictionary also includes any additional key/value pairs
          needed to define v_key, as well as any any additional
          key/value pairs needed to define dask keys contained in the
          block_indices list as references to dask array indices.
  
      """
  
      @functools.lru_cache
      def block_index_from_1d_index(dim, loc0, loc1, is_bool):
          """The positions of index elements in the range values loc0 and loc1.
  
          The index is the input assignment index that is defined in the
          namespace of the caller. It is assumed that negative elements
          of an integer array have already been posified.
  
          The non-hashable dsk is the output dask dictionary that is
          defined in the namespace of the caller.
  
          Parameters
          ----------
          dim : `int`
             The dimension position of the index that is used as a proxy
             for the non-hashable index to define the LRU cache key.
          loc0 : `int`
              The start index of the block along the dimension.
          loc1 : `int`
              The stop index of the block along the dimension.
          is_bool : `bool`
              Whether or not the index is of boolean data type.
  
          Returns
          -------
          numpy array or `str`
              If index is a numpy array then a numpy array is
              returned.
  
              If index is a dask array then the dask of the block index
              is inserted into the output dask dictionary, and its
              unique top-layer key is returned.
  
          """
          if is_bool:
              # Boolean array (dask or numpy)
              i = index[loc0:loc1]
          elif is_dask_collection(index):
              # Integer dask array
              #
              # Check for values in [loc0,loc1).
              #
              # Use the 3-argument "where" to insert place-holder
              # elements that will be searched for and removed in the
              # `setitem` function at compute time. The place-holder
              # value must be the size of the block, i.e. loc1-loc0. We
              # can't use a 1-argument "where" here because that won't
              # work if index has unknown chunk sizes.
              i = np.where(
                  (loc0 <= index) & (index < loc1), index, loc1
              )
              i -= loc0
          else:
              # Integer numpy array
              #
              # Check for positive values in [loc0,loc1).
              i = np.where((loc0 <= index) & (index < loc1))[0]
              i = index[i] - loc0
  
          if is_dask_collection(i):
              # Return dask key intead of dask array
              i = concatenate_array_chunks(i)
              dsk.update(dict(i.dask))
              i = next(flatten(i.__dask_keys__()))
  
          return i
  
      @functools.lru_cache
      def block_index_shape_from_1d_bool_index(dim, loc0, loc1):
          """Number of True index elements between positions loc0 and loc1.
  
          The index is the input assignment index that is defined in the
          namespace of the caller.
  
          Parameters
          ----------
          dim : `int`
             The dimension position of the index that is used as a proxy
             for the non-hashable index to define the LRU cache key.
          loc0 : `int`
              The start index of the block along the dimension.
          loc1 : `int`
              The stop index of the block along the dimension.
  
          Returns
          -------
          numpy array or dask array
              If index is a numpy array then a numpy array is
              returned.
  
              If index is dask array then a dask array is returned.
  
          """
          return np.sum(index[loc0:loc1])
  
      @functools.lru_cache
      def n_preceeding_from_1d_bool_index(dim, loc0):
          """Number of True index elements preceeding position loc0.
  
          The index is the input assignment index that is defined in the
          namespace of the caller.
  
          Parameters
          ----------
          dim : `int`
             The dimension position of the index that is used as a proxy
             for the non-hashable index to define the LRU cache key.
          loc0 : `int`
              The start index of the block along the dimension.
  
          Returns
          -------
          numpy array or dask array
              If index is a numpy array then a numpy array is
              returned.
  
              If index is dask array then a dask array is returned.
  
          """
          return np.sum(index[:loc0])
  
      @functools.lru_cache
      def value_indices_from_1d_int_index(dim, vsize, loc0, loc1):
          """Value indices for index elements between loc0 and loc1.
  
          The index is the input assignment index that is defined in the
          namespace of the caller. It is assumed that negative elements
          have already been posified.
  
          Parameters
          ----------
          dim : `int`
             The dimension position of the index that is used as a proxy
             for the non-hashable index to define the LRU cache key.
          vsize : `int`
              The full size of the dimension of the assignment value.
          loc0 : `int`
              The start index of the block along the dimension.
          loc1 : `int`
              The stop index of the block along the dimension.
  
          Returns
          -------
          numpy array or dask array
              If index is a numpy array then a numpy array is
              returned.
  
              If index is dask array then a dask array is returned.
  
          """
          # Check for values in [loc0,loc1)
          if is_dask_collection(index):
              if np.isnan(index.size):
                  # Integer dask array with unknown size.
                  #
                  # The 1-argument "where" won't work, so use the
                  # 3-argument "where" and convert to a boolean
                  # array. We concatenate the resulting boolean index
                  # and set the chunk size (which must be the full size
                  # of the dimension of the assignment value) which
                  # allows the returned array to be used as a
                  # __getitem__ index to the assignment value.
                  i = np.where(
                      (loc0 <= index) & (index < loc1), True, False
                  )
                  i = concatenate_array_chunks(i)
                  i._chunks = ((vsize,),)
              else:
                  # Integer dask array with known size
                  i = np.where((loc0 <= index) & (index < loc1))[0]
                  i = concatenate_array_chunks(i)
          else:
              # Integer numpy array.
              i = np.where((loc0 <= index) & (index < loc1))[0]
  
          return i
  
      from dask.core import flatten
  
      array_shape = array.shape
      value_shape = value.shape
      value_ndim = len(value_shape)
  
      # Reformat input indices
      (
          indices,
          implied_shape,
          reverse,
          implied_shape_positions,
      ) = parse_assignment_indices(indices, array_shape)
  
      # Empty slices can only be assigned size 1 values
      if 0 in implied_shape and value_shape and max(value_shape) > 1:
          raise ValueError(
              f"shape mismatch: value array of shape {value_shape} "
              "could not be broadcast to indexing result "
              f"of shape {tuple(implied_shape)}"
          )
  
      # Set variables needed when creating the part of the assignment
      # value that applies to each block.
      #
      #  offset: The additive offset to the assignment value dimension
      #          positions that results in the positions of the
      #          corresponding dimensions in the array. offset is a
      #          non-negative integer, and a positive value means that
      #          the array has more dimensions than the assignment
      #          value.
      #
      #  value_offset: The additive offset to the array dimension
      #                positions that results in the positions of the
      #                corresponding dimensions in the assignment
      #                value. value_offset is a non-negative integer,
      #                and a positive value means that the assignment
      #                value has more dimensions than the array.
      #
      #          For example:
      #
      #          array.shape   value.shape   offset  value_offset
      #          ------------  ------------  ------  ------------
      #          (3, 4)        (3, 4)        0       0
      #          (1, 1, 3, 4)  (3, 4)        2       0
      #          (3, 4)        (1, 1, 3, 4)  0       2
      #          ------------  ------------  ------  ------------
      #
      #  array_common_shape: The shape of those dimensions of array
      #                      which correspond to dimensions of the
      #                      assignment value.
      #
      #  value_common_shape: The shape of those dimensions of the
      #                      assignment value which correspond to
      #                      dimensions of the array.
      #
      #  base_value_indices: The indices used for initialising the
      #                      selection of the part of the assignment
      #                      value that applies to each block of
      #                      array. An element of `None` will end up
      #                      being replaced by an appropriate slice on a
      #                      block-by-block basis.
      #
      # non_broadcast_dimensions: The integer positions of
      #                           array_common_shape which do not
      #                           correspond to broadcast dimensions in
      #                           the assignment value.
      #
      # Note that array_common_shape and value_common_shape may be
      # different if there are any size 1 dimensions being brodacast.
      offset = len(implied_shape) - value_ndim
      if offset >= 0:
          # The array has the same number or more dimensions than the
          # assignment value
          array_common_shape = implied_shape[offset:]
          value_common_shape = value_shape
          value_offset = 0
          reverse = [i - offset for i in reverse if i >= offset]
      else:
          # The assigmment value has more dimensions than the array
          value_offset = -offset
          array_common_shape = implied_shape
          value_common_shape = value_shape[value_offset:]
          offset = 0
  
          # All of the extra leading dimensions must have size 1
          if value_shape[:value_offset] != (1,) * value_offset:
              raise ValueError(
                  "could not broadcast input array from shape"
                  f"{value_shape} into shape {tuple(implied_shape)}"
              )
  
      base_value_indices = []
      non_broadcast_dimensions = []
  
      for i, (a, b, j) in enumerate(
          zip(
              array_common_shape,
              value_common_shape,
              implied_shape_positions,
          )
      ):
          index = indices[j]
          if is_dask_collection(index) and index.dtype == bool:
              if math.isnan(b) or b <= index.size:
                  base_value_indices.append(None)
                  non_broadcast_dimensions.append(i)
              else:
                  raise ValueError(
                      f"shape mismatch: value array dimension size of {b} is "
                      "greater then corresponding boolean index size of "
                      f"{index.size}"
                  )
  
              continue
  
          if b == 1:
              base_value_indices.append(slice(None))
          elif a == b:
              base_value_indices.append(None)
              non_broadcast_dimensions.append(i)
          elif math.isnan(a):
              base_value_indices.append(None)
              non_broadcast_dimensions.append(i)
          else:
              raise ValueError(
                  f"shape mismatch: value array of shape {value_shape} "
                  "could not be broadcast to indexing result of shape "
                  f"{tuple(implied_shape)}"
              )
  
      # Translate chunks tuple to a set of array locations in product
      # order
      chunks = array.chunks
      cumdims = [
          cached_cumsum(bds, initial_zero=True) for bds in chunks
      ]
      array_locations = [
          [(s, s + dim) for s, dim in zip(starts, shapes)]
          for starts, shapes in zip(cumdims, chunks)
      ]
      array_locations = product(*array_locations)
  
      # Get the dask keys of the most recent layer in the same order as
      # the array locations.
      in_keys = list(flatten(array.__dask_keys__()))
  
      # Create a new "setitem" dask entry for each block in the array
      dsk = {}
      out_name = (out_name,)
      for in_key, locations in zip(in_keys, array_locations):
          # Now loop round each block dimension.
          #
          # If the block overlaps the indices then set the following
          # (which will be used to define a new dask entry):
          #
          # block_indices: The indices that will be used to assign to
          #                this block.
          #
          # block_indices_shape: The shape implied by block_indices.
          #
          # block_preceeding_sizes: How many assigned elements precede
          #                         this block along each dimension that
          #                         doesn't have an integer. It is
          #                         assumed that a slice will have a
          #                         positive step, as will be the case
          #                         for reformatted indices. `None` is
          #                         used for dimensions with 1-d integer
          #                         arrays.
          block_indices = []
          block_indices_shape = []
          block_preceeding_sizes = []
  
          local_offset = offset
  
          # Assume, until demonstrated otherwise, that this block
          # overlaps the assignment indices.
          overlaps = True
  
          # Note which dimension, if any, has 1-d integer array index
          dim_1d_int_index = None
  
          for dim, (index, (loc0, loc1)) in enumerate(
              zip(indices, locations)
          ):
              integer_index = isinstance(index, int)
              if isinstance(index, slice):
                  # Index is a slice
                  stop = loc1 - loc0
                  if index.stop < loc1:
                      stop -= loc1 - index.stop
  
                  start = index.start - loc0
                  if start < 0:
                      # Make start positive
                      start %= index.step
  
                  if start >= stop:
                      # This block does not overlap the slice index
                      overlaps = False
                      break
  
                  step = index.step
                  block_index = slice(start, stop, step)
                  block_index_size, rem = divmod(stop - start, step)
                  if rem:
                      block_index_size += 1
  
                  pre = index.indices(loc0)
                  n_preceeding, rem = divmod(pre[1] - pre[0], step)
                  if rem:
                      n_preceeding += 1
  
              elif integer_index:
                  # Index is an integer
                  local_offset += 1
                  if not loc0 <= index < loc1:
                      # This block does not overlap the integer index
                      overlaps = False
                      break
  
                  block_index = index - loc0
  
              else:
                  # Index is a 1-d array
                  is_bool = index.dtype == bool
                  block_index = block_index_from_1d_index(
                      dim, loc0, loc1, is_bool
                  )
                  if is_bool:
                      block_index_size = (
                          block_index_shape_from_1d_bool_index(
                              dim, loc0, loc1
                          )
                      )
                      n_preceeding = n_preceeding_from_1d_bool_index(
                          dim, loc0
                      )
                  else:
                      block_index_size = None
                      n_preceeding = None
                      dim_1d_int_index = dim
                      loc0_loc1 = loc0, loc1
  
                  if (
                      not is_dask_collection(index)
                      and not block_index.size
                  ):
                      # This block does not overlap the 1-d numpy array
                      # index
                      overlaps = False
                      break
  
                  # Note: When the 1-d array index is a dask array then
                  #       we can't tell if this block overlaps it, so we
                  #       assume that it does. If it in fact doesn't
                  #       overlap then the part of the assignment value
                  #       that cooresponds to this block will have zero
                  #       size which, at compute time, will indicate to
                  #       the `setitem` function to pass the block
                  #       through unchanged.
  
              # Still here? This block overlaps the index for this
              # dimension.
              block_indices.append(block_index)
              if not integer_index:
                  block_indices_shape.append(block_index_size)
                  block_preceeding_sizes.append(n_preceeding)
  
          # The new dask key
          out_key = out_name + in_key[1:]
  
          if not overlaps:
              # This block does not overlap the indices for all
              # dimensions, so pass the block through unchanged.
              dsk[out_key] = in_key
              continue
  
          # Still here? Then this block overlaps the indices for all
          # dimensions and so needs to have some of its elements
          # assigned.
  
          # Initialise the indices of the assignment value that define
          # the parts of it which are to be assigned to this block
          value_indices = base_value_indices[:]
          for i in non_broadcast_dimensions:
              j = i + offset
              if j == dim_1d_int_index:
                  # Index is a 1-d integer array
                  #
                  # Define index in the current namespace for use in
                  # `value_indices_from_1d_int_index`
                  index = indices[j]
  
                  value_indices[i] = value_indices_from_1d_int_index(
                      dim_1d_int_index,
                      value_shape[i + value_offset],
                      *loc0_loc1,
                  )
              else:
                  # Index is a slice or 1-d boolean array
                  start = block_preceeding_sizes[j]
                  value_indices[i] = slice(
                      start, start + block_indices_shape[j]
                  )
  
          # If required as a consequence of reformatting any slice
          # objects of the original indices to have a positive steps,
          # reverse the indices to assignment value.
          for i in reverse:
              size = value_common_shape[i]
              start, stop, step = value_indices[i].indices(size)
              size -= 1
              start = size - start
              stop = size - stop
              if stop < 0:
                  stop = None
  
              value_indices[i] = slice(start, stop, -1)
  
          if value_ndim > len(indices):
              # The assignment value has more dimensions than array, so
              # add a leading Ellipsis to the indices of value.
              value_indices.insert(0, Ellipsis)
  
          # Create the part of the full assignment value that is to be
          # assigned to elements of this block and make sure that it has
          # just one chunk (so we can represent it with a single key in
          # the argument list of setitem).
          v = value[tuple(value_indices)]
          v = concatenate_array_chunks(v)
          v_key = next(flatten(v.__dask_keys__()))
  
          # Insert into the output dask dictionary the dask of the part
          # of assignment value for this block (not minding when we
          # overwrite any existing keys as the values will be the same).
          dsk = merge(dict(v.dask), dsk)
  
          # Define the assignment function for this block.
          dsk[out_key] = (setitem, in_key, v_key, block_indices)
  
      block_index_from_1d_index.cache_clear()
      block_index_shape_from_1d_bool_index.cache_clear()
      n_preceeding_from_1d_bool_index.cache_clear()
      value_indices_from_1d_int_index.cache_clear()
  
      return dsk
  
imports: from __future__ import annotations
  import bisect
  import functools
  import math
  import warnings
  from itertools import product
  from numbers import Integral, Number
  from operator import itemgetter
  import numpy
  from tlz import concat, memoize, merge, pluck
  from dask import config, core, utils
  from dask.array.chunk import getitem
  from dask.base import is_dask_collection, tokenize
  from dask.highlevelgraph import HighLevelGraph
  from dask.utils import cached_cumsum, is_arraylike
label_desc: Use image node_id 28 for calling a global function with example usage: setitem_array(out_name, array, indices, value) and returns: dsk, i, np, np, i