#!/usr/bin/env python3 # PYTHON_ARGCOMPLETE_OK import ubelt as ub __devnotes__ = """ # We may want to delay actual imports, gdal import time can be excessive python -X importtime -c "import geowatch" WATCH_HACK_IMPORT_ORDER="" python -X importtime -m geowatch.cli find_dvc """ def main(cmdline=True, **kw): """ The geowatch command line interface """ modnames = [ 'watch_coco_stats', 'geojson_site_stats', 'validate_annotation_schemas', 'torch_model_stats', 'coco_spectra', 'draw_region', # 'geotiffs_to_kwcoco' # TODO: cleanup and add 'coco_visualize_videos', 'find_dvc', 'coco_add_watch_fields', 'coco_align', 'coco_clean_geotiffs', 'coco_average_features', 'coco_time_combine', 'coco_add_ignore_buffer', 'reproject_annotations', 'run_tracker', 'run_metrics_framework', 'crop_sites_to_regions', 'coco_remove_bad_images', 'coco_average_features', # Due to LightningCLI fit does not play nicely here. 'fit', # Predict also has issues because of its heavy imports. 'predict', # 'mlops_cli', 'special.finish_install', ] cmd_alias = { 'geowatch.cli.torch_model_stats': ['model_stats', 'model_info'], # 'geowatch.cli.geojson_site_stats': ['site_stats', 'geojson_stats', 'geomodel_stats'], 'geowatch.cli.watch_coco_stats': ['stats'], 'geowatch.cli.coco_visualize_videos': ['visualize'], 'geowatch.cli.coco_align': ['align', 'coco_align_geotiffs'], 'geowatch.cli.reproject_annotations': ['reproject', 'project'], 'geowatch.cli.coco_add_watch_fields': ['add_fields'], 'geowatch.cli.coco_spectra': ['spectra', 'intensity_histograms'], 'geowatch.cli.run_metrics_framework': ['iarpa_eval'], 'geowatch.cli.coco_clean_geotiffs': ['clean_geotiffs'], 'geowatch.cli.run_tracker': ['run_tracker'], 'geowatch.cli.find_dvc': ['dvc', 'dvcdir'], 'geowatch.cli.coco_average_features': ['average_features', 'ensemble'], 'geowatch.cli.coco_time_combine': ['time_combine'], 'geowatch.cli.coco_add_ignore_buffer': ['ignore_buffer'], 'geowatch.cli.crop_sites_to_regions': ['crop_sitemodels'], 'geowatch.cli.coco_remove_bad_images': ['remove_bad_images'], 'geowatch.cli.mlops_cli': ['mlops'], 'geowatch.cli.special.finish_install': [], 'geowatch.mlops.aggregate': ['mlops.aggregate'], 'geowatch.mlops.schedule_evaluation': ['mlops.schedule'], } module_lut = {} for name in modnames: mod = ub.import_module_from_name('geowatch.cli.{}'.format(name)) module_lut[name] = mod module_lut['schedule'] = ub.import_module_from_name('geowatch.mlops.schedule_evaluation') module_lut['manager'] = ub.import_module_from_name('geowatch.mlops.manager') module_lut['aggregate'] = ub.import_module_from_name('geowatch.mlops.aggregate') module_lut['repackage'] = ub.import_module_from_name('geowatch.mlops.repackager') # module_lut['fit'] = ub.import_module_from_name('geowatch.tasks.fusion.fit_lightning') # module_lut['predict'] = ub.import_module_from_name('geowatch.tasks.fusion.predict') # Create a list of all submodules with CLI interfaces cli_modules = list(module_lut.values()) import os from scriptconfig.modal import ModalCLI import geowatch WATCH_LOOSE_CLI = os.environ.get('WATCH_LOOSE_CLI', '') # https://emojiterra.com/time/ # Not sure how to make this consistent on different terminals fun_header = ub.codeblock( ''' 🛰️⌚🛰️ 🧠 🛰️⌚🛰️ 🌍🌎🌏 👁️ 👁️ 🌏🌎🌍 ''') boring_description = ub.codeblock( f''' 🌐🌐🌐 The GeoWATCH CLI 🌐🌐🌐 An open source research and production environment for image and video segmentation and detection with geospatial awareness. Developed by [link=https://www.kitware.com/]Kitware[/link]. Funded by the [link=https://www.iarpa.gov/research-programs/smart]IARPA SMART[/link] challenge. Version: {geowatch.__version__} ''') FUN = os.getenv('FUN', '') and not os.getenv('NOFUN', '') if FUN: description = fun_header + '\n' + boring_description else: description = boring_description modal = ModalCLI(description) # scriptconfig bug made this not work... # modal.__class__.version = geowatch.__version__ def get_version(self): import geowatch return geowatch.__version__ modal.__class__.version = property(get_version) for cli_module in cli_modules: cli_config = None if hasattr(cli_module, '__config__'): # New way cli_config = cli_module.__config__ elif hasattr(cli_module, '__cli__'): # New way cli_config = cli_module.__cli__ else: if hasattr(cli_module, 'modal'): continue raise AssertionError(f'We are only supporting scriptconfig CLIs. {cli_module} does not have __config__ attr') if not hasattr(cli_config, 'main'): if hasattr(cli_module, 'main'): main_func = cli_module.main # Hack the main function into the config cli_config.main = main_func else: raise AssertionError(f'No main function for {cli_module}') # Update configs to have aliases / commands attributes cli_modname = cli_module.__name__ cli_rel_modname = cli_modname.split('.')[-1] cmdname_aliases = ub.oset() alias = getattr(cli_config, '__alias__', []) if isinstance(alias, str): alias = [alias] command = getattr(cli_module, '__command__', None) command = getattr(cli_config, '__command__', command) if command is not None: cmdname_aliases.add(command) cmdname_aliases.update(alias) cmdname_aliases.update(cmd_alias.get(cli_modname, []) ) cmdname_aliases.add(cli_rel_modname) # parserkw = {} primary_cmdname = cmdname_aliases[0] secondary_cmdnames = cmdname_aliases[1:] if not isinstance(primary_cmdname, str): raise AssertionError(primary_cmdname) cli_config.__command__ = primary_cmdname cli_config.__alias__ = list(secondary_cmdnames) modal.register(cli_config) ret = modal.main(strict=not WATCH_LOOSE_CLI) return ret # def modal_main(self, argv=None, strict=True, autocomplete='auto'): # """ # Overwrite modal main to support Lightning CLI # """ # if isinstance(self, type): # self = self() # parser = self.argparse() # if autocomplete: # try: # import argcomplete # # Need to run: "$(register-python-argcomplete xdev)" # # or activate-global-python-argcomplete --dest=- # # activate-global-python-argcomplete --dest ~/.bash_completion.d # # To enable this. # except ImportError: # argcomplete = None # if autocomplete != 'auto': # raise # else: # argcomplete = None # if argcomplete is not None: # argcomplete.autocomplete(parser) # if strict: # ns = parser.parse_args(args=argv) # else: # ns, _ = parser.parse_known_args(args=argv) # kw = ns.__dict__ # if kw.pop('version', None): # print(self.version) # return 0 # sub_main = kw.pop('main', None) # if sub_main is None: # parser.print_help() # raise ValueError('no command given') # return 1 # try: # ret = sub_main(cmdline=False, **kw) # except Exception as ex: # print('ERROR ex = {!r}'.format(ex)) # raise # return 1 # else: # if ret is None: # ret = 0 # return ret if __name__ == '__main__': """ CommandLine: python -m geowatch.cli --help python -m geowatch.cli.watch_coco_stats """ main() # sys.exit(main())