#!/usr/bin/env python3
"""
CommandLine:
xdoctest -m kwcoco.cli.coco_plot_stats __doc__:0
xdoctest -m kwcoco.cli.coco_plot_stats __doc__:1
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> # Stats on a simple dataset
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> import kwcoco
>>> dpath = ub.Path.appdir('kwcoco/tests/vis_stats').ensuredir()
>>> coco_fpath = kwcoco.CocoDataset.demo('vidshapes8').fpath
>>> cmdline = 0
>>> kwargs = dict(src=coco_fpath, dst_dpath=dpath)
>>> cls = PlotStatsCLI
>>> cls.main(cmdline=cmdline, **kwargs)
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> # Stats on a more complex dataset
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> import kwcoco
>>> import kwarray.distributions
>>> import kwarray
>>> rng = kwarray.ensure_rng(0)
>>> dpath = ub.Path.appdir('kwcoco/tests/vis_stats2').ensuredir()
>>> dset = kwcoco.CocoDataset.demo('vidshapes8', image_size='random',
>>> timestamps=True, rng=rng)
>>> coco_fpath = dset.fpath
>>> cmdline = 0
>>> kwargs = dict(src=coco_fpath, dst_dpath=dpath)
>>> cls = PlotStatsCLI
>>> cls.main(cmdline=cmdline, **kwargs)
"""
import scriptconfig as scfg
import ubelt as ub
import os
[docs]
class PlotStatsCLI(scfg.DataConfig):
"""
Inspect properties of dataset and write raw data tables and visual plots.
"""
__command__ = 'plot_stats'
__alias__ = ['visual_stats']
src = scfg.Value(None, help='path to kwcoco file', position=1)
#
# from kwcoco.cli.coco_plot_stats import Plots # NOQA
# print(', '.join(Plots.available_plot_funcs().keys()))
# TODO: keep this in sync
plots = scfg.Value(None, help=ub.paragraph(
'''
Names of specific plots to create. If unspecified, all plots are
generated.
Available plot names are:
all_polygons, anns_per_image_histogram,
anns_per_image_histogram_ge1, anns_per_image_histogram_splity,
image_size_histogram, image_size_scatter, images_over_time,
images_timeofday_distribution, obox_size_distribution,
obox_size_distribution_jointplot, obox_size_distribution_logscale,
polygon_area_histogram, polygon_area_histogram_logscale,
polygon_area_histogram_splity, polygon_area_vs_num_verts,
polygon_area_vs_num_verts_jointplot,
polygon_area_vs_num_verts_jointplot_logscale,
polygon_centroid_absolute_distribution,
polygon_centroid_absolute_distribution_jointplot,
polygon_centroid_relative_distribution,
polygon_centroid_relative_distribution_jointplot,
polygon_num_vertices_histogram
'''), nargs='+', position=2)
dst_fpath = scfg.Value('auto', help='manifest of results. If unspecfied defaults to dst_dpath / "stats.json"')
dst_dpath = scfg.Value('./coco_annot_stats', help='directory to dump results')
with_process_context = scfg.Value(True, help='set to false to disable process contxt')
options = scfg.Value(None, help=ub.paragraph(
'''
YAML options specification
'''))
dpi = scfg.Value(300, help='dpi for figures')
[docs]
@classmethod
def main(cls, cmdline=1, **kwargs):
from kwcoco.util.util_rich import rich_print
try:
from rich.markup import escape
except ImportError:
from ubelt import identity as escape
config = cls.cli(cmdline=cmdline, data=kwargs, strict=True)
rich_print('config = ' + escape(ub.urepr(config, nl=1)))
run(config)
__cli__ = PlotStatsCLI
[docs]
def prep_plots(src, plots_dpath=None, options=None, dpi=300):
import kwcoco
import json
from kwcoco.util.util_rich import rich_print
with ub.Timer(label='Loading kwcoco file') as load_timer:
dset = kwcoco.CocoDataset.coerce(src)
scalar_stats, tables_data, nonsaved_data, dataframes = build_stats_data(dset)
cache_dpath = ub.Path(dset.fpath).absolute().parent / '_cache'
cacher = ub.Cacher('stats', depends=dset._cached_hashid(), dpath=cache_dpath)
loaded = cacher.tryload()
if loaded is None:
info = build_stats_data(dset)
scalar_stats, tables_data, nonsaved_data, dataframes = info
cacher.save((scalar_stats, tables_data, nonsaved_data))
else:
scalar_stats, tables_data, nonsaved_data = loaded
dataframes = None
scalar_stats['kwcoco_loadtime_seconds'] = load_timer.elapsed
if dataframes is not None:
perimage_data = dataframes['perimage_data']
perannot_data = dataframes['perannot_data']
perannot_summary = json.loads(perannot_data.describe().to_json())
perimage_summary = json.loads(perimage_data.describe().to_json())
rich_print('perannot_summary:')
rich_print(ub.urepr(perannot_summary, nl=-1, align=':', precision=2))
rich_print('perimage_summary:')
rich_print(ub.urepr(perimage_summary, nl=-1, align=':', precision=2))
print('Preparing plots')
plots = Plots(plots_dpath, tables_data, nonsaved_data, options, dpi=dpi)
plots.scalar_stats = scalar_stats
return plots
[docs]
def run(config):
import json
import safer
import kwutil
dst_dpath = ub.Path(config['dst_dpath'])
if config['dst_fpath'] == 'auto':
dst_fpath = dst_dpath / 'stats.json'
else:
dst_fpath = ub.Path(config['dst_fpath'])
dst_dpath = dst_dpath.absolute()
dst_dpath.ensuredir()
plots_dpath = dst_dpath / 'annot_stat_plots'
tables_fpath = dst_dpath / 'stats_tables.json'
tables_fpath = dst_dpath / 'stats_tables.json'
from kwcoco.util.util_rich import rich_print
rich_print(f'Destination Path: [link={dst_dpath}]{dst_dpath}[/link]')
if config.with_process_context:
from kwutil.process_context import ProcessContext
proc_context = ProcessContext(
name='kwcoco.cli.coco_plot_stats',
config=kwutil.Json.ensure_serializable(config.to_dict())
)
proc_context.start()
else:
proc_context = None
src = config['src']
options = config['options']
dpi = config.dpi
plots = prep_plots(src, plots_dpath, options, dpi=dpi)
tables_data = plots.tables_data
rich_print(f'Will write plots to: [link={plots_dpath}]{plots_dpath}[/link]')
scalar_stats = plots.scalar_stats
print(f'scalar_stats = {ub.urepr(scalar_stats, nl=1)}')
rich_print(f'Write {tables_fpath}')
with safer.open(tables_fpath, 'w', temp_file=not ub.WIN32) as fp:
json.dump(tables_data, fp, indent=' ')
available_plots = list(plots.plot_functions.keys())
if config.plots is not None:
user_requested_plots = ub.oset(config.plots)
requested_plots = list(user_requested_plots & set(available_plots))
unknown = user_requested_plots - set(available_plots)
if unknown:
print(f'WARNING: ignoring unknown plots unknown={unknown}')
else:
requested_plots = available_plots
print(f'requested_plots = {ub.urepr(requested_plots, nl=1)}')
rich_print(f'Will write plots to: [link={plots_dpath}]{plots_dpath}[/link]')
with ub.Timer(label='Plotting') as plot_timer:
pman = kwutil.util_progress.ProgressManager()
with pman:
# plot_func_keys = [
# # 'images_over_time',
# 'images_timeofday_distribution',
# ]
for key in pman.ProgIter(requested_plots, desc='plot'):
pman.update_info(f'Plotting: {key}')
func = plots.plot_functions[key]
try:
func(plots)
except Exception as ex:
rich_print(f'[red] ERROR: in {func}')
rich_print(f'ex = {ub.urepr(ex, nl=1)}')
import traceback
traceback.print_exc()
if 0:
raise
scalar_stats['plottime_seconds'] = plot_timer.elapsed
rich_print(f'Finished writing plots to: [link={plots_dpath}]{plots_dpath}[/link]')
# Write manifest of all data written to disk
summary_data = {}
if proc_context is not None:
proc_context.stop()
obj = proc_context.stop()
obj = kwutil.Json.ensure_serializable(obj)
summary_data['info'] = [obj]
summary_data['scalar_stats'] = kwutil.Json.ensure_serializable(scalar_stats)
print('Finalizing manifest')
summary_data['src'] = str(config['src'])
summary_data['plots_dpath'] = os.fspath(plots_dpath)
summary_data['tables_fpath'] = os.fspath(tables_fpath)
# summary_data['perannot_summary'] = perannot_summary
# summary_data['perimage_summary'] = perimage_summary
# Write file to indicate the process has completed correctly
# TODO: Use safer
rich_print(f'Write {dst_fpath}')
with safer.open(dst_fpath, 'w', temp_file=not ub.WIN32) as fp:
json.dump(summary_data, fp, indent=' ')
[docs]
def rerun_plots(tables_fpath):
"""
TODO:
- [ ] Easy CLI / IPython mechanism to rerun plots with precompiled stat tables
from kwcoco.cli.coco_plot_stats import * # NOQA
tables_fpath = './coco_annot_stats2/stats_tables.json'
import kwplot
import kwplot
kwplot.autosns()
"""
import kwutil
tables_data = kwutil.Json.load(tables_fpath)
plots_dpath = None
nonsaved_data = None
dpi = None
options = None
plots = Plots(plots_dpath, tables_data, nonsaved_data, options, dpi=dpi)
self = plots # NOQA
BuiltinPlots.polygon_area_vs_num_verts(self)
# plots.plot_functions['polygon_area_vs_num_verts'](self)
[docs]
def geospatial_stats(dset, images, perimage_data):
import math
import warnings
import numpy as np
ESTIMATE_SUNLIGHT = 1
if ESTIMATE_SUNLIGHT:
# This might be more of a domain-specific plugin feature, than
# something that kwcoco should be concerned with.
def coco_estimate_sunlight(dset, image_ids=None):
try:
from kwgis.utils.util_sunlight import estimate_sunlight
# import suntime # NOQA
import astral # NOQA
import timezonefinder # NOQA
import pytz # NOQA
raise ImportError
except ImportError as ex:
from kwutil.util_exception import add_exception_note
msg = f'''
Missing requirements, please:
pip install astral suntime timezonefinder pytz kwgis
{ex}
'''
if 0:
raise add_exception_note(ex, ub.codeblock(msg))
else:
warnings.warn(msg)
else:
from kwutil.util_math import Rational
sunlight_values = []
images = dset.images(image_ids)
for img in images.objs_iter():
if 'geos_point' not in img:
sunlight = math.nan
else:
geos_point = img['geos_point']
if isinstance(geos_point, float) and math.isnan(geos_point):
sunlight = math.nan
elif not isinstance(geos_point, dict):
warnings.warn(f'Warning: unknown geos_point format {geos_point!r} in {img!r}')
sunlight = np.nan
else:
coords = geos_point['coordinates']
point = [Rational.coerce(x) for x in coords]
lon, lat = point
datetime = img['datetime']
sunlight = estimate_sunlight(lat, lon, datetime)
sunlight_values.append(sunlight)
sunlight_values = np.array(sunlight_values)
return sunlight_values
try:
sunlight_values = coco_estimate_sunlight(dset, image_ids=images)
if sunlight_values is not None and not np.isnan(sunlight_values).all():
perimage_data['sunlight'] = sunlight_values
except ImportError as ex:
print(f'Unable to estimate sunlight: {ex}')
raise
except Exception as ex:
print(f'Unable to estimate sunlight: {ex}')
raise
[docs]
def build_stats_data(dset):
"""
Build a table of perimage and perannotation as well as a summary table of
higher level results.
"""
import kwimage
import numpy as np
import pandas as pd
import json
with ub.Timer(label='Building tables for stats') as stats_timer:
images = dset.images()
image_widths = images.get('width')
image_heights = images.get('height')
max_width = max(image_widths) # NOQA
max_height = max(image_heights) # NOQA
anns_per_image = np.array(images.n_annots)
images_with_eq0_anns = (anns_per_image == 0).sum()
images_with_ge1_anns = (anns_per_image >= 1).sum()
scalar_stats = {
**dset.basic_stats(),
'images_with_eq0_anns': images_with_eq0_anns,
'images_with_ge1_anns': images_with_ge1_anns,
'frac_images_with_ge1_anns': images_with_ge1_anns / len(images),
'frac_images_with_eq0_anns': images_with_eq0_anns / len(images),
}
# Fixme, standardize timestamp field
datetime = [
a or b for a, b in zip(images.get('timestamp', None),
images.get('datetime', None))
]
perimage_data = pd.DataFrame({
'anns_per_image': anns_per_image,
'width': image_widths,
'height': image_heights,
'datetime': datetime,
})
area = (perimage_data['width'] * perimage_data['height'])
area_sorted_idxs = area.sort_values().index
img_dsizes = perimage_data.loc[area_sorted_idxs, ['width', 'height']]
img_dsize_tuples = list(map(tuple, img_dsizes.values.tolist()))
dsize_hist = ub.udict(ub.dict_hist(img_dsize_tuples))
median_area_image_size = img_dsize_tuples[len(img_dsize_tuples) // 2]
most_frequent_image_size = '{} x {}'.format(*list(dsize_hist.items())[-1][0])
scalar_stats['median_area_image_size'] = median_area_image_size
scalar_stats['most_frequent_image_size'] = most_frequent_image_size
print(f'scalar_stats = {ub.urepr(scalar_stats, nl=1)}')
# scalar_stats['table_buildtime_seconds'] = stats_timer.elapsed
try:
geospatial_stats(dset, images, perimage_data)
except Exception as ex:
print(f'Failed to build domain (geospatial) stats: ex={ex}')
raise
else:
# TODO: medical stats / other domain stats
print('Built domain (geospatial) stats')
# try:
# # We dont want to require geopandas
# import geopandas as gpd
# _DataFrame = gpd.GeoDataFrame
# except Exception:
_DataFrame = pd.DataFrame
annots = dset.annots()
print('Building stats')
# detections : kwimage.Detections = annots.detections
# boxes : kwimage.Boxes = detections.boxes
# polys : kwimage.PolygonList = detections.data['segmentations']
alt_boxes = []
alt_polys = []
alt_geoms = []
for ann in ub.ProgIter(annots.objs, desc='gather annotation polygons'):
box = kwimage.Box.coerce(ann['bbox'], 'xywh')
alt_boxes.append(box)
try:
poly = kwimage.MultiPolygon.coerce(ann['segmentation'])
geom = poly.to_shapely()
except Exception:
# Masks with linestrings can get misinterpreted, but we can fix
# them by considering pixels as areas instead of points
sseg = ann.get('segmentation', None)
if sseg is not None:
mask = kwimage.Mask.coerce(sseg)
poly = mask.to_multi_polygon(pixels_are='areas')
geom = poly.to_shapely()
else:
# Fallback onto the box if the polygon broken
poly = box.to_polygon()
geom = poly.to_shapely()
alt_polys.append(poly)
alt_geoms.append(geom)
boxes = kwimage.Boxes.concatenate(alt_boxes)
polys = kwimage.PolygonList(alt_polys)
box_width = boxes.width.ravel()
box_height = boxes.height.ravel()
box_canvas_width = np.array(annots.images.get('width'))
box_canvas_height = np.array(annots.images.get('height'))
# geoms = [p.to_shapely() for p in polys]
geoms = alt_geoms
perannot_data = _DataFrame({
'geometry': geoms,
'annot_id': annots.ids,
'image_id': annots.image_id,
'box_rt_area': np.sqrt(boxes.area.ravel()),
'box_width': box_height,
'box_height': box_height,
'rel_box_width': box_width / box_canvas_width,
'rel_box_height': box_height / box_canvas_height,
})
perannot_data['num_vertices'] = perannot_data.geometry.apply(geometry_length)
sorted_box_rt_area = perannot_data['box_rt_area'].sort_values()
mean_box_rt_area_idx = sorted_box_rt_area.index[len(sorted_box_rt_area) // 2]
scalar_stats['median_box_rt_area'] = perannot_data.loc[mean_box_rt_area_idx, 'box_rt_area']
scalar_stats['median_box_dsize'] = tuple(map(float, perannot_data.loc[mean_box_rt_area_idx, ['box_width', 'box_height']]))
try:
perannot_data = polygon_shape_stats(perannot_data)
except Exception as ex:
print(f'ERROR: ex={ex}')
raise
else:
sorted_sseg_rt_area = perannot_data['sseg_rt_area'].sort_values()
mean_sseg_rt_area_idx = sorted_sseg_rt_area.index[len(sorted_sseg_rt_area) // 2]
scalar_stats['median_sseg_rt_area'] = perannot_data.loc[mean_sseg_rt_area_idx, 'sseg_rt_area']
scalar_stats['median_sseg_box_dsize'] = tuple(map(float, perannot_data.loc[mean_box_rt_area_idx, ['box_width', 'box_height']]))
scalar_stats['median_sseg_obox_dsize'] = tuple(map(float, perannot_data.loc[mean_box_rt_area_idx, ['obox_major', 'obox_minor']]))
geometry = perannot_data['geometry']
perannot_data['centroid_x'] = geometry.apply(lambda s: s.centroid.x)
perannot_data['centroid_y'] = geometry.apply(lambda s: s.centroid.y)
perannot_data['rel_centroid_x'] = perannot_data['centroid_x'] / box_canvas_width
perannot_data['rel_centroid_y'] = perannot_data['centroid_y'] / box_canvas_height
_summary_data = ub.udict(perannot_data.to_dict()) - {'geometry'}
_summary_df = pd.DataFrame(_summary_data)
tables_data = {}
tables_data['perannot_data'] = json.loads(_summary_df.to_json(orient='table'))
tables_data['perimage_data'] = json.loads(perimage_data.to_json(orient='table'))
# Data that we don't serialize, but some plots depend on.
nonsaved_data = {
'boxes': boxes,
'polys': polys,
}
dataframes = {
'perannot_data': perannot_data,
'perimage_data': perimage_data,
}
scalar_stats['table_buildtime_seconds'] = stats_timer.elapsed
# if 0:
# import io
# import pandas as pd
# perimage_data = pd.read_json(io.StringIO(json.dumps(tables_data['perimage_data'])), orient='table')
# perannot_data = pd.read_json(io.StringIO(json.dumps(tables_data['perannot_data'])), orient='table')
return scalar_stats, tables_data, nonsaved_data, dataframes
[docs]
class Plots:
"""
Defines plot functions as a class, to make it easier to share common
data and write the functions in a more concise manner. This also
makes it easier to enable / disable plots.
"""
_plot_function_registery = {}
[docs]
@classmethod
def demo(cls, options=None, **kwargs):
"""
Helper for tweaking visualizations
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> self = Plots.demo(timestamps=True)
>>> self.perimage_data
>>> self.perannot_data
"""
import kwcoco
dpath = ub.Path.appdir('kwcoco/demo/vis_stats').ensuredir()
src = kwcoco.CocoDataset.demo('vidshapes8', **kwargs)
plots = prep_plots(src, plots_dpath=dpath, options=options)
return plots
def __init__(self, plots_dpath, tables_data, nonsaved_data, options=None, dpi=300):
import kwplot
import pandas as pd
import json
import io
import kwutil
# kwplot 0.5.3 doesnt provide util_seaborn access via lazy loading
# work around this
import kwplot.util_seaborn
self.plots_dpath = plots_dpath
self.tables_data = tables_data
self.nonsaved_data = nonsaved_data
self.dpi = dpi
self.options = kwutil.Yaml.coerce(options)
sns = kwplot.autosns(verbose=3)
if nonsaved_data is not None:
self.polys = nonsaved_data['polys']
boxes = nonsaved_data['boxes']
self.annot_max_x = boxes.br_x.max()
self.annot_max_y = boxes.br_y.max()
self.perannot_data = pd.read_json(io.StringIO(json.dumps(tables_data['perannot_data'])), orient='table')
self.perimage_data = pd.read_json(io.StringIO(json.dumps(tables_data['perimage_data'])), orient='table')
self.max_anns_per_image = self.perimage_data['anns_per_image'].max()
self.plot_functions = {}
figman = kwplot.FigureManager(
dpath=plots_dpath,
dpi=dpi,
verbose=1
)
# define label mappings for humans
figman.labels.add_mapping({
'pd_datetime': 'Datetime',
# 'collection_size': '',
'num_vertices': 'Num Polygon Vertices',
'centroid_x': 'Polygon Centroid X',
'centroid_y': 'Polygon Centroid Y',
'obox_major': 'OBox Major Axes Length',
'obox_minor': 'OBox Minor Axes Length',
'sseg_rt_area': 'Polygon sqrt(Area)'
})
self.figman = figman
self.sns = sns
self.kwplot = kwplot
self.perimage_data = self.perimage_data
# Might want to modify to make this nicer for interactive / reloading
# use cases
for name, func in self.__class__.available_plot_funcs().items():
self.register(func)
[docs]
def resolve_options(self, defaults, plot_name):
defaults = ub.udict(defaults)
resolved = defaults.copy()
if self.options is not None:
import copy
options = ub.udict(copy.deepcopy(self.options))
if plot_name in options:
options.update(options[plot_name])
resolved = resolved | (options & resolved)
return resolved
[docs]
@classmethod
def available_plot_funcs(cls):
import inspect
unbound_methods_items = inspect.getmembers(BuiltinPlots, predicate=inspect.isfunction)
unbound_methods = dict(unbound_methods_items)
return unbound_methods
def __getitem__(self, key):
return self.plot_functions[key]
[docs]
def register(self, func):
key = func.__name__
if key in self.plot_functions:
raise AssertionError('duplicate plot name')
self.plot_functions[key] = func
[docs]
def run(self, plot_keys):
from kwcoco.util.util_rich import rich_print
import kwutil
pman = kwutil.util_progress.ProgressManager()
with pman:
# plot_func_keys = [
# # 'images_over_time',
# 'images_timeofday_distribution',
# ]
if plot_keys is None:
plot_keys = list(self.plot_functions.keys())
for key in pman.ProgIter(plot_keys, desc='plot'):
func = self.plot_functions[key]
try:
func(self)
except Exception as ex:
rich_print(f'[red] ERROR: in {func}')
rich_print(f'ex = {ub.urepr(ex, nl=1)}')
import traceback
traceback.print_exc()
if 0:
raise
[docs]
class BuiltinPlots:
"""
A class that ONLY contains methods that will produce a plot.
This is used to register them with :class:`Plots`.
Note:
The "self" argument is an instance of Plots, not this class.
It would be nice to find a better way to organize this.
"""
[docs]
def polygon_centroid_absolute_distribution(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['polygon_centroid_absolute_distribution'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
self.sns.kdeplot(data=self.perannot_data, x='centroid_x', y='centroid_y', ax=ax)
self.sns.scatterplot(data=self.perannot_data, x='centroid_x', y='centroid_y', ax=ax, hue='sseg_rt_area', alpha=0.8)
ax.set_aspect('equal')
ax.set_title('Polygon Absolute Centroid Positions')
#ax.set_xlim(0, max_width)
#ax.set_ylim(0, max_height)
ax.set_xlim(0, ax.get_xlim()[1])
ax.set_ylim(0, ax.get_ylim()[1])
self.figman.labels.relabel(ax)
ax.set_aspect('equal')
ax.invert_yaxis()
self.figman.finalize('polygon_centroid_absolute_distribution.png')
[docs]
def polygon_centroid_relative_distribution(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['polygon_centroid_relative_distribution'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
self.sns.kdeplot(data=self.perannot_data, x='rel_centroid_x', y='rel_centroid_y', ax=ax)
self.sns.scatterplot(data=self.perannot_data, x='rel_centroid_x', y='rel_centroid_y', ax=ax, hue='sseg_rt_area', alpha=0.8)
ax.set_aspect('equal')
ax.set_title('Polygon Relative Centroid Positions')
#ax.set_xlim(0, max_width)
#ax.set_ylim(0, max_height)
ax.set_xlim(0, ax.get_xlim()[1])
ax.set_ylim(0, ax.get_ylim()[1])
ax.set_xlabel('Polygon X Centroid')
ax.set_ylabel('Polygon Y Centroid')
self.figman.labels.relabel(ax)
ax.set_aspect('equal')
ax.invert_yaxis()
self.figman.finalize('polygon_centroid_relative_distribution.png')
[docs]
def polygon_centroid_absolute_distribution_jointplot(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo(timestamps=True)
>>> self['polygon_centroid_absolute_distribution_jointplot'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
with self.kwplot.util_seaborn.MonkeyPatchPyPlotFigureContext(self.figman.fig):
self.figman.fig.clf()
marginal_kws = dict()
joint_kws = dict()
# marginal_kws['log_scale'] = True
# joint_kws['log_scale'] = True
jointplot_kws = dict(
joint_kws=joint_kws,
marginal_kws=marginal_kws,
kind='hist',
)
self.sns.jointplot(data=self.perannot_data, x='centroid_x', y='centroid_y', **jointplot_kws)
ax = self.figman.fig.gca()
ax.set_title('Polygon Absolute Centroid Positions')
#ax.set_xlim(0, max_width)
#ax.set_ylim(0, max_height)
ax.set_xlim(0, ax.get_xlim()[1])
ax.set_ylim(0, ax.get_ylim()[1])
self.figman.labels.relabel(ax)
# ax.set_aspect('equal')
ax.invert_yaxis()
self.figman.finalize('polygon_centroid_absolute_distribution_jointplot.png')
[docs]
def polygon_centroid_relative_distribution_jointplot(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo(timestamps=True)
>>> self['polygon_centroid_relative_distribution_jointplot'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
# self.sns.kdeplot(data=self.perannot_data, x='rel_centroid_x', y='rel_centroid_y', ax=ax)
# self.sns.scatterplot(data=self.perannot_data, x='rel_centroid_x', y='rel_centroid_y', ax=ax, hue='sseg_rt_area', alpha=0.8)
ax = self.figman.figure(fnum=1, doclf=True).gca()
with self.kwplot.util_seaborn.MonkeyPatchPyPlotFigureContext(self.figman.fig):
self.figman.fig.clf()
marginal_kws = dict()
joint_kws = dict()
# marginal_kws['log_scale'] = True
# joint_kws['log_scale'] = True
jointplot_kws = dict(
joint_kws=joint_kws,
marginal_kws=marginal_kws,
kind='hist',
)
self.sns.jointplot(data=self.perannot_data, x='rel_centroid_x', y='rel_centroid_y', **jointplot_kws)
ax = self.figman.fig.gca()
# self.sns.kdeplot(data=self.perannot_data, x='obox_major', y='obox_minor', ax=ax)
ax.set_title('Polygon Relative Centroid Positions')
ax.set_xlim(0, ax.get_xlim()[1])
ax.set_ylim(0, ax.get_ylim()[1])
ax.set_xlabel('Polygon X Centroid')
ax.set_ylabel('Polygon Y Centroid')
self.figman.labels.relabel(ax)
# ax.set_aspect('equal')
ax.invert_yaxis()
self.figman.finalize('polygon_centroid_relative_distribution_jointplot.png')
[docs]
def image_size_histogram(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo(timestamps=True, image_size='random', num_frames=100)
>>> self['image_size_histogram'](self)
"""
import kwplot
# self.figman.figure(fnum=1, doclf=True).gca()
img_dsizes = [f'{w}✕{h}' for w, h in zip(self.perimage_data['width'], self.perimage_data['height'])]
self.perimage_data['img_dsizes'] = img_dsizes
# self.sns.histplot(data=perimage_data, x='img_dsizes', ax=ax)
data = self.perimage_data
x = 'img_dsizes'
snskw = dict(binwidth=1, discrete=True)
ax_top, ax_bottom, split_y = kwplot.util_seaborn.histplot_splity(
data=data, x=x, **snskw)
ax_bottom.set_xlabel('Image Width ✕ Height')
ax_bottom.set_ylabel('Number of Images')
ax_top.set_title('Image Size Histogram')
self.figman.finalize('image_size_histogram.png', fig=ax_top.figure)
[docs]
def image_size_scatter(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo(timestamps=True, image_size='random', num_frames=100)
>>> self['image_size_scatter'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
# self.sns.kdeplot(data=self.perimage_data, x='width', y='height', ax=ax)
# self.sns.stripplot(data=self.perimage_data, x='width', y='height', ax=ax)
self.sns.kdeplot(data=self.perimage_data, x='width', y='height', ax=ax)
self.sns.scatterplot(data=self.perimage_data, x='width', y='height', ax=ax)
# self.sns.swarmplot(data=self.perimage_data, x='width', y='height', ax=ax)
ax.set_title('Image Size Distribution')
# ax.set_aspect('equal')
ax.set_ylabel('Image Height')
ax.set_xlabel('Image Width')
# ax.set_xlim(0, ax.get_xlim()[1])
# ax.set_ylim(0, ax.get_ylim()[1])
self.figman.labels.relabel(ax)
self.figman.finalize('image_size_scatter.png')
[docs]
def obox_size_distribution(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['obox_size_distribution'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
self.sns.kdeplot(data=self.perannot_data, x='obox_major', y='obox_minor', ax=ax)
self.sns.scatterplot(data=self.perannot_data, x='obox_major', y='obox_minor', ax=ax)
#ax.set_xscale('log')
#ax.set_yscale('log')
ax.set_title('Oriented Bounding Box Sizes')
ax.set_aspect('equal')
ax.set_xlim(0, ax.get_xlim()[1])
ax.set_ylim(0, ax.get_ylim()[1])
self.figman.labels.relabel(ax)
self.figman.finalize('obox_size_distribution.png')
[docs]
def obox_size_distribution_jointplot(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['obox_size_distribution_jointplot'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
with self.kwplot.util_seaborn.MonkeyPatchPyPlotFigureContext(self.figman.fig):
self.figman.fig.clf()
marginal_kws = dict()
joint_kws = dict()
# marginal_kws['log_scale'] = True
# joint_kws['log_scale'] = True
jointplot_kws = dict(
joint_kws=joint_kws,
marginal_kws=marginal_kws,
kind='hist',
)
self.sns.jointplot(data=self.perannot_data, x='obox_major', y='obox_minor', **jointplot_kws)
ax = self.figman.fig.gca()
# self.sns.kdeplot(data=self.perannot_data, x='obox_major', y='obox_minor', ax=ax)
#ax.set_xscale('log')
#ax.set_yscale('log')
ax.set_title('Oriented Bounding Box Sizes')
# ax.set_aspect('equal')
ax.set_xlim(0, ax.get_xlim()[1])
ax.set_ylim(0, ax.get_ylim()[1])
self.figman.labels.relabel(ax)
self.figman.finalize('obox_size_distribution_jointplot.png')
[docs]
def obox_size_distribution_logscale(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['obox_size_distribution_logscale'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
# self.sns.kdeplot(data=self.perannot_data, x='obox_major', y='obox_minor', ax=ax)
# self.sns.scatterplot(data=self.perannot_data, x='obox_major', y='obox_minor', ax=ax)
with self.kwplot.util_seaborn.MonkeyPatchPyPlotFigureContext(self.figman.fig):
# TODO: logscale on boxes
self.figman.fig.clf()
marginal_kws = dict()
joint_kws = dict()
marginal_kws['log_scale'] = True
joint_kws['log_scale'] = True
jointplot_kws = dict(
joint_kws=joint_kws,
marginal_kws=marginal_kws,
kind='hist',
# kind='hex',
# kind='scatter',
# hue='num_vertices'
)
self.sns.jointplot(data=self.perannot_data, x='obox_major', y='obox_minor', **jointplot_kws)
ax = self.figman.fig.gca()
ax.set_xscale('symlog')
ax.set_yscale('symlog')
ax.set_title('Oriented Bounding Box Sizes')
# ax.set_aspect('equal')
# TODO: set better min
minx = self.perannot_data['obox_major'].min()
miny = self.perannot_data['obox_minor'].min()
ax.set_xlim(minx, ax.get_xlim()[1])
ax.set_ylim(miny, ax.get_ylim()[1])
self.figman.labels.relabel(ax)
self.figman.finalize('obox_size_distribution_logscale.png')
[docs]
def polygon_area_vs_num_verts(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['polygon_area_vs_num_verts'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
self.sns.kdeplot(data=self.perannot_data, x='sseg_rt_area', y='num_vertices', ax=ax)
self.sns.scatterplot(data=self.perannot_data, x='sseg_rt_area', y='num_vertices', ax=ax)
# self.sns.jointplot(data=self.perannot_data, x='sseg_rt_area', y='num_vertices')
self.figman.labels.relabel(ax)
ax.set_xlim(0, ax.get_xlim()[1])
ax.set_ylim(0, ax.get_ylim()[1])
ax.set_title('Polygon Area vs Num Vertices')
self.figman.finalize('polygon_area_vs_num_verts.png')
[docs]
def polygon_area_vs_num_verts_jointplot(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['polygon_area_vs_num_verts_jointplot'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
with self.kwplot.util_seaborn.MonkeyPatchPyPlotFigureContext(self.figman.fig):
self.figman.fig.clf()
marginal_kws = dict()
joint_kws = dict()
# marginal_kws['log_scale'] = True
# joint_kws['log_scale'] = True
jointplot_kws = dict(
joint_kws=joint_kws,
marginal_kws=marginal_kws,
kind='hist',
# kind='scatter',
)
self.sns.jointplot(data=self.perannot_data, x='sseg_rt_area', y='num_vertices', **jointplot_kws)
ax = self.figman.fig.gca()
# self.sns.kdeplot(data=self.perannot_data, x='sseg_rt_area', y='num_vertices', ax=ax)
self.figman.labels.relabel(ax)
minx = self.perannot_data['sseg_rt_area'].min()
miny = self.perannot_data['num_vertices'].min()
ax.set_xlim(minx, ax.get_xlim()[1])
ax.set_ylim(miny, ax.get_ylim()[1])
# ax.set_xlim(0, ax.get_xlim()[1])
# ax.set_ylim(0, ax.get_ylim()[1])
ax.set_title('Polygon Area vs Num Vertices')
self.figman.finalize('polygon_area_vs_num_verts_jointplot.png')
[docs]
def polygon_area_vs_num_verts_jointplot_logscale(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo(timestamps=True)
>>> self['polygon_area_vs_num_verts_jointplot_logscale'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
x = 'sseg_rt_area'
y = 'num_vertices'
# Hack for logscale to work
perannot_data = self.perannot_data.copy()
perannot_data[perannot_data[y] == 0] = float('nan')
perannot_data[perannot_data[x] == 0] = float('nan')
maxx = perannot_data[x].max()
maxy = perannot_data[y].max()
minx = perannot_data[x].min()
miny = perannot_data[y].min()
with self.kwplot.util_seaborn.MonkeyPatchPyPlotFigureContext(self.figman.fig):
self.figman.fig.clf()
marginal_kws = dict()
joint_kws = dict()
marginal_kws['log_scale'] = True
joint_kws['log_scale'] = True
jointplot_kws = dict(
joint_kws=joint_kws,
marginal_kws=marginal_kws,
kind='hist',
# kind='scatter',
)
self.sns.jointplot(data=perannot_data, x=x, y=y, **jointplot_kws)
ax = self.figman.fig.gca()
self.figman.labels.relabel(ax)
ax.set_xlim(minx, maxx)
ax.set_ylim(miny, maxy)
ax.set_title('Polygon Area vs Num Vertices')
self.figman.finalize('polygon_area_vs_num_verts_jointplot_logscale.png')
[docs]
def polygon_area_histogram_logscale(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['polygon_area_histogram_logscale'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
self.sns.histplot(data=self.perannot_data, x='sseg_rt_area', ax=ax, kde=True, log_scale=True)
# self.sns.histplot(data=self.perannot_data, x='sseg_rt_area', ax=ax, kde=True)
self.figman.labels.relabel(ax)
ax.set_title('Polygon sqrt(Area) Histogram')
# TODO: better min. Figman needs a good way of helping with this.
ax.set_xlim(10, ax.get_xlim()[1])
ax.set_ylabel('Number of Annotations')
# ax.set_yscale('symlog')
# ax.set_xscale('symlog')
self.figman.finalize('polygon_area_histogram_logscale.png')
[docs]
def polygon_area_histogram(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['polygon_area_histogram'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
# self.sns.histplot(data=self.perannot_data, x='sseg_rt_area', ax=ax, kde=True, binwidth=32, log_scale=True)
self.sns.histplot(data=self.perannot_data, x='sseg_rt_area', ax=ax, kde=True)
self.figman.labels.relabel(ax)
ax.set_title('Polygon sqrt(Area) Histogram')
ax.set_xlim(10, ax.get_xlim()[1])
ax.set_ylabel('Number of Annotations')
# ax.set_yscale('symlog')
# ax.set_xscale('symlog')
self.figman.finalize('polygon_area_histogram.png')
[docs]
def polygon_area_histogram_splity(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['polygon_area_histogram_splity'](self)
"""
# ax = self.figman.figure(fnum=1, doclf=True).gca()
# self.sns.histplot(data=self.perannot_data, x='sseg_rt_area', ax=ax, kde=True)
# self.figman.labels.relabel(ax)
# ax.set_title('Polygon sqrt(Area) Histogram')
# ax.set_xlim(0, ax.get_xlim()[1])
# ax.set_ylabel('Number of Annotations')
# ax.set_yscale('symlog')
# self.figman.finalize('polygon_area_histogram.png')
split_y = 'auto'
snskw = dict(binwidth=50, discrete=False, kde=True)
ax_top, ax_bottom, split_y = self.kwplot.util_seaborn.histplot_splity(
data=self.perannot_data, x='sseg_rt_area', split_y=split_y,
**snskw)
ax = ax_top
fig = ax.figure
ax.set_yscale('linear')
ax_bottom.set_ylabel('Number of Annotations')
ax_top.set_ylabel('')
ax_top.set_title('Polygon sqrt(Area) Histogram')
# ax_bottom.set_xlim(0 - 0.5, self.max_anns_per_image + 1.5)
ax.set_xlim(0, ax.get_xlim()[1])
ax_top.set_ylim(bottom=split_y) # those limits are fake
ax_bottom.set_ylim(0, split_y)
# self.figman.labels.force_integer_ticks(axis='x', method='ticker', ax=ax_bottom)
# self.figman.labels.force_integer_ticks(axis='x', method='maxn', ax=ax_bottom)
self.figman.finalize('polygon_area_histogram_splity.png', fig=fig)
[docs]
def polygon_num_vertices_histogram(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['polygon_num_vertices_histogram'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
self.sns.histplot(data=self.perannot_data, x='num_vertices', ax=ax)
ax.set_title('Polygon Number of Vertices Histogram')
ax.set_ylabel('Number of Annotations')
ax.set_xlim(0, ax.get_xlim()[1])
ax.set_yscale('linear')
self.figman.labels.relabel(ax)
self.figman.finalize('polygon_num_vertices_histogram.png')
[docs]
def anns_per_image_histogram(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['anns_per_image_histogram'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
self.sns.histplot(data=self.perimage_data, x='anns_per_image', ax=ax, binwidth=1, discrete=True)
ax.set_yscale('linear')
ax.set_xlabel('Number of Annotations')
ax.set_ylabel('Number of Images')
ax.set_title('Number of Annotations per Image')
ax.set_xlim(0 - 0.5, self.max_anns_per_image + 1.5)
self.figman.labels.relabel(ax)
# ax.set_yscale('symlog', linthresh=10)
self.figman.labels.force_integer_xticks()
self.figman.finalize('anns_per_image_histogram.png')
[docs]
def anns_per_image_histogram_splity(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['anns_per_image_histogram_splity'](self)
"""
# split_y = 'auto'
default_options = ub.udict({
'split_y': 'auto',
})
options = self.resolve_options(default_options, 'anns_per_image_histogram_splity')
split_y = options['split_y']
snskw = dict(binwidth=1, discrete=True)
ax_top, ax_bottom, split_y = self.kwplot.util_seaborn.histplot_splity(
data=self.perimage_data, x='anns_per_image', split_y=split_y,
**snskw
)
ax = ax_top
fig = ax.figure
ax.set_yscale('linear')
ax_bottom.set_xlabel('Number of Annotations')
ax_bottom.set_ylabel('Number of Images')
ax_top.set_ylabel('')
ax_top.set_title('Number of Annotations per Image')
ax_bottom.set_xlim(0 - 0.5, self.max_anns_per_image + 1.5)
ax_top.set_ylim(bottom=split_y) # those limits are fake
ax_bottom.set_ylim(0, split_y)
# self.figman.labels.force_integer_ticks(axis='x', method='ticker', ax=ax_bottom)
self.figman.labels.force_integer_ticks(axis='x', method='maxn', ax=ax_bottom)
self.figman.finalize('anns_per_image_histogram_splity.png', fig=fig)
[docs]
def anns_per_image_histogram_ge1(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo()
>>> self['anns_per_image_histogram_ge1'](self)
"""
ax = self.figman.figure(fnum=1, doclf=True).gca()
perimage_data = self.perimage_data
perimage_ge1_data = perimage_data[perimage_data['anns_per_image'] >= 1]
self.sns.histplot(data=perimage_ge1_data, x='anns_per_image', ax=ax, binwidth=1, discrete=True)
ax.set_yscale('linear')
ax.set_xlabel('Number of Annotations')
ax.set_ylabel('Number of Images')
ax.set_title('Number of Annotations per Image\n(with at least 1 annotation)')
self.figman.labels.relabel(ax)
ax.set_xlim(1 - 0.5, self.max_anns_per_image + 0.5)
# self.figman.labels.force_integer_ticks(axis='x', method='maxn', ax=ax)
self.figman.labels.force_integer_ticks(axis='x', method='ticker', ax=ax, hack_labels=0)
# ax.set_xticks(ax.get_xticks().astype(int))
# ax.set_yscale('symlog', linthresh=10)
self.figman.finalize('anns_per_image_histogram_ge1.png')
[docs]
def images_over_time(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo(timestamps=True)
>>> self['images_over_time'](self)
"""
import pandas as pd
import numpy as np
img_df = self.perimage_data.sort_values('datetime')
img_df['pd_datetime'] = pd.to_datetime(img_df.datetime)
img_df['collection_size'] = np.arange(1, len(img_df) + 1)
ax = self.figman.figure(fnum=1, doclf=True).gca()
self.sns.histplot(data=img_df, x='pd_datetime', ax=ax, cumulative=True)
# self.sns.lineplot(data=img_df, x='pd_datetime', y='collection_size')
ax.set_title('Images collected over time')
ax.set_xlabel('Datetime')
ax.set_ylabel('Number of Images Collected')
self.figman.finalize('images_over_time.png')
[docs]
def images_timeofday_distribution(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> self = Plots.demo(timestamps=True)
>>> self['images_timeofday_distribution'](self)
"""
import pandas as pd
import numpy as np
import kwutil
import kwimage
import kwplot
img_df = self.perimage_data.sort_values('datetime')
img_df['pd_datetime'] = pd.to_datetime(img_df.datetime)
img_df['collection_size'] = np.arange(1, len(img_df) + 1)
datetimes = [kwutil.datetime.coerce(x) for x in img_df['datetime']]
# img_df['timestamp'] = [x.timestamp() for x in datetimes]
# img_df['date'] = [x.date() for x in datetimes]
# img_df['year_month'] = [x.strftime('%Y-%m') for x in datetimes]
# img_df['month'] = [x.strftime('%m') for x in datetimes]
img_df['time'] = [x.time() if not pd.isnull(x) else None for x in datetimes]
img_df['day_of_year'] = [x.timetuple().tm_yday if not pd.isnull(x) else None for x in datetimes]
img_df['hour_of_day'] = [None if z is None else z.hour + z.minute / 60 + z.second / 3600 for z in img_df['time']]
self.snskw = {}
has_sunlight = 'sunlight' in img_df.columns
if has_sunlight and not img_df['sunlight'].isna().all():
palette = self.sns.color_palette("flare", n_colors=4, as_cmap=True).reversed()
self.snskw['hue'] = 'sunlight'
self.snskw['palette'] = palette
ax = self.figman.figure(fnum=1, doclf=True).gca()
# self.sns.histplot(data=img_df, x='month', ax=ax)
# self.sns.kdeplot(data=img_df, x='day_of_year', y='hour_of_day')
# self.sns.scatterplot(data=img_df, x='day_of_year', y='hour_of_day', hue='sunlight_values')
self.sns.scatterplot(data=img_df, x='day_of_year', y='hour_of_day')
self.sns.scatterplot(data=img_df, x='day_of_year', y='hour_of_day', **self.snskw, legend=False)
# self.sns.kdeplot(data=img_df, x='hour_of_day', y='day_of_year')
if has_sunlight:
kwplot.phantom_legend({
'Night': kwimage.Color.coerce(palette.colors[0]).as255(),
'Day': kwimage.Color.coerce(palette.colors[-1]).as255(),
'nan': 'blue',
}, mode='circle')
ax.set_title('Time Captured')
ax.set_xlabel('Day of Year')
ax.set_ylabel('Hour of Day')
self.figman.finalize('images_timeofday_distribution.png')
[docs]
def all_polygons(self):
"""
Example:
>>> # xdoctest: +REQUIRES(module:kwutil)
>>> from kwcoco.cli.coco_plot_stats import * # NOQA
>>> options = '''
all_polygons:
facecolor: red
edgecolor: green
'''
>>> self = Plots.demo(timestamps=True, options=options)
>>> self['all_polygons'](self)
Ignore:
https://drive.google.com/file/d/1mUzJw4QrDfxWqqCsPZ_C7QWcQbfR_IBb/view
~/code/kwimage/kwimage/_im_color_data.py
"""
import kwimage
default_options = ub.udict({
'edgecolor': 'kitware_darkgray',
'facecolor': '#b1dfaa',
})
options = self.resolve_options(default_options, 'all_polygons')
ax = self.figman.figure(fnum=1, doclf=True).gca()
# edgecolor = kwimage.Color.coerce('kitware_darkblue').as01()
# facecolor = kwimage.Color.coerce('kitware_green').as01()
# edgecolor = kwimage.Color.coerce('white').as01()
# facecolor = kwimage.Color.coerce('kitware_darkblue').as01()
# edgecolor = kwimage.Color.coerce('almost black').as01()
edgecolor = kwimage.Color.coerce(options['edgecolor']).as01()
# facecolor = kwimage.Color.coerce('kitware_green').as01()
#ad900d
facecolor = kwimage.Color.coerce(options['facecolor']).as01()
self.polys.draw(alpha=0.5, edgecolor=edgecolor, facecolor=facecolor)
ax.set_xlabel('Image X Coordinate')
ax.set_ylabel('Image Y Coordinate')
ax.set_title(f'All {len(self.polys)} Polygons')
ax.set_aspect('equal')
ax.set_xlim(0, self.annot_max_x)
ax.set_ylim(0, self.annot_max_y)
self.figman.labels.relabel(ax)
ax.set_ylim(0, self.annot_max_y) # not sure why this needs to be after the relabel, should ideally fix that.
ax.invert_yaxis()
self.figman.finalize('all_polygons.png', tight_layout=0) # tight layout seems to cause issues here
[docs]
def polygon_shape_stats(df):
"""
Compute shape statistics about a geopandas dataframe (assume UTM CRS)
TODO:
- [ ] Use geopandas if available, but fallback to non-geopandas logic
"""
import numpy as np
import kwimage
geometry = df['geometry']
def hull_area(s):
try:
return s.convex_hull.area
except Exception:
return np.nan
def sseg_area(s):
try:
return s.area
except Exception:
return np.nan
df['hull_rt_area'] = np.sqrt(geometry.apply(hull_area))
df['sseg_rt_area'] = np.sqrt(geometry.apply(sseg_area))
obox_majors = []
obox_minors = []
for s in df.geometry:
try:
obox = kwimage.MultiPolygon.from_shapely(s).oriented_bounding_box()
wh = obox.extent
obox_majors.append(max(wh))
obox_minors.append(min(wh))
except Exception:
obox_majors.append(np.nan)
obox_minors.append(np.nan)
df['obox_major'] = np.array(obox_majors)
df['obox_minor'] = np.array(obox_minors)
df['major_obox_ratio'] = df['obox_major'] / df['obox_minor']
# df['ch_aspect_ratio'] =
# df['isoperimetric_quotient'] = df.geometry.apply(shapestats.ipq)
# df['boundary_amplitude'] = df.geometry.apply(shapestats.compactness.boundary_amplitude)
# df['eig_seitzinger'] = df.geometry.apply(shapestats.compactness.eig_seitzinger)
return df
[docs]
def geometry_flatten(geom):
"""
References:
https://gis.stackexchange.com/questions/119453/count-the-number-of-points-in-a-multipolygon-in-shapely
"""
if hasattr(geom, 'geoms'): # Multi<Type> / GeometryCollection
for g in geom.geoms:
yield from geometry_flatten(g)
elif hasattr(geom, 'interiors'): # Polygon
yield geom.exterior
yield from geom.interiors
else: # Point / LineString
yield geom
[docs]
def geometry_length(geom):
return sum(len(g.coords) for g in geometry_flatten(geom))
if __name__ == '__main__':
r"""
CommandLine:
LINE_PROFILE=1 python -m kwcoco.cli.coco_plot_stats $HOME/data/dvc-repos/kwcoco/data.kwcoco.json \
--dst_fpath $HOME/code/kwcoco/coco_annot_stats/stats.json \
--dst_dpath $HOME/code/kwcoco/coco_annot_stats
"""
__cli__.main()