geowatch.cli.run_tracker module

This file contains logic to convert a kwcoco file into an IARPA Site Model.

At a glance the IARPA Site Model is a GeoJSON FeatureCollection with the following informal schema:

For official documentation about the KWCOCO json format see [kwcoco]. A formal json-schema can be found in kwcoco.coco_schema

For official documentation about the IARPA json format see [2, 3]_. A formal json-schema can be found in ../../geowatch/rc/site-model.schema.json.

References

SeeAlso:
  • ../tasks/tracking/from_heatmap.py

  • ../tasks/tracking/old_polygon_extraction.py

  • ../tasks/tracking/polygon_extraction.py

  • ../tasks/tracking/utils.py

  • ../../tests/test_tracker.py

class geowatch.cli.run_tracker.KWCocoToGeoJSONConfig(*args, **kwargs)[source]

Bases: DataConfig

Convert KWCOCO to IARPA GeoJSON

Valid options: []

Parameters:
  • *args – positional arguments for this data config

  • **kwargs – keyword arguments for this data config

default = {'append_mode': <Value(False)>, 'boundary_region': <Value(None)>, 'clear_annots': <Value(False)>, 'default_track_fn': <Value(None)>, 'in_file': <Value(None)>, 'in_file_gt': <Value(None)>, 'out_kwcoco': <Value(None)>, 'out_site_summaries_dir': <Value(None)>, 'out_site_summaries_fpath': <Value(None)>, 'out_sites_dir': <Value(None)>, 'out_sites_fpath': <Value(None)>, 'region_id': <Value(None)>, 'sensor_warnings': <Value(True)>, 'site_score_thresh': <Value(None)>, 'site_summary': <Value(None)>, 'smoothing': <Value(None)>, 'time_pad_after': <Value(None)>, 'time_pad_before': <Value(None)>, 'track_fn': <Value(None)>, 'track_kwargs': <Value('{}')>, 'viz_out_dir': <Value(None)>}
geowatch.cli.run_tracker.coco_create_observation(coco_dset, anns)[source]

Group kwcoco annotations in the same track (site) and image into one Feature in an IARPA site model

geowatch.cli.run_tracker.predict_phase_changes(site_id, observations)[source]

Set predicted_phase_transition and predicted_phase_transition_date.

This should only kick in when the site does not end before the current day (latest available image). See tracking.normalize.normalize_phases for what happens if the site has ended.

Parameters:
  • site_id (str) – site identifier

  • features (List[Dict]) – observation feature dictionaries for the site

Returns:

dict

References

https://smartgitlab.com/TE/standards/-/wikis/Site-Model-Specification https://gitlab.kitware.com/smart/standards-wiki/-/blob/main/Site-Model-Specification.md

Example

>>> from geowatch.geoannots import geomodels
>>> site = geomodels.SiteModel.random(rng=0, num_observations=20)
>>> site_id = site.site_id
>>> observations = list(site.body_features())
>>> observations[-1]['properties']['cache'] = {'phase_transition_days': [100]}
>>> predict_phase_changes(site_id, observations)
geowatch.cli.run_tracker.smooth_observation_scores(observations, smoothing=0.5, smooth_mode='ewma')[source]

Add smoothed scores inplace

Example

>>> from geowatch.cli.run_tracker import *  # NOQA
>>> from geowatch.geoannots import geomodels
>>> site = geomodels.SiteModel.random(num_observations=15)
>>> observations = list(site.observations())
>>> # Add random scores for tests
>>> import kwarray
>>> rng = kwarray.ensure_rng()
>>> for obs in observations:
>>>     obs['properties']['cache'] = {'raw_multi_scores': [{
>>>         'No Activity': rng.rand(),
>>>         'Site Preparation': rng.rand(),
>>>         'Post Construction': rng.rand(),
>>>         'Active Construction': rng.rand(),
>>> }]}
>>> data1 = [obs['properties']['cache']['raw_multi_scores'][0] for obs in observations]
>>> smooth_observation_scores(observations, smooth_mode='ewma')
>>> data2 = [obs['properties']['cache']['smooth_scores'].copy() for obs in observations]
>>> smooth_observation_scores(observations, smooth_mode='conv3')
>>> data3 = [obs['properties']['cache']['smooth_scores'].copy() for obs in observations]
>>> import pandas as pd
>>> df1 = pd.DataFrame(data1)
>>> df2 = pd.DataFrame(data2)
>>> df3 = pd.DataFrame(data3)
>>> print(df1)
>>> print(df2)
>>> print(df3)
geowatch.cli.run_tracker.classify_site(site, config)[source]

Modify a site inplace with classifications.

Given a site with extracted and scored observations, postprocess the raw observation scores and make site-level predictions.

geowatch.cli.run_tracker.coco_create_site_header(region_id, site_id, trackid, observations)[source]

Feature containing metadata about the site

Returns:

geomodels.SiteSummary | geomodels.SiteHeader

geowatch.cli.run_tracker.convert_kwcoco_to_iarpa(coco_dset, default_region_id=None)[source]

Convert a kwcoco coco_dset to the IARPA JSON format

Parameters:

coco_dset (kwcoco.CocoDataset) – a coco dataset, but requires images are geotiffs as well as certain special fields.

Returns:

sites

dictionary of json-style data in IARPA site format

Return type:

dict

Example

>>> import geowatch
>>> from geowatch.cli.run_tracker import *  # NOQA
>>> from geowatch.tasks.tracking.normalize import run_tracking_pipeline
>>> from geowatch.tasks.tracking.from_polygon import MonoTrack
>>> import ubelt as ub
>>> coco_dset = geowatch.coerce_kwcoco('geowatch-msi', heatmap=True, geodata=True, dates=True)
>>> coco_dset = run_tracking_pipeline(
>>>     coco_dset, track_fn=MonoTrack, overwrite=False,
>>>     sensor_warnings=False)
>>> videos = coco_dset.videos()
>>> videos.set('name', ['DM_R{:03d}'.format(vidid) for vidid in videos])
>>> sites = convert_kwcoco_to_iarpa(coco_dset)
>>> print(f'{len(sites)} sites')
>>> if 0:  # validation fails
>>>     import jsonschema
>>>     SITE_SCHEMA = geowatch.rc.load_site_model_schema()
>>>     for site in sites:
>>>         jsonschema.validate(site, schema=SITE_SCHEMA)
>>> elif 0:  # but this works if metrics are available
>>>     import tempfile
>>>     import json
>>>     from iarpa_smart_metrics.evaluation import SiteStack
>>>     for site in sites:
>>>         with tempfile.NamedTemporaryFile() as f:
>>>             json.dump(site, open(f.name, 'w'))
>>>             SiteStack(f.name)
geowatch.cli.run_tracker.coco_track_to_site(coco_dset, trackid, region_id, site_idx=None)[source]

Turn a kwcoco track into an IARPA site model or site summary

geowatch.cli.run_tracker.assign_sites_to_videos(coco_dset, site_summaries, viz_out_dir=None)[source]

Compute assignments between which sites summaries should be projected onto which videos for scoring.

geowatch.cli.run_tracker.add_site_summary_to_kwcoco(possible_summaries, coco_dset, default_region_id=None, viz_out_dir=None)[source]

Add a site summary(s) to a kwcoco dataset as a set of polygon annotations. These annotations will have category “Site Boundary”, 1 track per summary.

This function is mainly for SC. The “possible_summaries” indicate regions flagged by BAS (which could also be truth data if we are evaluating SC independently) that need SC processing. We need to associate these and place them in the correct videos so we can process those areas.

geowatch.cli.run_tracker.main(argv=None, **kwargs)[source]

Example

>>> # test BAS and default (SC) modes
>>> from geowatch.cli.run_tracker import *  # NOQA
>>> from geowatch.cli.run_tracker import main
>>> from geowatch.demo import smart_kwcoco_demodata
>>> from kwgis.utils import util_gis
>>> import json
>>> import kwcoco
>>> import ubelt as ub
>>> # run BAS on demodata in a new place
>>> import geowatch
>>> coco_dset = geowatch.coerce_kwcoco('geowatch-msi', heatmap=True, geodata=True, dates=True)
>>> dpath = ub.Path.appdir('geowatch', 'test', 'tracking', 'main0').ensuredir()
>>> coco_dset.reroot(absolute=True)
>>> coco_dset.fpath = dpath / 'bas_input.kwcoco.json'
>>> coco_dset.clear_annotations()
>>> coco_dset.dump(coco_dset.fpath, indent=2)
>>> region_id = 'dummy_region'
>>> regions_dir = dpath / 'regions/'
>>> bas_coco_fpath = dpath / 'bas_output.kwcoco.json'
>>> sc_coco_fpath = dpath / 'sc_output.kwcoco.json'
>>> bas_fpath = dpath / 'bas_sites.json'
>>> sc_fpath = dpath / 'sc_sites.json'
>>> # Run BAS
>>> argv = bas_args = [
>>>     '--in_file', coco_dset.fpath,
>>>     '--out_site_summaries_dir', str(regions_dir),
>>>     '--out_site_summaries_fpath',  str(bas_fpath),
>>>     '--out_kwcoco', str(bas_coco_fpath),
>>>     '--track_fn', 'saliency_heatmaps',
>>>     '--sensor_warnings', 'False',
>>>     '--track_kwargs', json.dumps({
>>>        'thresh': 1e-9, 'min_area_square_meters': None,
>>>        'max_area_square_meters': None,
>>>        'polygon_simplify_tolerance': 1}),
>>> ]
>>> main(argv)
>>> # Run SC on the same dset, but with BAS pred sites removed
>>> sites_dir = dpath / 'sites'
>>> argv = sc_args = [
>>>     '--in_file', coco_dset.fpath,
>>>     '--out_sites_dir', str(sites_dir),
>>>     '--out_sites_fpath', str(sc_fpath),
>>>     '--out_kwcoco', str(sc_coco_fpath),
>>>     '--track_fn', 'class_heatmaps',
>>>     '--site_summary', str(bas_fpath),
>>>     '--sensor_warnings', 'False',
>>>     '--track_kwargs', json.dumps(
>>>         {'thresh': 1e-9, 'min_area_square_meters': None, 'max_area_square_meters': None,
>>>          'polygon_simplify_tolerance': 1, 'key': 'salient'}),
>>> ]
>>> main(argv)
>>> # Check expected results
>>> bas_coco_dset = kwcoco.CocoDataset(bas_coco_fpath)
>>> sc_coco_dset = kwcoco.CocoDataset(sc_coco_fpath)
>>> bas_trackids = bas_coco_dset.annots().lookup('track_id', None)
>>> sc_trackids = sc_coco_dset.annots().lookup('track_id', None)
>>> print('bas_trackids = {}'.format(ub.urepr(bas_trackids, nl=1)))
>>> print('sc_trackids = {}'.format(ub.urepr(sc_trackids, nl=1)))
>>> assert len(bas_trackids) and None not in bas_trackids
>>> assert len(sc_trackids) and None not in sc_trackids
>>> summaries = list(util_gis.coerce_geojson_datas(bas_fpath, format='dataframe'))
>>> sites = list(util_gis.coerce_geojson_datas(sc_fpath, format='dataframe'))
>>> import pandas as pd
>>> sc_df = pd.concat([d['data'] for d in sites])
>>> bas_df = pd.concat([d['data'] for d in summaries])
>>> ssum_rows = bas_df[bas_df['type'] == 'site_summary']
>>> site_rows = sc_df[sc_df['type'] == 'site']
>>> obs_rows = sc_df[sc_df['type'] == 'observation']
>>> assert len(site_rows) > 0
>>> assert len(ssum_rows) > 0
>>> assert len(ssum_rows) == len(site_rows)
>>> assert len(obs_rows) > len(site_rows)
>>> # Cleanup
>>> #dpath.delete()

Example

>>> # test resolution
>>> from geowatch.cli.run_tracker import *  # NOQA
>>> from geowatch.cli.run_tracker import main
>>> import geowatch
>>> dset = geowatch.coerce_kwcoco('geowatch-msi', heatmap=True, geodata=True, dates=True)
>>> dpath = ub.Path.appdir('geowatch', 'test', 'tracking', 'main1').ensuredir()
>>> out_fpath = dpath / 'resolution_test.kwcoco.json'
>>> regions_dir = dpath / 'regions'
>>> bas_fpath = dpath / 'bas_sites.json'
>>> import json
>>> track_kwargs = json.dumps({
>>>         'resolution': '10GSD',
>>>         'min_area_square_meters': 1000000,  # high area threshold filters results
>>>         'max_area_square_meters': None,
>>>         'thresh': 1e-9,
>>> })
>>> kwargs = {
>>>     'in_file': str(dset.fpath),
>>>     'out_site_summaries_dir': str(regions_dir),
>>>     'out_site_summaries_fpath':  str(bas_fpath),
>>>     'out_kwcoco': str(out_fpath),
>>>     'track_fn': 'saliency_heatmaps',
>>>     'track_kwargs': track_kwargs,
>>>     'sensor_warnings': False,
>>> }
>>> argv = []
>>> # Test case for no results
>>> main(argv=argv, **kwargs)
>>> from kwgis.utils import util_gis
>>> assert len(list(util_gis.coerce_geojson_datas(bas_fpath))) == 0
>>> # Try to get results here
>>> track_kwargs = json.dumps({
>>>         'resolution': '10GSD',
>>>         'min_area_square_meters': None,
>>>         'max_area_square_meters': None,
>>>         'thresh': 1e-9,
>>> })
>>> kwargs = {
>>>     'in_file': str(dset.fpath),
>>>     'out_site_summaries_dir': str(regions_dir),
>>>     'out_site_summaries_fpath':  str(bas_fpath),
>>>     'out_kwcoco': str(out_fpath),
>>>     'track_fn': 'saliency_heatmaps',
>>>     'track_kwargs': track_kwargs,
>>>     'sensor_warnings': False,
>>> }
>>> argv = []
>>> main(argv=argv, **kwargs)
>>> assert len(list(util_gis.coerce_geojson_datas(bas_fpath))) > 0

Example

>>> # xdoctest: +REQUIRES(--slow)
>>> # test a more complicated track function
>>> import geowatch
>>> from geowatch.cli.run_tracker import demo
>>> import kwcoco
>>> import geowatch
>>> import ubelt as ub
>>> # make a new BAS dataset
>>> coco_dset = geowatch.coerce_kwcoco('geowatch-msi', heatmap=True, geodata=True)
>>> #coco_dset.images().set('sensor_coarse', 'S2')
>>> for img in coco_dset.imgs.values():
>>>     img['sensor_coarse'] = 'S2'
>>> coco_dset.remove_categories(coco_dset.cats.keys())
>>> coco_dset.fpath = 'bas.kwcoco.json'
>>> # TODO make serializable, check set() and main()
>>> coco_dset.dump(coco_dset.fpath, indent=2)
>>> # make a new SC dataset
>>> coco_dset_sc = smart_kwcoco_demodata.demo_kwcoco_with_heatmaps(
>>>     num_videos=2)
>>> for img in coco_dset_sc.imgs.values():
>>>     img['sensor_coarse'] = 'S2'
>>> coco_dset_sc.remove_categories(coco_dset_sc.cats.keys())
>>> for img in coco_dset_sc.imgs.values():
>>>     for aux, key in zip(img['auxiliary'],
>>>                         ['Site Preparation', 'Active Construction',
>>>                          'Post Construction', 'No Activity']):
>>>         aux['channels'] = key
>>> coco_dset_sc.fpath = 'sc.kwcoco.json'
>>> coco_dset_sc.dump(coco_dset_sc.fpath, indent=2)
>>> regions_dir = 'regions/'
>>> sites_dir = 'sites/'
>>> # moved this to a separate function for length
>>> demo(coco_dset, regions_dir, coco_dset_sc, sites_dir, cleanup=True)
geowatch.cli.run_tracker.coco_video_gdf(coco_dset)[source]
geowatch.cli.run_tracker.assign_videos_to_regions(video_gdf, boundary_regions_gdf)[source]

Assign each video to a region (usually for BAS)

geowatch.cli.run_tracker.coco_remove_out_of_bound_tracks(coco_dset, video_region_assignments)[source]
geowatch.cli.run_tracker.demo(coco_dset, regions_dir, coco_dset_sc, sites_dir, cleanup=True)[source]