code: def build_annoy_index(
      X,
      path,
      metric="angular",
      ntrees=50,
      build_index_on_disk=True,
      verbose=1,
      n_jobs=-1,
  ):
      """Build a standalone annoy index.
  
      :param array X: numpy array with shape (n_samples, n_features)
      :param str path: The filepath of a trained annoy index file
          saved on disk.
      :param int ntrees: The number of random projections trees built by Annoy to
          approximate KNN. The more trees the higher the memory usage, but the
          better the accuracy of results.
      :param bool build_index_on_disk: Whether to build the annoy index directly
          on disk. Building on disk should allow for bigger datasets to be indexed,
          but may cause issues.
      :param str metric: Which distance metric Annoy should use when building KNN index.
          Supports "angular", "euclidean", "manhattan", "hamming", or "dot".
      :param int verbose: Controls the volume of logging output the model
          produces when training. When set to 0, silences outputs, when above 0
          will print outputs.
  
      """
  
      if verbose:
          print("Building KNN index")
  
      if len(X.shape) > 2:
          if not "reshape" in dir(X):
              raise ValueError(
                  "Attempting to build AnnoyIndex on multi-dimensional data"
                  " without providing a reshape method. AnnoyIndexes require"
                  " 2D data - rows and columns."
              )
          if verbose:
              print(
                  "Flattening multidimensional input before building KNN index using Annoy"
              )
          X = X.reshape((X.shape[0], -1))
  
      batched_retrieval = hasattr(X, "get_batch")
  
      index = AnnoyIndex(X.shape[1], metric=metric)
      if build_index_on_disk:
          index.on_disk_build(path)
  
      if not batched_retrieval:
          if issparse(X):
              for i in tqdm(range(X.shape[0]), disable=verbose < 1):
                  vector = X[i].toarray()[0]
                  index.add_item(i, vector)
          else:
              for i in tqdm(range(X.shape[0]), disable=verbose < 1):
                  vector = X[i]
                  index.add_item(i, vector)
      else:
          # Reusing n_jobs for batch_size here - separate config may be better
          batch_size = (
              n_jobs if n_jobs > 0 else os.cpu_count() + n_jobs + 1
          )
          for batch_start_idx in tqdm(
              range(0, X.shape[0], batch_size), disable=verbose < 1
          ):
              batch_indices = range(
                  batch_start_idx,
                  min(batch_start_idx + batch_size, X.shape[0]),
              )
              batch = X.get_batch(batch_indices)
              for idx, vector in zip(batch_indices, batch):
                  index.add_item(idx, vector)
  
      try:
          index.build(ntrees, n_jobs=n_jobs)
      except Exception as e:
          raise Exception(
              "Error building Annoy Index. Passing on_disk_build=False "
              "may solve the issue, especially on Windows."
          ) from e
  
      if not build_index_on_disk:
          index.save(path)
      return index
  
imports: from concurrent.futures import ThreadPoolExecutor
  import os
  import shutil
  import time
  from abc import abstractmethod
  from threading import Thread
  from collections.abc import Sequence
  from pathlib import Path
  from scipy.sparse import issparse
  from annoy import AnnoyIndex
  from tqdm import tqdm
  import numpy
  from ..data import get_uint_ctype
label_desc: Use image node_id 2 for calling a global function with example usage: build_annoy_index(X, path, metric, ntrees, build_index_on_disk, verbose, n_jobs) and returns: index