code: def get_candidates(
      ngram2phrases: Dict[str, List[Tuple[int, int, int, float]]],
      phrases: List[str],
      letters: Union[str, List[str]],
      pool_for_random_candidates: List[str],
      min_phrase_coverage: float = 0.8,
  ) -> List[Tuple[str, int, int, float, float]]:
      """Given an index of custom vocabulary and an ASR-hypothesis retrieve 10 candidates.
      Args:
          ngram2phrases: dict where key=ngram, value=list of tuples (phrase_id, begin_pos, size, logprob)
          phrases: List of all phrases in custom vocabulary. Position corresponds to phrase_id.
          letters: list of letters of ASR-hypothesis. Should not contain spaces - real spaces should be replaced with underscores.
          pool_for_random_candidates: large list of strings, from which to sample random candidates in case when there are less than 10 real candidates
          min_phrase_coverage: We discard candidates which are not covered by n-grams to at least to this extent
            (to avoid cases where some repeating n-gram gives many hits to a phrase, but the phrase itself is not well covered).
       Returns:
          candidates: list of tuples (candidate_text, approximate_begin_position, length, coverage of window in ASR-hypothesis, coverage of phrase itself).
      """
      phrases2positions, position2ngrams = search_in_index(
          ngram2phrases, phrases, letters
      )
      (
          candidate2coverage,
          candidate2position,
      ) = get_all_candidates_coverage(phrases, phrases2positions)
  
      # mask for each custom phrase, how many which symbols are covered by input ngrams
      phrases2coveredsymbols = [
          [0 for x in phrases[i].split(" ")]
          for i in range(len(phrases))
      ]
      candidates = []
      k = 0
      for idx, coverage in sorted(
          enumerate(candidate2coverage),
          key=lambda item: item[1],
          reverse=True,
      ):
          begin = candidate2position[
              idx
          ]  # this is most likely beginning of this candidate
          phrase_length = phrases[idx].count(" ") + 1
          for pos in range(begin, begin + phrase_length):
              # we do not know exact end of custom phrase in text, it can be different from phrase length
              if pos >= len(position2ngrams):
                  break
              for ngram in position2ngrams[pos]:
                  for phrase_id, b, size, lp in ngram2phrases[ngram]:
                      if phrase_id != idx:
                          continue
                      for ppos in range(b, b + size):
                          if ppos >= phrase_length:
                              break
                          phrases2coveredsymbols[phrase_id][ppos] = 1
          k += 1
          if k > 100:
              break
          real_coverage = sum(phrases2coveredsymbols[idx]) / len(
              phrases2coveredsymbols[idx]
          )
          if real_coverage < min_phrase_coverage:
              continue
          candidates.append(
              (
                  phrases[idx],
                  begin,
                  phrase_length,
                  coverage,
                  real_coverage,
              )
          )
  
      # no need to process this sentence further if it does not contain any real candidates
      if len(candidates) == 0:
          print("WARNING: no real candidates", candidates)
          return []
  
      while len(candidates) < 10:
          dummy = random.choice(pool_for_random_candidates)
          dummy = " ".join(list(dummy.replace(" ", "_")))
          candidates.append((dummy, -1, dummy.count(" ") + 1, 0.0, 0.0))
  
      candidates = candidates[:10]
      random.shuffle(candidates)
      if len(candidates) != 10:
          print("WARNING: cannot get 10 candidates", candidates)
          return []
  
      return candidates
  
imports: import json
  import math
  import random
  import re
  from collections import defaultdict, namedtuple
  from typing import Dict, List, Set, Tuple, Union
  import numpy
  from numba import jit
label_desc: Use image node_id 9 for calling a global function with example usage: get_candidates(ngram2phrases, phrases, letters, pool_for_random_candidates, min_phrase_coverage) and returns: candidates, list, list