docs: def check_module_usage(modpath_patterns):
      """
      FIXME: not fully implmented
  
      Desired behavior is ---
      Given a set of modules specified by a list of patterns, returns how the
      functions defined in the modules are called: a) with themselves and b) by
      other files in the project not in the given set.
  
      Args:
          modpath_patterns (list):
  
      CommandLine:
          python -m utool.util_inspect check_module_usage --show
          utprof.py -m utool.util_inspect check_module_usage --show
          python -m utool.util_inspect check_module_usage --pat="['auto*', 'user_dialogs.py', 'special_query.py', 'qt_inc_automatch.py', 'devcases.py']"
          python -m utool.util_inspect check_module_usage --pat="preproc_detectimg.py"
          python -m utool.util_inspect check_module_usage --pat="neighbor_index.py"
          python -m utool.util_inspect check_module_usage --pat="manual_chip_funcs.py"
          python -m utool.util_inspect check_module_usage --pat="preproc_probchip.py"
          python -m utool.util_inspect check_module_usage --pat="guiback.py"
  
          python -m utool.util_inspect check_module_usage --pat="util_str.py"
  
      Ignore:
  
      Example:
          >>> # DISABLE_DOCTEST
          >>> from utool.util_inspect import *  # NOQA
          >>> import utool as ut
          >>> modpath_patterns = ['_grave*']
          >>> modpath_patterns = ['auto*', 'user_dialogs.py', 'special_query.py', 'qt_inc_automatch.py', 'devcases.py']
          >>> modpath_patterns = ['neighbor_index.py']
          >>> modpath_patterns = ['manual_chip_funcs.py']
          >>> modpath_patterns = ut.get_argval('--pat', type_=list, default=['*'])
          >>> result = check_module_usage(modpath_patterns)
          >>> print(result)
      """
      import utool as ut
      #dpath = '~/code/ibeis/ibeis/algo/hots'
      modpaths = ut.flatten([ut.glob_projects(pat) for pat in modpath_patterns])
      modpaths = ut.unique(modpaths)
      modnames = ut.lmap(ut.get_modname_from_modpath, modpaths)
      print('Checking usage of modules: ' + ut.repr3(modpaths))
  
      # Mark as True is module is always explicitly imported
      restrict_to_importing_modpaths = False
      cache = {}
  
      def find_where_module_is_imported(modname):
          """ finds where a module was explicitly imported. (in most scenareos) """
          # Find places where the module was imported
          patterns = ut.possible_import_patterns(modname)
          # do modname grep with all possible import patterns
          grepres = ut.grep_projects(patterns, new=True, verbose=False, cache=cache)
          return grepres.found_fpath_list
  
      def find_function_callers(funcname, importing_modpaths):
          """ searches for places where a function is used """
          pattern = '\\b' + funcname + '\\b',
          # Search which module uses each public member
          grepres = ut.grep_projects(
              pattern, new=True, verbose=False, cache=cache,
              fpath_list=importing_modpaths)
          # Exclude places where function is defined or call is commented out
          nohit_patterns = [
              r'^\s*def',
              r'^\s*#',
              r'\-\-exec\-',
              r'\-\-test-',
              r'^\s*python -m ',
              r'^\s*python -m ibeis ',
              r'^\s*ibeis ',
              r'\-\-test\-[a-zA-z]*\.',
              r'\-\-exec\-[a-zA-z]*\.',
          ]
          nohit_patterns += [
              r'^\s*\>\>\>',
          ]
          filter_pat = ut.regex_or(nohit_patterns)
          # import copy
          # grepres_ = copy.deepcopy(grepres)
          grepres.inplace_filter_results(filter_pat)
          grepres.found_modnames = ut.lmap(ut.get_modname_from_modpath,
                                           grepres.found_fpath_list)
          parent_numlines = ut.lmap(len, grepres.found_lines_list)
  
          numcall_graph_ = dict(zip(grepres.found_modnames, parent_numlines))
          # Remove self references
          #ut.delete_keys(numcall_graph_, modnames)
          return numcall_graph_, grepres
  
      print('Find modules that use this the query modules')
      # Note: only works for explicit imports
      importing_modpaths_list = [find_where_module_is_imported(modname) for modname in modnames]
      print('Find members of the query modules')
      funcnames_list = [get_funcnames_from_modpath(modpath) for modpath in modpaths]
  
      print('Building call graph')
      cache = {}
      func_numcall_graph = ut.ddict(dict)
      grep_results = ut.ddict(dict)
      # Extract public members from each module
      exclude_self = ut.get_argflag('--exclude-self')
      _iter = list(zip(modnames, modpaths, importing_modpaths_list, funcnames_list))
      _iter = ut.ProgIter(_iter, lbl='Searching query module', bs=False)
      for modname, modpath, importing_modpaths, funcname_list in _iter:
          if not restrict_to_importing_modpaths:
              importing_modpaths = None
  
          # Search for each function in modpath
          for funcname in ut.ProgIter(funcname_list, lbl='Searching funcs in query module'):
              numcall_graph_, grepres = find_function_callers(funcname, importing_modpaths)
              grep_results[modname][funcname] = grepres
              if exclude_self:
                  if modname in numcall_graph_:
                      del numcall_graph_[modname]
              func_numcall_graph[modname][funcname] = numcall_graph_
  
      # Sort by incidence cardinality
      # func_numcall_graph = ut.odict([(key, ut.sort_dict(val, 'vals', len)) for key, val in func_numcall_graph.items()])
      # Sort by weighted degree
      func_numcall_graph = ut.odict([(key, ut.sort_dict(val, 'vals', lambda x: sum(x.values())))
                                     for key, val in func_numcall_graph.items()])
      # Print out grep results in order
      print('PRINTING GREP RESULTS IN ORDER')
      for modname, num_callgraph in func_numcall_graph.items():
          print('\n============\n')
          for funcname in num_callgraph.keys():
              print('\n============\n')
              with ut.Indenter('[%s]' % (funcname,)):
                  grepres = grep_results[modname][funcname]
                  print(grepres)
                  # print(func_numcall_graph[modname][funcname])
      print('PRINTING NUMCALLGRAPH IN ORDER')
      # Print out callgraph in order
      print('func_numcall_graph = %s' % (ut.repr3(func_numcall_graph),))
  
      # importance_dict = {}
      # import copy
      # func_call_graph2 = copy.deepcopy(func_numcall_graph)
      # #ignore_modnames = []
      # ignore_modnames = ['ibeis.algo.hots.multi_index', 'ibeis.algo.hots._neighbor_experiment']
      # num_callers = ut.ddict(dict)
      # for modname, modpath in list(zip(modnames, modpaths)):
      #     subdict = func_call_graph2[modname]
      #     for funcname in subdict.keys():
      #         numcall_graph_ = subdict[funcname]
      #         ut.delete_keys(numcall_graph_, modnames)
      #         ut.delete_keys(numcall_graph_, ignore_modnames)
      #         num_callers[modname][funcname] = sum(numcall_graph_.values())
      #     print(ut.repr4(num_callers[modname], sorted_=True, key_order_metric='val'))
  
      # # Check external usage
      # unused_external = []
      # grep_results2 = copy.deepcopy(grep_results)
      # for modname, grepres_subdict in grep_results2.items():
      #     for funcname, grepres_ in grepres_subdict.items():
      #         idxs = ut.find_list_indexes(grepres_.found_modnames, modnames)
      #         idxs += ut.find_list_indexes(grepres_.found_modnames, ignore_modnames)
      #         idxs = list(ut.filter_Nones(idxs))
      #         ut.delete_items_by_index(grepres_, idxs)
      #         ut.delete_items_by_index(grepres_.found_modnames, idxs)
      #         if len(grepres_) > 0:
      #             print(grepres_.make_resultstr())
      #         else:
      #             unused_external += [funcname]
  
      # print('internal grep')
      # # Check internal usage
      # unused_internal = []
      # grep_results2 = copy.deepcopy(grep_results)
      # for modname, grepres_subdict in grep_results2.items():
      #     for funcname, grepres_ in grepres_subdict.items():
      #         idxs = ut.filter_Nones(ut.find_list_indexes(grepres_.found_modnames, [modname]))
      #         idxs_ = ut.index_complement(idxs, len(grepres_.found_modnames))
      #         ut.delete_items_by_index(grepres_, idxs_)
      #         ut.delete_items_by_index(grepres_.found_modnames, idxs_)
      #         grepres_.hack_remove_pystuff()
      #         #self = grepres_
      #         if len(grepres_) > 0:
      #             #print(modname)
      #             #print(funcname)
      #             #print(grepres_.extended_regex_list)
      #             print(grepres_.make_resultstr())
      #         else:
      #             unused_internal += [funcname]
  
      # # HACK: how to write ut.parfor
      # # returns a 0 lenth iterator so the for loop is never run. Then uses code
      # # introspection to determine the content of the for loop body executes code
      # # using the values of the local variables in a parallel / distributed
      # # context.
  
      # for modname, modpath in zip(modnames, modpaths):
      #     pattern = '\\b' + modname + '\\b',
      #     grepres = ut.grep_projects(pattern, new=True, verbose=False, cache=cache)
      #     parent_modnames = ut.lmap(ut.get_modname_from_modpath, grepres.found_fpath_list)
      #     parent_numlines = ut.lmap(len, grepres.found_lines_list)
      #     importance = dict(zip(parent_modnames, parent_numlines))
      #     ut.delete_keys(importance, modnames)
      #     importance_dict[modname] = importance
  
      # print('importance_dict = %s' % (ut.repr3(importance_dict),))
      # combo = reduce(ut.dict_union, importance_dict.values())
      # print('combined %s' % (ut.repr3(combo),))
      # print(ut.repr3(found_fpath_list))
      pass