docs: def sort_image(image, size, vertical=False, path=None, path_kwargs=None, max_interval=0, progressive_amount=0,
                 randomize=False,
                 edge_threshold=0, edge_data=None, image_threshold=None, image_mask=None, key=None, discretize=0,
                 reverse=False,
                 mirror=False, splice=0, splice_random=False):
      """
      Applies pixel sorting to an image. This is done by first creating a sort mask that describes the sorting intervals,
      and then calling apply_sort_mask to the image using the generated mask.
      :param discretize: Amount by which to "discretize" pixel values. This is done by dividing each pixel's value
      (after applying the sort key) by the given amount and then converting to an integer.
      This would mean that pixels are "binned" into discrete categories, and sorting would be different.
      Since sorting is stable, this means that pixels with low-level noise remain grouped together,
      and small details can be preserved in sorting.
      :param progressive_amount: If this is non-zero,
      then the sorting interval increases as one progresses row-by-row through the image.
      progressive_amount indicates the amount, in pixels, by which to increase the sorting interval after each row.
      :param path: The specific path used to iterate through the image, as a list of rows,
      where each row is a list of (x, y) coordinates.
      :param path_kwargs: Arguments that should be passed to the path iterator.
      :param image: A list of tuples (R,G,B) representing the pixels of the image
      :param size: The size of the image as a tuple (width, height)
      :param vertical: Whether or not the color sorting is applied vertically (the default is horizontal)
      :param max_interval: The largest interval of adjacent pixels to sort
      :param randomize: Whether to use random intervals of pixels
      :param key: The function to use for sorting, e.g. brightness or red amount.
                  This function takes a pixel and returns a value to be sorted.
      :param reverse: Whether or not to reverse the direction of the sorting
      :param mirror: Mirrors each pixel interval after sorting.
      :param splice: Splices each pixel interval after sorting, at the given position (from 0.0 to 1.0).
      :param splice_random: Splices each pixel interval at a random position.
      :param edge_threshold: If greater than zero, stops sorting intervals at pixels whose "edge detection" value
      is greater than the given threshold.
      :param edge_data: Stores edge data for the image, if it is already cached.
      :param image_threshold: If not None, uses pixel's brightness to determine sort intervals.
      Pixels that are outside the range [threshold, MAX - threshold] are not sorted. So a value of 0 will sort all pixels
      (depending on the value of other arguments, of course), while a value of 1 will not sort any pixels.
      :param image_mask: The image to use as an initial sorting mask. Edge data and other sorting intervals
      will be applied on top of this.
      :return: The pixels of the resulting image as a list of (R,G,B) tuples
      """
      width, height = size
      out_pixels = list(image)
  
      # get edge data if necessary
      if edge_threshold > 0 and edge_data is None:
          edge_data = edge_detect(image, size)
  
      if image_threshold is not None:
          image_threshold = clamp(image_threshold, 0.0, 1.0)
  
      if discretize > 0 and key is not None:
          def sort_key(p):
              return int(key(p) / discretize)
      else:
          sort_key = key
  
      # if path not given, use a horizontal or vertical path
      if path is None:
          if vertical:
              path_iterator = vertical_path(size)
          else:
              path_iterator = horizontal_path(size)
      else:
          # path_iterator = path_generator(size, **(path_kwargs if path_kwargs is not None else {}))
          path_iterator = path(size, **path_kwargs)
  
      # check if interval should increase progressively through image
      if progressive_amount > 0:
          current_max_interval = max_interval * progressive_amount
      else:
          current_max_interval = max_interval
  
      # traverse image and compute random sort intervals
      pixels_sorted = 0
      for row_iter in path_iterator:
          path_finished = False
          # traverse path until it is finished
          while not path_finished:
              if progressive_amount > 0:
                  current_max_interval += max_interval * progressive_amount
  
              if randomize and current_max_interval > 0:
                  interval = randint(1, int(current_max_interval) + 1)
              else:
                  interval = current_max_interval
  
              # get pixel coordinates of path
              i = 0
  
              px_indices = []
              # if interval is 0, just sort whole line at once
              while i < interval or interval == 0:
                  try:
                      coords = next(row_iter)
                  except StopIteration:
                      path_finished = True
                      break
  
                  i += 1
                  pixels_sorted += 1
                  if pixels_sorted % 200000 == 0:
                      logger.info("Created sort mask for %d / %d pixels (%2.2f%%)..." %
                                  (pixels_sorted, width * height, 100 * pixels_sorted / float(width * height)))
  
                  idx = coords_to_index(coords, width)
  
                  # use various image data to set up sort intervals, before computing random intervals
                  if image_mask is not None and luma(image_mask[idx]) > 128:
                      break
                  # edge detection
                  if edge_data is not None and edge_data[idx] > edge_threshold > 0:
                      break
                  # use image color to determine ends of sorting intervals
                  if image_threshold is not None:
                      brightness = luma(image[idx])
                      t = image_threshold * 255 / 2
                      if brightness < t or brightness > 255 - t:
                          break
  
                  # add current pixel to interval
                  px_indices.append(idx)
  
              # sort pixels, apply to output image
              if len(px_indices) > 0:
                  sorted_pixels = sorted([out_pixels[i] for i in px_indices], key=sort_key, reverse=reverse)
                  sorted_pixels = sort_filter(sorted_pixels, mirror=mirror, splice=splice, splice_random=splice_random)
                  for i in range(len(px_indices)):
                      index = px_indices[i]
                      pixel = sorted_pixels[i]
                      out_pixels[index] = pixel
  
      return out_pixels