code: def get_dataset_info_from_source(source: DataSource) -> DatasetInfo:
      """Constructs FieldInfo objects for each feature in dataset. These objects are used for downstream type
      inference.
  
      # Inputs
      :param source: (DataSource) A wrapper around a data source, which may represent a pandas or Dask dataframe.
  
      # Return
      :return: (DatasetInfo) Structure containing list of FieldInfo objects.
      """
      row_count = len(source)
      fields = []
      for field in tqdm(
          source.columns,
          desc="Analyzing fields",
          total=len(source.columns),
      ):
          logger.info(f"Analyzing field: {field}")
          dtype = source.get_dtype(field)
          (
              num_distinct_values,
              distinct_values,
              distinct_values_balance,
          ) = source.get_distinct_values(
              field, MAX_DISTINCT_VALUES_TO_RETURN
          )
          nonnull_values = source.get_nonnull_values(field)
          image_values = source.get_image_values(field)
          audio_values = source.get_audio_values(field)
  
          if dtype == "object":
              # Check if it is a nullboolean field. We do this since if you read a csv with
              # pandas that has a column of booleans and some missing values, the column is
              # interpreted as object dtype instead of bool
              if is_field_boolean(source, field):
                  dtype = "bool"
  
          avg_words = None
          if source.is_string_type(dtype):
              try:
                  avg_words = source.get_avg_num_tokens(field)
              except AttributeError:
                  # Series is not actually a string type despite being an object, e.g., Decimal, Datetime, etc.
                  avg_words = None
  
          fields.append(
              FieldInfo(
                  name=field,
                  dtype=dtype,
                  distinct_values=distinct_values,
                  num_distinct_values=num_distinct_values,
                  distinct_values_balance=distinct_values_balance,
                  nonnull_values=nonnull_values,
                  image_values=image_values,
                  audio_values=audio_values,
                  avg_words=avg_words,
              )
          )
      return DatasetInfo(
          fields=fields,
          row_count=row_count,
          size_bytes=source.size_bytes(),
      )
  
imports: import logging
  import os
  from dataclasses import dataclass
  from typing import Any, Dict, List, Set, Union
  import dask.dataframe
  import numpy
  import pandas
  import yaml
  from dataclasses_json import dataclass_json, LetterCase
  from tqdm import tqdm
  from ludwig.api_annotations import DeveloperAPI
  from ludwig.backend import Backend
  from ludwig.constants import COLUMN, COMBINER, ENCODER, EXECUTOR, HYPEROPT, INPUT_FEATURES, PREPROCESSING, SCHEDULER, SEARCH_ALG, SPLIT, TEXT, TYPE
  from ludwig.types import ModelConfigDict
  from ludwig.utils.automl.data_source import DataSource, wrap_data_source
  from ludwig.utils.automl.field_info import FieldConfig, FieldInfo, FieldMetadata
  from ludwig.utils.automl.type_inference import infer_type, should_exclude
  from ludwig.utils.data_utils import load_yaml
  from ludwig.utils.misc_utils import merge_dict
  from ludwig.utils.system_utils import Resources
label_desc: Use image node_id 9 for calling a global function with example usage: get_dataset_info_from_source(source) and returns: DatasetInfo