geowatch.cli.coco_average_features module

class geowatch.cli.coco_average_features.CocoAverageFeaturesConfig(*args, **kwargs)[source]

Bases: DataConfig

Average multiple kwcoco files - i.e. ensemble heatmap predictions.

Create a new kwcoco file with averaged features from multiple kwcoco files.

High Level Steps:
  1. Load kwcoco files. Must have at least two kwcoco files.

  2. Create new kwcoco file by copying first kwcoco file.

  3. For each image ID in the kwcoco file, load the features from each kwcoco file.
    1. Average the features from each kwcoco file.

    2. Save the averaged features to the new kwcoco image.

  4. Save the new kwcoco file.

Valid options: []

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

  • **kwargs – keyword arguments for this data config

default = {'channel_name': <Value(None)>, 'flexible_merge': <Value(False)>, 'io_workers': <Value('avail')>, 'kwcoco_file_paths': <Value(None)>, 'output_channel_names': <Value(None)>, 'output_kwcoco_path': <Value(None)>, 'resolution': <Value(None)>, 'sensors': <Value(None)>, 'weights': <Value(None)>}
geowatch.cli.coco_average_features.split_channel_names_by_grammar(channel_names)[source]

Split a string containing channel names by commas (,) and pipes (|).

Parameters:

channel_names (str) – A string that may contain commas and pipe characters.

Returns:

A list of strings that were originally divided by certain characters.

Return type:

list(str)

geowatch.cli.coco_average_features.check_kwcoco_file(kwcoco_file, channel_name, sensor_names=None, flexible_merge=False)[source]

Make sure that kwcoco files exist and contain required channel name.

Parameters:
  • kwcoco_file (kwcoco.CocoDataset) – kwcoco file containing images.

  • channel_name (str) – Name of channel thats required to be in kwcoco file.

  • sensor_names (list(str), optional) – Only check images of from these types of sensors. Defaults to None.

  • flexible_merge (bool, optional) – Skip images that do not contain channel_name. Defaults to False.

Returns:

A list of names corresponding to images without the channel name.

Return type:

missing_image_names (list)

geowatch.cli.coco_average_features.merge_kwcoco_channels(kwcoco_file_paths, output_kwcoco_path, channel_names, weights, output_channel_names, sensor_names=None, resolution=None, flexible_merge=False, io_workers='avail')[source]

Compute a weighted mean of channels from separate kwcoco file and save into merged kwcoco file.

Assumptions:
  • The channel_nams to merge are not a subset of a group of channels targeted in the kwcoco file.
    • I.e. ‘salient’ in ‘salient|notsalient’ will not work.

Parameters:
  • kwcoco_file_paths (list(str)) – A list of paths representing pathes to kwcoco files to be merged.

  • output_kwcoco_path (str) – Local path to the kwcoco file with merged channels.

  • channel_names (list(str)) – A list of channel names corresponding to the channel name to merge from each kwcoco file. Note, the length of the channel names be equal to the number of kwcoco file paths.

  • weights (list(int)) – A list of floats representing how much weight a particular kwcoco file should contribute to the final merged prediction.

  • output_channel_names (list(str)) – A list containing the names of the output channel names. Must contain the same number of channel names as the input channel names.

  • sensor_names (list(str), optional) – Only merge images belonging to sensors in this list. Defaults to None (aka do not filter by sensor).

  • resolution (int | str, optional) – GSD to resize the resolution of the images to. Defaults to None.

  • flexible_merge (bool, optional) – Skip images that do not contain channel_name. Defaults to False.

Example

>>> # TEST 1: Merge two kwcoco files with the same number of images and plot results.
>>> from geowatch.cli.coco_average_features import *  # NOQA
>>> import geowatch
>>> import kwimage
>>> import numpy as np
>>> from kwcoco.demo.perterb import perterb_coco
>>> import kwcoco
>>> dpath = ub.Path.appdir('geowatch/tests/coco_average_features')
>>> base_dset = geowatch.coerce_kwcoco('geowatch-msi', geodata=True, dates=True, image_size=(64, 64), num_videos=2, num_frames=2)
>>> # Construct two copies of the same data with slightly different heatmaps
>>> dset1 = perterb_coco(base_dset.copy(), box_noise=0.5, cls_noise=0.5, n_fp=10, n_fn=10, rng=32)
>>> dset2 = base_dset.copy()
>>> for video in dset1.dataset['videos']:
...      video['resolution'] = '10GSD'
>>> for video in dset2.dataset['videos']:
...      video['resolution'] = '10GSD'
>>> dset1.fpath = ub.Path(dset1.fpath).augment(stemsuffix='_heatmap1')
>>> dset2.fpath = ub.Path(dset2.fpath).augment(stemsuffix='_heatmap2')
>>> geowatch.demo.smart_kwcoco_demodata.hack_in_heatmaps(dset1, heatmap_dname='dummy_heatmap1', with_nan=0, rng=423432)
>>> geowatch.demo.smart_kwcoco_demodata.hack_in_heatmaps(dset2, heatmap_dname='dummy_heatmap2', with_nan=0, rng=132129)
>>> dset1.dump(dset1.fpath)
>>> dset2.dump(dset2.fpath)
>>> # Build method args
>>> kwcoco_file_paths = [dset1.fpath, dset2.fpath]
>>> output_bundle_dpath = (dpath / 'merge_bundle').delete().ensuredir()
>>> output_kwcoco_path = output_bundle_dpath / 'data.kwcoco.json'
>>> channel_names = ['notsalient|salient'] * 2
>>> weights = [1.0, 1.0]
>>> output_channel_names = 'notsalient|salient'
>>> sensor_name = None
>>> resolution = '12GSD'
>>> # Execute merge
>>> merge_kwcoco_channels(kwcoco_file_paths, output_kwcoco_path,
>>>                       channel_names, weights, output_channel_names,
>>>                       sensor_name, resolution=resolution)
>>> # Check results
>>> output_dset = kwcoco.CocoDataset(output_kwcoco_path)
>>> gid = output_dset.images()[1]
>>> imdata1 = dset1.coco_image(gid).imdelay('salient', space='asset').finalize()
>>> imdata2 = dset2.coco_image(gid).imdelay('salient', space='asset').finalize()
>>> imdataM = output_dset.coco_image(gid).imdelay('salient', space='asset').finalize()
>>> imdata1_img = dset1.coco_image(gid).imdelay('salient', space='image').finalize()
>>> imdata2_img = dset2.coco_image(gid).imdelay('salient', space='image').finalize()
>>> imdataM_img = output_dset.coco_image(gid).imdelay('salient', space='image').finalize()
>>> print(f'Weights: {weights}')
>>> print(f'Img1  mean: {np.nan_to_num(imdata1).mean()}')
>>> print(f'Img2  mean: {np.nan_to_num(imdata2).mean()}')
>>> print(f'Merge mean: {np.nan_to_num(imdataM).mean()}')
>>> print()
>>> print(f'Img1  shape (asset space): {imdata1.shape}')
>>> print(f'Img2  shape (asset space): {imdata2.shape}')
>>> print(f'Merge shape (asset space): {imdataM.shape}')
>>> print(f'Img1  shape (img space): {imdata1_img.shape}')
>>> print(f'Img2  shape (img space): {imdata2_img.shape}')
>>> print(f'Merge shape (img space): {imdataM_img.shape}')
>>> os.remove(dset1.fpath)
>>> os.remove(dset2.fpath)
>>> os.remove(output_dset.fpath)
>>> # xdoctest: +REQUIRES(--show)
>>> import kwplot
>>> kwplot.autompl()
>>> F = kwimage.fill_nans_with_checkers
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdata1)), title='img1 (asset)', pnum=(2, 3, 1), fnum=1)
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdata2)), title='img2 (asset)', pnum=(2, 3, 2), fnum=1)
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdataM)), title='mean (asset)', pnum=(2, 3, 3), fnum=1)
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdata1_img)), title='img1 (img)', pnum=(2, 3, 4), fnum=1)
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdata2_img)), title='img2 (img)', pnum=(2, 3, 5), fnum=1)
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdataM_img)), title='mean (img)', pnum=(2, 3, 6), fnum=1)
>>> save_figure_path = dpath / 'test_1_result_plot.png'
>>> import matplotlib.pyplot as plt
>>> plt.savefig(save_figure_path)
>>> print(f'Test 1 plot saved to: {save_figure_path}')

Example

>>> # TEST 2: Merge two kwcoco files with geo information.
>>> from geowatch.cli.coco_average_features import *  # NOQA
>>> import geowatch
>>> import kwimage
>>> from kwcoco.demo.perterb import perterb_coco
>>> import kwcoco
>>> import numpy as np
>>> dpath = ub.Path.appdir('geowatch/tests/coco_average_features')
>>> base_dset = geowatch.coerce_kwcoco('geowatch-msi', geodata=True, dates=True, image_size=(64, 64), num_videos=2, num_frames=2)
>>> # Construct two copies of the same data with slightly different heatmaps
>>> dset1 = perterb_coco(base_dset.copy(), box_noise=0.5, cls_noise=0.5, n_fp=10, n_fn=10, rng=32)
>>> dset2 = base_dset.copy()
>>> dset1.fpath = ub.Path(dset1.fpath).augment(stemsuffix='_heatmap1')
>>> dset2.fpath = ub.Path(dset2.fpath).augment(stemsuffix='_heatmap2')
>>> geowatch.demo.smart_kwcoco_demodata.hack_in_heatmaps(dset1, heatmap_dname='dummy_heatmap1', with_nan=0, rng=423555)
>>> geowatch.demo.smart_kwcoco_demodata.hack_in_heatmaps(dset2, heatmap_dname='dummy_heatmap2', with_nan=0, rng=132666)
>>> dset1.dump(dset1.fpath)
>>> dset2.dump(dset2.fpath)
>>> # Build method args
>>> kwcoco_file_paths = [dset1.fpath, dset2.fpath]
>>> output_bundle_dpath = (dpath / 'merge_bundle').delete().ensuredir()
>>> output_kwcoco_path = output_bundle_dpath / 'data.kwcoco.json'
>>> channel_names = ['notsalient|salient'] * 2
>>> weights = [1.0, 1.0]
>>> output_channel_names = 'notsalient|salient'
>>> sensor_name = None
>>> resolution = None
>>> # Execute merge
>>> merge_kwcoco_channels(kwcoco_file_paths, output_kwcoco_path,
>>>                       channel_names, weights, output_channel_names,
>>>                       sensor_name, resolution=resolution)
>>> # Check results
>>> output_dset = kwcoco.CocoDataset(output_kwcoco_path)
>>> gid = 3
>>> imdata1 = dset1.coco_image(gid).imdelay('salient', space='asset').finalize()
>>> imdata2 = dset2.coco_image(gid).imdelay('salient', space='asset').finalize()
>>> imdataM = output_dset.coco_image(gid).imdelay('salient', space='asset').finalize()
>>> imdata1_img = dset1.coco_image(gid).imdelay('salient', space='image').finalize()
>>> imdata2_img = dset2.coco_image(gid).imdelay('salient', space='image').finalize()
>>> imdataM_img = output_dset.coco_image(gid).imdelay('salient', space='image').finalize()
>>> print(f'Weights: {weights}')
>>> print(f'Img1  mean: {np.nan_to_num(imdata1).mean()}')
>>> print(f'Img2  mean: {np.nan_to_num(imdata2).mean()}')
>>> print(f'Merge mean: {np.nan_to_num(imdataM).mean()}')
>>> print()
>>> print(f'Img1  shape (asset space): {imdata1.shape}')
>>> print(f'Img2  shape (asset space): {imdata2.shape}')
>>> print(f'Merge shape (asset space): {imdataM.shape}')
>>> print(f'Img1  shape (img space): {imdata1_img.shape}')
>>> print(f'Img2  shape (img space): {imdata2_img.shape}')
>>> print(f'Merge shape (img space): {imdataM_img.shape}')
>>> os.remove(dset1.fpath)
>>> os.remove(dset2.fpath)
>>> os.remove(output_dset.fpath)
>>> # xdoctest: +REQUIRES(--show)
>>> import kwplot
>>> kwplot.autompl()
>>> F = kwimage.fill_nans_with_checkers
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdata1)), title='img1 (asset)', pnum=(2, 3, 1), fnum=1)
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdata2)), title='img2 (asset)', pnum=(2, 3, 2), fnum=1)
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdataM)), title='mean (asset)', pnum=(2, 3, 3), fnum=1)
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdata1_img)), title='img1 (img)', pnum=(2, 3, 4), fnum=1)
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdata2_img)), title='img2 (img)', pnum=(2, 3, 5), fnum=1)
>>> kwplot.imshow(F(kwimage.normalize_intensity(imdataM_img)), title='mean (img)', pnum=(2, 3, 6), fnum=1)
>>> save_figure_path = dpath / 'test_2_result_plot.png'
>>> import matplotlib.pyplot as plt
>>> plt.savefig(save_figure_path)
>>> print(f'Test 2 plot saved to: {save_figure_path}')
geowatch.cli.coco_average_features.main(cmdline=True, **kw)[source]

Main function for merge_kwcoco_channels. See CocoAverageFeaturesConfig for details

TODO: Add examples

Example

>>> from geowatch.cli.coco_average_features import *  # NOQA
>>> import geowatch
>>> import kwimage
>>> import numpy as np
>>> from kwcoco.demo.perterb import perterb_coco
>>> import kwcoco
>>> dpath = ub.Path.appdir('geowatch/tests/coco_average_features_main')
>>> base_dset = geowatch.coerce_kwcoco('geowatch-msi', geodata=True, dates=True, image_size=(64, 64), num_videos=2, num_frames=2)
>>> # Construct two copies of the same data with slightly different heatmaps
>>> dset1 = perterb_coco(base_dset.copy(), box_noise=0.5, cls_noise=0.5, n_fp=10, n_fn=10, rng=32)
>>> dset2 = base_dset.copy()
>>> for video in dset1.dataset['videos']:
...      video['resolution'] = '10GSD'
>>> for video in dset2.dataset['videos']:
...      video['resolution'] = '10GSD'
>>> dset1.fpath = ub.Path(dset1.fpath).augment(stemsuffix='_heatmap1')
>>> dset2.fpath = ub.Path(dset2.fpath).augment(stemsuffix='_heatmap2')
>>> geowatch.demo.smart_kwcoco_demodata.hack_in_heatmaps(dset1, heatmap_dname='dummy_heatmap1', with_nan=0, rng=423432)
>>> geowatch.demo.smart_kwcoco_demodata.hack_in_heatmaps(dset2, heatmap_dname='dummy_heatmap2', with_nan=0, rng=132129)
>>> dset1.dump(dset1.fpath)
>>> dset2.dump(dset2.fpath)
>>> output_kwcoco_path = dpath / 'output.kwcoco.zip'
>>> # Execute merge
>>> kwargs = {
>>>     'kwcoco_file_paths': [dset1.fpath, dset2.fpath],
>>>     'output_kwcoco_path': output_kwcoco_path,
>>>     'channels': ['notsalient|salient'],
>>>     'resolution': 30,
>>> }
>>> cmdline = 0
>>> main(cmdline=cmdline, **kwargs)
>>> output_dset = kwcoco.CocoDataset(output_kwcoco_path)
>>> coco_img = output_dset.images().coco_images[0]
>>> import numpy as np
>>> assert not np.all(np.isnan(coco_img.imdelay('salient').finalize()))