class_docstr: Consider FunctionExpression: ObjDetector -> (labels, boxes)
  
  `output`: If the user wants only subset of outputs. Eg,
  ObjDetector.labels the parser with set output to 'labels'
  
  `output_objs`: It is populated by the binder. In case the
  output is None, the binder sets output_objs to list of all
  output columns of the FunctionExpression. Eg, ['labels',
  'boxes']. Otherwise, only the output columns.
  
  FunctionExpression also needs to prepend its alias to all the
  projected columns. This is important as other parts of the query
  might be assessing the results using alias. Eg,
  
  `Select Detector.labels
   FROM Video JOIN LATERAL ObjDetector AS Detector;`
class_docstr_tok: ["Consider","FunctionExpression",":","ObjDetector","-",">","(","labels",",","boxes",")","`","output","`",":","If","the","user","wants","only","subset","of","outputs",".","Eg",",","ObjDetector.labels","the","parser","with","set","output","to","'labels'","`","output_objs","`",":","It","is","populated","by","the","binder",".","In","case","the","output","is","None",",","the","binder","sets","output_objs","to","list","of","all","output","columns","of","the","FunctionExpression",".","Eg",",","[","'labels","'",",","'boxes","'","]",".","Otherwise",",","only","the","output","columns",".","FunctionExpression","also","needs","to","prepend","its","alias","to","all","the","projected","columns",".","This","is","important","as","other","parts","of","the","query","might","be","assessing","the","results","using","alias",".","Eg",",","`","Select","Detector.labels","FROM","Video","JOIN","LATERAL","ObjDetector","AS","Detector",";","`"]
code: def evaluate(self, batch: Batch, **kwargs) -> Batch:
      func = self._gpu_enabled_function()
      # record the time taken for the function execution
      # note the function might be using cache
      with self._stats.timer:
          # apply the function and project the required columns
          outcomes = self._apply_function_expression(
              func, batch, **kwargs
          )
  
          # process outcomes only if output is not empty
          if outcomes.frames.empty is False:
              outcomes = outcomes.project(self.projection_columns)
              outcomes.modify_column_alias(self.alias)
  
      # record the number of function calls
      self._stats.num_calls += len(batch)
  
      # try persisting the stats to catalog and do not crash if we fail in doing so
      try:
          self.consolidate_stats()
      except Exception as e:
          logger.warn(
              f"Persisting FunctionExpression {str(self)} stats failed with {str(e)}"
          )
  
      return outcomes
  
imports: from dataclasses import dataclass
  from typing import Callable, List, Tuple
  import numpy
  import pandas
  from evadb.catalog.models.function_catalog import FunctionCatalogEntry
  from evadb.catalog.models.function_io_catalog import FunctionIOCatalogEntry
  from evadb.constants import NO_GPU
  from evadb.executor.execution_context import Context
  from evadb.expression.abstract_expression import AbstractExpression, ExpressionType
  from evadb.functions.gpu_compatible import GPUCompatible
  from evadb.models.storage.batch import Batch
  from evadb.parser.alias import Alias
  from evadb.utils.kv_cache import DiskKVCache
  from evadb.utils.logging_manager import logger
  from evadb.utils.stats import FunctionStats
label_desc: Use image node_id 10 for calling the FunctionExpression obj's underlying member method code with example usage: obj.evaluate(batch) and returns: outcomes