Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions pyodi/apps/train_config/train_config_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@
from pathlib import Path
from shutil import copyfile
from tempfile import TemporaryDirectory
from typing import Any, Dict, Optional, Tuple
from typing import Any, Dict, Optional, Tuple, Union

import numpy as np
import pandas as pd
from loguru import logger

from pyodi.core.anchor_generator import AnchorGenerator
Expand Down Expand Up @@ -120,23 +121,27 @@ def load_anchor_config_file(anchor_config_file: str) -> Dict[str, Any]:

@logger.catch
def train_config_evaluation(
ground_truth_file: str,
ground_truth_file: Union[str, pd.DataFrame],
anchor_config: str,
input_size: Tuple[int, int] = (1280, 720),
show: bool = True,
output: Optional[str] = None,
output_size: Tuple[int, int] = (1600, 900),
keep_ratio: bool = False,
) -> None:
"""Evaluates the fitness between `ground_truth_file` and `anchor_config_file`.

Args:
ground_truth_file: Path to COCO ground truth file.
ground_truth_file: Path to COCO ground truth file or coco df_annotations DataFrame
to be used from
[`pyodi train-config generation`][pyodi.apps.train_config.train_config_generation.train_config_generation]
anchor_config: Path to MMDetection-like `anchor_generator` section. It can also be a
dictionary with the required data.
input_size: Model image input size. Defaults to (1333, 800).
show: Show results or not. Defaults to True.
output: Output file where results are saved. Defaults to None.
output: Output directory where results going to be saved. Defaults to None.
output_size: Size of saved images. Defaults to (1600, 900).
keep_ratio: Whether to keep the aspect ratio or not. Defaults to False.

Examples:
```python
Expand All @@ -150,20 +155,25 @@ def train_config_evaluation(
```
"""
if output is not None:
output = str(Path(output) / Path(ground_truth_file).stem)
Path(output).mkdir(parents=True, exist_ok=True)

coco_ground_truth = load_ground_truth_file(ground_truth_file)
if isinstance(ground_truth_file, str):
coco_ground_truth = load_ground_truth_file(ground_truth_file)

df_images, df_annotations = coco_ground_truth_to_dfs(coco_ground_truth)
df_images, df_annotations = coco_ground_truth_to_dfs(coco_ground_truth)

df_annotations = join_annotations_with_image_sizes(df_annotations, df_images)
df_annotations = join_annotations_with_image_sizes(df_annotations, df_images)

df_annotations = filter_zero_area_bboxes(df_annotations)
df_annotations = filter_zero_area_bboxes(df_annotations)

df_annotations = scale_bbox_dimensions(df_annotations, input_size=input_size)
df_annotations = scale_bbox_dimensions(
df_annotations, input_size=input_size, keep_ratio=keep_ratio
)

df_annotations = get_scale_and_ratio(df_annotations, prefix="scaled")
Comment on lines +161 to +173
Copy link
Copy Markdown
Contributor Author

@mmeendez8 mmeendez8 Apr 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines are repeated in train-config generation and train-config evaluation...
We might move this into a new function but not sure about where and if it is really necessary


df_annotations = get_scale_and_ratio(df_annotations, prefix="scaled")
else:
df_annotations = ground_truth_file

df_annotations["log_scaled_ratio"] = np.log(df_annotations["scaled_ratio"])

Expand Down
23 changes: 18 additions & 5 deletions pyodi/apps/train_config/train_config_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
import numpy as np
from loguru import logger

from pyodi.apps.train_config.train_config_evaluation import train_config_evaluation
from pyodi.core.anchor_generator import AnchorGenerator
from pyodi.core.boxes import (
filter_zero_area_bboxes,
Expand Down Expand Up @@ -131,7 +132,8 @@ def train_config_generation(
output: Optional[str] = None,
output_size: Tuple[int, int] = (1600, 900),
keep_ratio: bool = False,
) -> AnchorGenerator:
evaluate: bool = True,
) -> None:
"""Computes optimal anchors for a given COCO dataset based on iou clustering.

Args:
Expand All @@ -143,15 +145,17 @@ def train_config_generation(
base_sizes: The basic sizes of anchors in multiple levels.
If None is given, strides will be used as base_sizes.
show: Show results or not. Defaults to True.
output: Output file where results are saved. Defaults to None.
output: Output directory where results going to be saved. Defaults to None.
output_size: Size of saved images. Defaults to (1600, 900).
keep_ratio: Whether to keep the aspect ratio or not. Defaults to False.
evaluate: Whether to evaluate or not the anchors. Check
[`pyodi train-config evaluation`][pyodi.apps.train_config.train_config_evaluation.train_config_evaluation]
for more information.

Returns:
Anchor generator instance.
"""
if output is not None:
output = str(Path(output) / Path(ground_truth_file).stem)
Path(output).mkdir(parents=True, exist_ok=True)

coco_ground_truth = load_ground_truth_file(ground_truth_file)
Expand Down Expand Up @@ -216,9 +220,18 @@ def train_config_generation(
title="COCO_anchor_generation",
)

if evaluate:
anchor_config = dict(anchor_generator=anchor_generator.to_dict())
train_config_evaluation(
ground_truth_file=df_annotations,
anchor_config=anchor_config, # type: ignore
input_size=input_size,
show=show,
output=output,
output_size=output_size,
)

if output:
output_file = Path(output) / "anchor_config.py"
with open(output_file, "w") as f:
f.write(anchor_generator.to_string())

return anchor_generator
63 changes: 0 additions & 63 deletions pyodi/core/anchor_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,6 @@ def __init__(
self.center_offset = center_offset
self.base_anchors = self.gen_base_anchors()

@property
def num_base_anchors(self) -> List[int]:
"""Returns the number of anchors per level.

Returns:
List with number of anchors per level.

"""
return [base_anchors.size(0) for base_anchors in self.base_anchors]

@property
def num_levels(self) -> int:
"""Returns the number of levels.
Expand Down Expand Up @@ -264,59 +254,6 @@ def single_level_grid_anchors(
# then (0, 1), (0, 2), ...
return all_anchors

# todo: this function depends on the following commented function
# def valid_flags(
# self,
# featmap_sizes: List[Tuple[int, int]],
# pad_shape: Tuple[int, int],
# device: str = "cuda",
# ) -> List:
# """Generate valid flags of anchors in multiple feature levels
#
# Args:
# featmap_sizes: List of feature map sizes in multiple feature levels.
# pad_shape: The padded shape of the image.
# device: Device where the anchors will be put on. Defaults to "cuda".
#
# Returns:
# Valid flags of anchors in multiple levels (List[torch.Tensor]).
# """
# assert self.num_levels == len(featmap_sizes)
# multi_level_flags = []
# for i in range(self.num_levels):
# anchor_stride = self.strides[i]
# feat_h, feat_w = featmap_sizes[i]
# h, w = pad_shape[:2]
# valid_feat_h = min(int(np.ceil(h / anchor_stride)), feat_h)
# valid_feat_w = min(int(np.ceil(w / anchor_stride)), feat_w)
# flags = self.single_level_valid_flags(
# (feat_h, feat_w),
# (valid_feat_h, valid_feat_w),
# self.num_base_anchors[i],
# device=device,
# )
# multi_level_flags.append(flags)
# return multi_level_flags

# todo: update with numpy if necessary
# def single_level_valid_flags(
# self, featmap_size, valid_size, num_base_anchors, device="cuda"
# ):
# feat_h, feat_w = featmap_size
# valid_h, valid_w = valid_size
# assert valid_h <= feat_h and valid_w <= feat_w
# valid_x = torch.zeros(feat_w, dtype=torch.uint8, device=device)
# valid_y = torch.zeros(feat_h, dtype=torch.uint8, device=device)
# valid_x[:valid_w] = 1
# valid_y[:valid_h] = 1
# valid_xx, valid_yy = self._meshgrid(valid_x, valid_y)
# valid = valid_xx & valid_yy
# valid = (
# valid[:, None]
# .expand(valid.size(0), num_base_anchors).contiguous().view(-1)
# )
# return valid

def __repr__(self) -> str:
indent_str = " "
repr_str = self.__class__.__name__ + "(\n"
Expand Down