code: def parse_assignment_indices(indices, shape):
      """Reformat the indices for assignment.
  
      The aim of this is to convert the indices to a standardised form
      so that it is easier to ascertain which chunks are touched by the
      indices.
  
      This function is intended to be called by `setitem_array`.
  
      A slice object that is decreasing (i.e. with a negative step), is
      recast as an increasing slice (i.e. with a postive step. For
      example ``slice(7,3,-1)`` would be cast as ``slice(4,8,1)``. This
      is to facilitate finding which blocks are touched by the
      index. The dimensions for which this has occured are returned by
      the function.
  
      Parameters
      ----------
      indices : numpy-style indices
          Indices to array defining the elements to be assigned.
      shape : sequence of `int`
          The shape of the array.
  
      Returns
      -------
      parsed_indices : `list`
          The reformated indices that are equivalent to the input
          indices.
      implied_shape : `list`
          The shape implied by the parsed indices. For instance, indices
          of ``(slice(0,2), 5, [4,1,-1])`` will have implied shape
          ``[2,3]``.
      reverse : `list`
          The positions of the dimensions whose indices in the
          parsed_indices output are reversed slices.
      implied_shape_positions: `list`
          The positions of the dimensions whose indices contribute to
          the implied_shape. For instance, indices of ``(slice(0,2), 5,
          [4,1,-1])`` will have implied_shape ``[2,3]`` and
          implied_shape_positions ``[0,2]``.
  
      Examples
      --------
      >>> parse_assignment_indices((slice(1, -1),), (8,))
      ([slice(1, 7, 1)], [6], [], [0])
  
      >>> parse_assignment_indices(([1, 2, 6, 5],), (8,))
      ([array([1, 2, 6, 5])], [4], [], [0])
  
      >>> parse_assignment_indices((3, slice(-1, 2, -1)), (7, 8))
      ([3, slice(3, 8, 1)], [5], [1], [1])
  
      >>> parse_assignment_indices((slice(-1, 2, -1), 3, [1, 2]), (7, 8, 9))
      ([slice(3, 7, 1), 3, array([1, 2])], [4, 2], [0], [0, 2])
  
      >>> parse_assignment_indices((slice(0, 5), slice(3, None, 2)), (5, 4))
      ([slice(0, 5, 1), slice(3, 4, 2)], [5, 1], [], [0, 1])
  
      >>> parse_assignment_indices((slice(0, 5), slice(3, 3, 2)), (5, 4))
      ([slice(0, 5, 1), slice(3, 3, 2)], [5, 0], [], [0])
  
      """
      if not isinstance(indices, tuple):
          indices = (indices,)
  
      # Disallow scalar boolean indexing, and also indexing by scalar
      # numpy or dask array.
      #
      # numpy allows these, but Array.__getitem__ does not yet implement
      # them properly, so disallow it for now in __setitem__
      for index in indices:
          if index is True or index is False:
              raise NotImplementedError(
                  "dask does not yet implement assignment to a scalar "
                  f"boolean index: {index!r}"
              )
  
          if (
              is_arraylike(index) or is_dask_collection(index)
          ) and not index.ndim:
              raise NotImplementedError(
                  "dask does not yet implement assignment to a scalar "
                  f"numpy or dask array index: {index!r}"
              )
  
      # Inititalize output variables
      implied_shape = []
      implied_shape_positions = []
      reverse = []
      parsed_indices = list(normalize_index(indices, shape))
  
      n_lists = 0
  
      for i, (index, size) in enumerate(zip(parsed_indices, shape)):
          is_slice = isinstance(index, slice)
          if is_slice:
              # Index is a slice
              start, stop, step = index.indices(size)
              if step < 0 and stop == -1:
                  stop = None
  
              index = slice(start, stop, step)
  
              if step < 0:
                  # When the slice step is negative, transform the
                  # original slice to a new slice with a positive step
                  # such that the result of the new slice is the reverse
                  # of the result of the original slice.
                  #
                  # For example, if the original slice is slice(6,0,-2)
                  # then the new slice will be slice(2,7,2).
                  #
                  # >>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
                  # >>> a[slice(6, 0, -2)]
                  # [6, 4, 2]
                  # >>> a[slice(2, 7, 2)]
                  # [2, 4, 6]
                  # >>> a[slice(6, 0, -2)] == list(reversed(a[slice(2, 7, 2)]))
                  # True
                  start, stop, step = index.indices(size)
                  step *= -1
                  div, mod = divmod(start - stop - 1, step)
                  div_step = div * step
                  start -= div_step
                  stop = start + div_step + 1
  
                  index = slice(start, stop, step)
                  reverse.append(i)
  
              start, stop, step = index.indices(size)
  
              # Note: We now have stop >= start and step >= 0
  
              div, mod = divmod(stop - start, step)
              if not div and not mod:
                  # stop equals start => zero-sized slice for this
                  # dimension
                  implied_shape.append(0)
              else:
                  if mod != 0:
                      div += 1
  
                  implied_shape.append(div)
                  implied_shape_positions.append(i)
  
          elif isinstance(index, (int, np.integer)):
              # Index is an integer
              index = int(index)
  
          elif isinstance(index, np.ndarray) or is_dask_collection(
              index
          ):
              # Index is 1-d array
              n_lists += 1
              if n_lists > 1:
                  raise NotImplementedError(
                      "dask is currently limited to at most one "
                      "dimension's assignment index being a "
                      "1-d array of integers or booleans. "
                      f"Got: {indices}"
                  )
  
              if index.ndim != 1:
                  raise IndexError(
                      f"Incorrect shape ({index.shape}) of integer "
                      f"indices for dimension with size {size}"
                  )
  
              index_size = index.size
              if (
                  index.dtype == bool
                  and not math.isnan(index_size)
                  and index_size != size
              ):
                  raise IndexError(
                      "boolean index did not match indexed array along "
                      f"dimension {i}; dimension is {size} but "
                      f"corresponding boolean dimension is {index_size}"
                  )
  
              # Posify an integer dask array (integer numpy arrays were
              # posified in `normalize_index`)
              if is_dask_collection(index):
                  if index.dtype == bool:
                      index_size = np.nan
                  else:
                      index = np.where(index < 0, index + size, index)
  
              implied_shape.append(index_size)
              implied_shape_positions.append(i)
  
          parsed_indices[i] = index
  
      return (
          parsed_indices,
          implied_shape,
          reverse,
          implied_shape_positions,
      )
  
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 26 for calling a global function with example usage: parse_assignment_indices(indices, shape) and returns: parsed_indices,  implied_shape,  reverse,  implied_shape_positions