import os def test_tracker_with_sv(): """ Tests tracker with site verification on """ import pytest pytest.skip('slow') import json import kwcoco import pandas as pd import ubelt as ub from geowatch.cli import run_tracker from geowatch.utils import util_gis import geowatch coco_dset = geowatch.coerce_kwcoco('geowatch-msi', heatmap=True, geodata=True, dates=True) video0 = coco_dset.videos().objs[0] video0_images = coco_dset.images(video_id=video0['id']) assert len(video0_images) >= 10, 'should have a several frames' assert len(set(video0_images.lookup('date_captured'))) > 5, ( 'should be on different dates') #coco_dset = smart_kwcoco_demodata.demo_smart_aligned_kwcoco() dpath = ub.Path.appdir('geowatch', 'test', 'tracking', 'unit_test1').ensuredir() dpath.delete().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) regions_dir = dpath / 'regions/' bas_coco_fpath = dpath / 'bas_output.kwcoco.json' sc_coco_fpath = dpath / 'sc_output.kwcoco.json' sv_coco_fpath = dpath / 'sv_output.kwcoco.json' bas_fpath = dpath / 'bas_sites.json' sc_fpath = dpath / 'sc_sites.json' sv_fpath = dpath / 'sv_sites.json' # Run BAS bas_argv = [ '--in_file', os.fspath(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', '0', '--track_kwargs', json.dumps({ 'thresh': 0.5, 'time_thresh': .8, 'min_area_square_meters': None, 'max_area_square_meters': None, 'polygon_simplify_tolerance': 1, # 'moving_window_size': 1, }), ] run_tracker.main(bas_argv) bas_coco_dset = kwcoco.CocoDataset(bas_coco_fpath) bas_trackids = bas_coco_dset.annots().lookup('track_id', None) assert len(bas_trackids) > len(set(bas_trackids)), ( 'should have multiple observations per track') # Run SC on the same dset sites_dir = dpath / 'sites' sc_argv = [ '--in_file', os.fspath(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', '0', '--track_kwargs', json.dumps({ 'thresh': 1e-9, 'min_area_square_meters': None, 'max_area_square_meters': None, 'polygon_simplify_tolerance': 1, 'boundaries_as': 'polys', 'key': ('salient',) }), ] run_tracker.main(sc_argv) # Run SV on the same dset sites_dir = dpath / 'sv_sites' sv_argv = [ '--in_file', str(sc_coco_fpath), '--out_sites_dir', str(sites_dir), '--out_sites_fpath', str(sv_fpath), '--out_kwcoco', str(sv_coco_fpath), '--track_fn', 'site_validation', '--site_summary', str(bas_fpath), '--sensor_warnings', '0', '--track_kwargs', json.dumps({ 'thresh': 1e-9, 'min_area_square_meters': None, 'max_area_square_meters': None, 'polygon_simplify_tolerance': 1, 'key': 'salient', # 'thresh': 0 }), ] run_tracker.main(sv_argv) # Check expected results sc_coco_dset = kwcoco.CocoDataset(sc_coco_fpath) sc_trackids = sc_coco_dset.annots().lookup('track_id', None) 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')) 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'] print(ub.codeblock( f''' len(site_rows) = {len(site_rows)} len(ssum_rows) = {len(ssum_rows)} len(obs_rows) = {len(obs_rows)} ''')) assert len(site_rows) > 0 assert len(ssum_rows) > 0 assert len(ssum_rows) == len(site_rows), ( 'number of site summaries and site headers should always be equal' ) assert len(obs_rows) > len(site_rows), ( 'we should have more than one observation per-track') # Cleanup # dpath.delete() def test_tracker_bas_with_boundary_region(): """ Runs two variants of the tracker, one with region bounds on and another with region bounds off. We generate demo region in a way that should always result in some tracks being removed. CommandLine: pytest tests/test_tracker.py -k test_tracker_bas_with_boundary_region -s """ from geowatch.demo.smart_kwcoco_demodata import random_inscribed_polygon import json import kwcoco import ubelt as ub from geowatch.cli import run_tracker import geowatch from geowatch.geoannots import geomodels from geowatch.geoannots.geococo_objects import CocoGeoVideo from geowatch.utils import util_gis import kwimage import kwarray coco_dset = geowatch.coerce_kwcoco('geowatch-msi', heatmap=True, geodata=True, dates=True, image_size=(96, 96)) coco_dset.clear_annotations() rng = kwarray.ensure_rng(4329423) # Make some region models for this dataset import geopandas as gpd region_models = [] crs84 = util_gis.get_crs84() video_name_to_crs84_bounds = {} for video in coco_dset.videos().objs: coco_video = CocoGeoVideo(video=video, dset=coco_dset) dates = coco_video.images.lookup('date_captured') start_time = min(dates) end_time = max(dates) utm_part = coco_video.wld_corners_gdf utm_poly = kwimage.Polygon.coerce(utm_part.iloc[0]['geometry']) # Make a random inscribed polygon to use as the test region utm_region_poly = random_inscribed_polygon(utm_poly, rng=rng) # Shrink it so we are more likely to remove annotations utm_region_poly = utm_region_poly.scale(0.5, about='centroid') crs84_region_poly = kwimage.Polygon.coerce(gpd.GeoDataFrame( geometry=[utm_region_poly], crs=utm_part.crs).to_crs(crs84).iloc[0]['geometry']) video_name_to_crs84_bounds[coco_video['name']] = crs84_region_poly region_model = geomodels.RegionModel.random( region_id=coco_video['name'], region_poly=crs84_region_poly, rng=rng, start_time=start_time, end_time=end_time) region_models.append(region_model) video0 = coco_dset.videos().objs[0] video0_images = coco_dset.images(video_id=video0['id']) assert len(video0_images) >= 10, 'should have a several frames' assert len(set(video0_images.lookup('date_captured'))) > 5, ( 'should be on different dates') dpath = ub.Path.appdir('geowatch', 'test', 'tracking', 'unit_test2').ensuredir() dpath.delete().ensuredir() dpath1 = (dpath / 'with_boundary_region').ensuredir() dpath2 = (dpath / 'without_boundary_region').ensuredir() # Write region models to disk region_models_dpath = (dpath1 / 'region_models').ensuredir() for region_model in region_models: region_fpath = region_models_dpath / (region_model.region_id + '.geojson') region_fpath.write_text(region_model.dumps(indent=4)) track_kwargs = { 'thresh': 0.5, 'time_thresh': .8, 'min_area_square_meters': None, 'max_area_square_meters': None, 'polygon_simplify_tolerance': 1, # 'moving_window_size': 1, } # Run BAS with region bounds on (1) coco_dset.reroot(absolute=True) in_coco_fpath1 = dpath1 / 'bas_input.kwcoco.json' coco_dset.dump(in_coco_fpath1, indent=2) regions_dir1 = dpath1 / 'regions/' bas_coco_fpath1 = dpath1 / 'bas_output.kwcoco.json' bas_fpath1 = dpath1 / 'bas_sites.json' bas_argv1 = [ '--in_file', os.fspath(in_coco_fpath1), '--out_site_summaries_dir', str(regions_dir1), '--out_site_summaries_fpath', str(bas_fpath1), '--out_kwcoco', str(bas_coco_fpath1), '--track_fn', 'saliency_heatmaps', '--boundary_region', region_models_dpath, '--track_kwargs', json.dumps(track_kwargs), '--sensor_warnings', '0', ] run_tracker.main(bas_argv1) # Run BAS with region bounds off (2) in_coco_fpath2 = dpath2 / 'bas_input.kwcoco.json' coco_dset.dump(in_coco_fpath2, indent=2) regions_dir2 = dpath2 / 'regions/' bas_coco_fpath2 = dpath2 / 'bas_output.kwcoco.json' bas_fpath2 = dpath2 / 'bas_sites.json' bas_argv2 = [ '--in_file', in_coco_fpath2, '--out_site_summaries_dir', str(regions_dir2), '--out_site_summaries_fpath', str(bas_fpath2), '--out_kwcoco', str(bas_coco_fpath2), '--track_fn', 'saliency_heatmaps', '--track_kwargs', json.dumps(track_kwargs), '--sensor_warnings', '0', ] run_tracker.main(bas_argv2) bas_coco_dset1 = kwcoco.CocoDataset(bas_coco_fpath1) bas_coco_dset2 = kwcoco.CocoDataset(bas_coco_fpath2) bas_trackids = bas_coco_dset1.annots().lookup('track_id', None) assert len(bas_trackids) > len(set(bas_trackids)), ( 'should have multiple observations per track') num_oob_tracks_in_dset1 = 0 num_oob_tracks_in_dset2 = 0 import numpy as np from shapely.ops import unary_union from shapely.geometry import shape for video_id in bas_coco_dset1.videos(): video_name1 = bas_coco_dset1.index.videos[video_id]['name'] video_name2 = bas_coco_dset1.index.videos[video_id]['name'] assert video_name1 == video_name2, 'dsets should agree' region_bounds = video_name_to_crs84_bounds[video_name1].to_shapely() video_annots1 = bas_coco_dset1.images(video_id=video_id).annots video_annots2 = bas_coco_dset2.images(video_id=video_id).annots annots1 = bas_coco_dset1.annots(list(ub.flatten(video_annots1))) annots2 = bas_coco_dset2.annots(list(ub.flatten(video_annots2))) assert len(annots1) <= len(annots2), ( 'boundaries should strictly remove annots') trackid_to_geoms1 = ub.group_items(annots1.lookup('segmentation_geos'), annots1.lookup('track_id')) trackid_to_geoms2 = ub.group_items(annots2.lookup('segmentation_geos'), annots2.lookup('track_id')) inbound_flags1 = [] for tid1, geoms1 in trackid_to_geoms1.items(): if len(geoms1): track_poly1 = unary_union(list(map(shape, geoms1))) inbound_flag1 = region_bounds.intersects(track_poly1) inbound_flags1.append(inbound_flag1) inbound_flags2 = [] for tid2, geoms2 in trackid_to_geoms2.items(): if len(geoms2): track_poly2 = unary_union(list(map(shape, geoms2))) inbound_flag2 = region_bounds.intersects(track_poly2) inbound_flags2.append(inbound_flag2) assert all(inbound_flags1), ( 'the region_bounds version should never have a fully oob track') num_oob_tracks_in_dset1 += (1 - np.array(inbound_flags1, dtype=int)).sum() num_oob_tracks_in_dset2 += (1 - np.array(inbound_flags2, dtype=int)).sum() assert num_oob_tracks_in_dset1 == 0 if num_oob_tracks_in_dset2 == 0: raise AssertionError( 'This test should have been written such that some of the generated ' 'tracks should have been out of bounds. However, if the demodata changes' 'that assumption may break. This error could be a warning if it fails, ' 'but then the tests should be fixed to ensure there are oob sites ' 'being removed' ) def test_tracker_nan_params(): """ Test that nan params are properly handled """ import json import kwcoco import ubelt as ub from geowatch.cli import run_tracker import geowatch coco_dset = geowatch.coerce_kwcoco('geowatch-msi', heatmap=True, geodata=True, dates=True) video0 = coco_dset.videos().objs[0] video0_images = coco_dset.images(video_id=video0['id']) assert len(video0_images) >= 10, 'should have a several frames' assert len(set(video0_images.lookup('date_captured'))) > 5, ( 'should be on different dates') #coco_dset = smart_kwcoco_demodata.demo_smart_aligned_kwcoco() dpath = ub.Path.appdir('geowatch', 'test', 'tracking', 'unit_test1').ensuredir() dpath.delete().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) regions_dir = dpath / 'regions/' bas_coco_fpath = dpath / 'bas_output.kwcoco.json' bas_fpath = dpath / 'bas_sites.json' # Run BAS bas_argv = [ '--in_file', os.fspath(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', '0', '--track_kwargs', json.dumps({ 'thresh': 0.5, 'time_thresh': .8, 'min_area_square_meters': None, 'moving_window_size': None, 'max_area_square_meters': None, 'polygon_simplify_tolerance': 1, }), ] run_tracker.main(bas_argv) bas_coco_dset = kwcoco.CocoDataset(bas_coco_fpath) bas_trackids = bas_coco_dset.annots().lookup('track_id', None) assert len(bas_trackids) > len(set(bas_trackids)), ( 'should have multiple observations per track') def test_tracker_ac_refinement(): """ Generates random BAS predictions and a demo dataset with random AC heatmaps. Then run AC tracking. Quick Links: ~/code/geowatch/geowatch/cli/run_tracker.py CommandLine: WARN_WITH_TRACEBACK=1 xdoctest ~/code/geowatch/tests/test_tracker.py test_tracker_ac_refinement pytest tests/test_tracker.py -k test_tracker_ac_refinement -s """ from geowatch.demo.smart_kwcoco_demodata import random_inscribed_polygon import json import ubelt as ub from geowatch.cli import run_tracker import geowatch from geowatch.geoannots import geomodels from geowatch.geoannots.geococo_objects import CocoGeoVideo from geowatch.utils import util_gis import kwimage import kwarray import kwcoco coco_dset = geowatch.coerce_kwcoco( 'geowatch', heatmap='No Activity|Site Preparation|Active Construction|Post Construction,ac_salient', num_frames=12, num_videos=1, geodata=True, dates=True, image_size=(96, 96)) coco_dset.clear_annotations() rng = kwarray.ensure_rng(4329423) # Make some region models for this dataset import geopandas as gpd input_region_models = [] crs84 = util_gis.get_crs84() video_name_to_crs84_bounds = {} for video in coco_dset.videos().objs: coco_video = CocoGeoVideo(video=video, dset=coco_dset) dates = coco_video.images.lookup('date_captured') start_time = min(dates) end_time = max(dates) utm_part = coco_video.wld_corners_gdf utm_poly = kwimage.Polygon.coerce(utm_part.iloc[0]['geometry']) # Make a random inscribed polygon to use as the test region utm_region_poly = random_inscribed_polygon(utm_poly, rng=rng) # Shrink it so we are more likely to remove annotations utm_region_poly = utm_region_poly.scale(0.8, about='centroid') crs84_region_poly = kwimage.Polygon.coerce(gpd.GeoDataFrame( geometry=[utm_region_poly], crs=utm_part.crs).to_crs(crs84).iloc[0]['geometry']) video_name_to_crs84_bounds[coco_video['name']] = crs84_region_poly region_model = geomodels.RegionModel.random( region_id=coco_video['name'], region_poly=crs84_region_poly, rng=rng, start_time=start_time, end_time=end_time) for sitesum in region_model.site_summaries(): # Modify status of site summaries to simulate bas output # sitesum['properties']['status'] = 'system_confirmed' sitesum['properties']['status'] = 'Site Boundary' input_region_models.append(region_model) video0 = coco_dset.videos().objs[0] video0_images = coco_dset.images(video_id=video0['id']) assert len(video0_images) >= 10, 'should have a several frames' assert len(set(video0_images.lookup('date_captured'))) > 5, ( 'should be on different dates') dpath = ub.Path.appdir('geowatch', 'test', 'tracking', 'ac_refine').ensuredir() dpath.delete().ensuredir() # Write region models to disk in_region_models_dpath = (dpath / 'input_region_models').ensuredir() for region_model in input_region_models: region_fpath = in_region_models_dpath / (region_model.region_id + '.geojson') region_fpath.write_text(region_model.dumps(indent=4)) track_kwargs = { 'thresh': 0.2, 'time_thresh': .4, 'polygon_simplify_tolerance': None, 'new_algo': 'crall', # 'boundaries_as': 'bounds', 'boundaries_as': None, # 'resolution': '8GSD', # 'min_area_square_meters': None, # 'max_area_square_meters': None, # 'polygon_simplify_tolerance': 1, # 'moving_window_size': 1, } # Run AC with polygon refinement coco_dset.reroot(absolute=True) in_coco_fpath = dpath / 'ac_heatmap.kwcoco.json' coco_dset.dump(in_coco_fpath, indent=2) out_coco_fpath = dpath / 'ac_tracked.kwcoco.json' out_sitesum_dpath = dpath / 'out_site_summaries/' out_sitesum_fpath = dpath / 'out_site_summaries.json' out_site_fpath = dpath / 'out_sites.json' out_sites_dir = dpath / 'out_sites' ac_argv = [ '--input_kwcoco', os.fspath(in_coco_fpath), '--in_site_summaries', os.fspath(in_region_models_dpath), '--out_sites_dir', str(out_sites_dir), '--out_sites_fpath', str(out_site_fpath), '--out_site_summaries_dir', str(out_sitesum_dpath), '--out_site_summaries_fpath', str(out_sitesum_fpath), '--out_kwcoco', str(out_coco_fpath), '--track_fn', 'class_heatmaps', # '--boundary_region', in_region_models_dpath, '--track_kwargs', json.dumps(track_kwargs), '--site_score_thresh', '0.2', '--smoothing', '0.0', '--sensor_warnings', '0', '--viz_out_dir', ub.Path('~/testviz').expand(), ] run_tracker.main(ac_argv) ac_coco_dset = kwcoco.CocoDataset(out_coco_fpath) trackids = ac_coco_dset.annots().lookup('track_id', None) print(f'trackids={trackids}') DEVELOPER_SPACE_VIZ = 1 if DEVELOPER_SPACE_VIZ: import kwplot kwplot.autosns() # Show via site summaries print(out_sitesum_dpath.ls()) bas_region = geomodels.RegionModel.coerce(in_region_models_dpath / 'toy_video_1.geojson') refined_region = geomodels.RegionModel.coerce(out_sitesum_dpath / 'toy_video_1.geojson') fig = kwplot.figure(fnum=1, doclf=1) ax = fig.gca() gdf1 = bas_region.pandas_summaries() gdf2 = refined_region.pandas_summaries() gdf1.plot(ax=ax, edgecolor='red', facecolor='none') gdf2.plot(ax=ax, edgecolor='blue', facecolor='none') from shapely.ops import unary_union big = unary_union([gdf1.unary_union, gdf2.unary_union]) poly = kwimage.MultiPolygon.coerce(big) box = poly.box().scale(1.1, about='centroid') ax.set_ylim(box.tl_y, box.br_y) ax.set_xlim(box.tl_x, box.br_x) DEVELOPER_SPACETIME_VIZ = 0 if DEVELOPER_SPACETIME_VIZ: # -- # Show what the input regions looked like on heatmaps bas_coco_fpath = dpath / 'input_sites.kwcoco.json' _ = ub.cmd(ub.codeblock( f''' geowatch reproject \ --src "{in_coco_fpath}" \ --dst "{bas_coco_fpath}" \ --region_models "{in_region_models_dpath}" \ --status_to_catname "{{'system_confirmed': 'positive'}}" " '''), verbose=3, system=True) _ = ub.cmd(f'geowatch visualize {bas_coco_fpath} --smart', verbose=3, system=True) _ = ub.cmd(f'geowatch visualize {out_coco_fpath} --smart', verbose=3, system=True) """ # Real Data Testing cd /home/joncrall/data/dvc-repos/smart_expt_dvc/_airflow/preeval18_batch_v136/KR_R001/sc-fusion/ cd /home/joncrall/data/dvc-repos/smart_expt_dvc/_airflow/preeval18_batch_v136/KR_R002/sc-fusion/ VIZ_DPATH=/home/joncrall/testviz/real-old python -m geowatch.cli.run_tracker \ --in_file=./sc_fusion_kwcoco.json \ --in_file_gt=None \ --out_kwcoco=./sc_fusion_kwcoco_tracked2.json \ --out_sites_dir=./sc_out_site_models2 \ --out_site_summaries_dir=./sc_out_region_models2 \ --out_sites_fpath=./site_models_manifest2.json \ --out_site_summaries_fpath=./sc_out_region_models_manifest.json \ --region_id=None \ --track_fn=None \ --default_track_fn=class_heatmaps \ --viz_out_dir="$VIZ_DPATH" \ --site_summary='./sv_out_region_models/*.geojson' \ --clear_annots=True \ --append_mode=False \ --boundary_region=None \ --sensor_warnings=True \ --time_pad_before=None \ --time_pad_after=None \ --smoothing=0.0 \ --site_score_thresh=0.3 \ --track_kwargs ' { "boundaries_as": "bounds", # "boundaries_as": "null", "min_area_square_meters": 7200, "new_algo": "crall", "polygon_simplify_tolerance": 1, "resolution": "8GSD", "thresh": 0.3 }' cd "$VIZ_DPATH" kwimage stack_images "$VIZ_DPATH"/*/*final_labels.png --out "$VIZ_DPATH"/all_final_labels.png kwimage stack_images "$VIZ_DPATH"/*/*agg_cube.png --out "$VIZ_DPATH"/all_agg_cube.png kwimage stack_images "$VIZ_DPATH"/*/*cubes.png --out "$VIZ_DPATH"/cubes.png kwimage stack_images "$VIZ_DPATH"/*/*step_007_volume_labels_bounds.png --out "$VIZ_DPATH"/all_step_007_volume_labels_bounds.png # Real Data Testing cd /home/joncrall/data/dvc-repos/smart_expt_dvc/_airflow/preeval18_batch_v136/KR_R002/sc-fusion/ VIZ_DPATH=/home/joncrall/testviz/kr2-real-new python -m geowatch.cli.run_tracker \ --in_file=./sc_fusion_kwcoco.json \ --out_kwcoco=./sc_fusion_kwcoco_tracked2.json \ --out_sites_dir=./sc_out_site_models2 \ --out_site_summaries_dir=./sc_out_region_models2 \ --out_sites_fpath=./site_models_manifest2.json \ --out_site_summaries_fpath=./sc_out_region_models_manifest.json \ --in_file_gt=None \ --region_id=None \ --track_fn=None \ --default_track_fn=class_heatmaps \ --viz_out_dir="$VIZ_DPATH" \ --site_summary='./sv_out_region_models/*.geojson' \ --clear_annots=True \ --append_mode=False \ --boundary_region=None \ --sensor_warnings=True \ --time_pad_before=None \ --time_pad_after=None \ --smoothing=0.0 \ --site_score_thresh=0.3 \ --track_kwargs ' { # "boundaries_as": "bounds", "boundaries_as": "null", "min_area_square_meters": 7200, "new_algo": "crall", "polygon_simplify_tolerance": 1, "resolution": "8GSD", "thresh": 0.3 }' cd "$VIZ_DPATH" VIZ_DPATH="." kwimage stack_images "$VIZ_DPATH"/*/*final_labels.png --out "$VIZ_DPATH"/all_final_labels.png kwimage stack_images "$VIZ_DPATH"/*/*agg_cube.png --out "$VIZ_DPATH"/all_agg_cube.png kwimage stack_images "$VIZ_DPATH"/*/*cubes.png --out "$VIZ_DPATH"/cubes.png """