docs: def search(self, files=None, defined_fields=None, **kwargs):
          """Search files in the layout by metadata fields.
  
          Args:
              files (list): Optional list of names of files to search. If None,
                  all files in the layout are scanned.
              defined_fields (list): Optional list of names of fields that must
                  be defined in the JSON sidecar in order to consider the file a
                  match, but which don't need to match any particular value.
              kwargs: Optional keyword arguments defining search constraints;
                  keys are names of metadata fields, and values are the values
                  to match those fields against (e.g., SliceTiming=0.017) would
                  return all files that have a SliceTiming value of 0.071 in
                  metadata.
  
          Returns: A list of filenames that match all constraints.
          """
  
          if defined_fields is None:
              defined_fields = []
  
          all_keys = set(defined_fields) | set(kwargs.keys())
          if not all_keys:
              raise ValueError("At least one field to search on must be passed.")
  
          # If no list of files is passed, use all files in layout
          if files is None:
              files = set(self.layout.files.keys())
  
          # Index metadata for any previously unseen files
          for f in files:
              self.index_file(f)
  
          # Get file intersection of all kwargs keys--this is fast
          filesets = [set(self.key_index.get(k, [])) for k in all_keys]
          matches = reduce(lambda x, y: x & y, filesets)
  
          if files is not None:
              matches &= set(files)
  
          if not matches:
              return []
  
          def check_matches(f, key, val):
              if isinstance(val, six.string_types) and '*' in val:
                  val = ('^%s$' % val).replace('*', ".*")
                  return re.search(str(self.file_index[f][key]), val) is not None
              else:
                  return val == self.file_index[f][key]
  
          # Serially check matches against each pattern, with early termination
          for k, val in kwargs.items():
              matches = list(filter(lambda x: check_matches(x, k, val), matches))
              if not matches:
                  return []
  
          return matches