code: def are_equal_metadata(
      actual_meta: List[Dict[str, Any]],
      expected_meta: List[Dict[str, Any]],
      exclude_keys: Optional[List[str]],
  ) -> bool:
      if actual_meta == expected_meta:
          return True
  
      for actual_dict, expected_dict in zip(actual_meta, expected_meta):
          for (act_k, act_v), (exp_k, exp_v) in zip(
              sorted(actual_dict.items(), key=lambda kv: kv[0]),
              sorted(expected_dict.items(), key=lambda kv: kv[0]),
          ):
              if exclude_keys is not None and act_k in exclude_keys:
                  continue
  
              if act_k != exp_k:
                  return False
  
              if act_v == exp_v:
                  continue
  
              # Bboxes are tuples but stored as lists in expected metadata
              if (
                  isinstance(act_v, list)
                  and all(
                      isinstance(x, tuple) for x in zip(act_v, exp_v)
                  )
                  and len(act_v) == len(exp_v)
                  and all(list(x) == y for x, y in zip(act_v, exp_v))
              ):
                  continue
  
              """
              Allow relative paths in expected metadata: just check that the end of the
              actual path matches the expected path
              """
              if not (
                  isinstance(act_v, str)
                  and isinstance(exp_v, str)
                  and act_v[-len(exp_v) :] == exp_v
              ):
                  return False
  
      return True
  
file_path: AugLy/augly/tests/image_tests/base_unit_test.py
imports: import os
  import tempfile
  import unittest
  from typing import Any, Callable, Dict, List, Optional
  import numpy
  from augly.tests import ImageAugConfig
  from augly.utils import pathmgr, TEST_URI
  from PIL import Image
label_desc: Use image node_id 2 for calling a global function with example usage: are_equal_metadata(actual_meta, expected_meta, exclude_keys) and returns: True, True, False, False