Skip to content

Commit

Permalink
Fix icevision master branch (#1174)
Browse files Browse the repository at this point in the history
* squashed chhanges from other PRs

* add importorskip on fiftyone

* fix tests

* update ci to python 3.9, dev extras includes all

* black formatting

* fix build-pkg workflow

* fix build-pkg python version

* fix fridge class map and expected results

* update expected results

* use ubuntu-20.04

* use updated model checkpoint

* update dependencies

* use pre-release model link

* add skips for mmcv and mmseg tests

* update workflows

* update to yolov5==6.2.0

* use yaml.safe_load in yolo cfg loading

* use python native types casting

* use register in detection matching

* black formatting

* disable soft dependency check on mmseg and mmdet

* add swap action to fix failing tests

* update mk-docs ci to use python 3.9

* reenable mmseg tests

* reenable mmdet tests

* update and use installation script

* unskip torchvision backbones tests

* update mmseg and mmdet configs according to the installed versions

* fix yolov5 disabling notebook img display

* update dependencies

* black formatting

* add tests

* remove unused code
  • Loading branch information
potipot authored Oct 31, 2024
1 parent ffc0815 commit f7770b8
Show file tree
Hide file tree
Showing 37 changed files with 407 additions and 344 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/build-pkg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ jobs:
- uses: actions/checkout@master
- uses: actions/setup-python@v2
with:
python-version: 3.7
python-version: 3.9

- name: Build package
run: python setup.py sdist

- name: Install package
run: |
# pip install numpy
pip install -e .
python -c "from icevision.all import *"
12 changes: 9 additions & 3 deletions .github/workflows/ci-all-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-18.04]
python-version: [3.7, 3.8]
os: [ubuntu-20.04]
python-version: [3.9]

steps:
- uses: actions/checkout@v2
Expand All @@ -25,7 +25,7 @@ jobs:
- name: Install package
run: |
sh ./icevision_install.sh cpu
pip install -e ".[all,dev]"
pip install -e .[dev]
pip install fiftyone
- name: Lint with flake8
Expand All @@ -34,6 +34,12 @@ jobs:
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Set Swap Space
uses: pierotofy/set-swap-space@master
with:
swap-size-gb: 10

- name: Unit tests
run: pytest tests -m "not cuda" --cov=icevision --cov-report=xml --color=yes

Expand Down
10 changes: 3 additions & 7 deletions .github/workflows/mk-docs-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ env:

jobs:
build-docs:
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
Expand All @@ -22,14 +22,10 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
python-version: 3.9

- name: Install package
run: |
sh ./icevision_install.sh cpu
pip install -e ".[all,dev]"
run: pip install -e .[dev]
- name: Prepare the docs
run: |
cd docs
Expand Down
3 changes: 3 additions & 0 deletions icevision/core/bbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def __eq__(self, other) -> bool:
return self.xyxy == other.xyxy
return False

def __hash__(self):
return hash(self.xyxy)

@property
def width(self):
return self.xmax - self.xmin
Expand Down
1 change: 1 addition & 0 deletions icevision/core/record_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ def _autofix(self) -> Dict[str, bool]:

def _remove_annotation(self, i):
self.label_ids.pop(i)
self.labels.pop(i)

def _aggregate_objects(self) -> Dict[str, List[dict]]:
return {**super()._aggregate_objects(), "labels": self.label_ids}
Expand Down
134 changes: 0 additions & 134 deletions icevision/data/convert_records_to_coco_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,147 +6,13 @@
"coco_api_from_records",
"coco_api_from_preds",
"create_coco_eval",
"export_batch_inferences_as_coco_annotations",
]

from icevision.imports import *
from icevision.utils import *
from icevision.core import *
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
import json
import numpy as np
import PIL


class NpEncoder(json.JSONEncoder):
"""
Smooths out datatype conversions for IceVision preds to JSON export
"""

def default(self, obj):
if isinstance(obj, np.integer):
return int(obj)
if isinstance(obj, np.floating):
return float(obj)
if isinstance(obj, np.ndarray):
return obj.tolist()
return json.JSONEncoder.default(self, obj)


def export_batch_inferences_as_coco_annotations(
preds,
img_files,
transforms,
class_map,
output_filepath="inference_results_as_coco_annotations.json",
info=None,
licenses=None,
):
"""
For converting object detection predictions to COCO annotation format.
Useful for e.g. leveraging partly-trained models to help annotate
unlabeled data and make round trips back into annotation programs.
Parameters
----------
preds : List[Prediction]
The result of predict_from_dl()
img_files : fastcore.foundation.L (i.e. 'Paths')
References to the original image filepaths in array-like form.
transforms : Albumentations Adapter
Transforms that were applied to the original images (to be reversed)
class_map : icevision.core.class_map.ClassMap
The map of classes your model is familiar with
output_filepath : str, optional
The filepath (including filename) where you want the json results
to be serialized, by default
"new_pseudo_labels_for_further_training.json"
info: dict, optional
Option to manually create the info dict containing annotation metadata
including year, version, description, contributor, url, and date created
For example:
"info": {
"year": "2022",
"version": "1",
"description": "Exported from IceVision",
"contributor": "Awesome contributor",
"url": "https://lazyannotator.fun",
"date_created": "2022-08-05T20:13:09+00:00"
}
licenses: List[dict], optional
Option to manually create the license metadata for the annotations, e.g.
licenses = [
{
"name": "Creative Commons Attribution 4.0",
"id": 0,
"url": "https://creativecommons.org/licenses/by/4.0/legalcode",
}
]
Returns
-------
None
This just spits out a serialized .json file and returns nothing.
"""
object_category_list = [
{"id": v, "name": k, "supercategory": ""}
for k, v in class_map._class2id.items()
]

if info is None:
# Then automatically generate COCO annotation metadata:
info = {
"contributor": "",
"date_created": "",
"description": "",
"url": "",
"version": "",
"year": "",
}

if licenses is None:
licenses = [
{
"name": "",
"id": 0,
"url": "",
}
]

addl_info = {
"licenses": licenses,
"info": info,
"categories": object_category_list,
}

# Each entry needs a filepath
[pred.add_component(FilepathRecordComponent()) for pred in preds]
[preds[_].set_filepath(img_files[_]) for _ in range(len(preds))]

# process_bbox_predictions happens inplace, thus no new variable
for p in preds:
process_bbox_predictions(
p, PIL.Image.open(Path(p.pred.filepath)), transforms.tfms_list
)

coco_style_preds = convert_preds_to_coco_style(preds)
imgs_array = [PIL.Image.open(Path(fname)) for fname in img_files]

sizes = [{"x": img._size[0], "y": img._size[1]} for img in imgs_array]

for idx, image in enumerate(coco_style_preds["images"]):
coco_style_preds["images"][idx]["width"] = sizes[idx]["x"]
coco_style_preds["images"][idx]["height"] = sizes[idx]["y"]

finalized_pseudo_labels = {**addl_info, **coco_style_preds}

# Serialize
with open(output_filepath, "w") as jfile:
json.dump(finalized_pseudo_labels, jfile, cls=NpEncoder)

# Print confirmation message
print(f"New COCO annotation file saved to {output_filepath}")


def create_coco_api(coco_records) -> COCO:
Expand Down
27 changes: 27 additions & 0 deletions icevision/data/data_splitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"RandomSplitter",
"FixedSplitter",
"FuncSplitter",
"FolderSplitter",
]

from icevision.imports import *
Expand Down Expand Up @@ -132,3 +133,29 @@ def __init__(self, func):

def split(self, records: Sequence[BaseRecord]):
return self.func(records)


class FolderSplitter(DataSplitter):
"""
Split items into subsets based on provided keywords.
Set of items not containing any of the keywords is returned as first.
"""

def __init__(self, keywords=Collection[str]):
self.keywords = keywords

def split(self, records: Sequence[BaseRecord]):
"""
Splits records based on the provided keywords by their filepaths.
If some records don't match any keywords, they are returned as an additional split.
"""
remainder_set = {record.record_id for record in records}
keyword_sets = [set() for _ in self.keywords]
for record in records:
for keyword, other_set in zip(self.keywords, keyword_sets):
if keyword in record.filepath.as_posix():
other_set.add(record.record_id)
remainder_set -= set.union(*keyword_sets)
if remainder_set:
keyword_sets.append(remainder_set)
return keyword_sets
15 changes: 9 additions & 6 deletions icevision/data/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ def __len__(self):
return len(self.records)

def __getitem__(self, i):
record = self.records[i].load()
if self.tfm is not None:
record = self.tfm(record)
if isinstance(i, slice):
return self.__class__(self.records[i], self.tfm)
else:
# HACK FIXME
record.set_img(np.array(record.img))
return record
record = self.records[i].load()
if self.tfm is not None:
record = self.tfm(record)
else:
# HACK FIXME
record.set_img(np.array(record.img))
return record

def __repr__(self):
return f"<{self.__class__.__name__} with {len(self.records)} items>"
Expand Down
21 changes: 11 additions & 10 deletions icevision/metrics/confusion_matrix/confusion_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
class MatchingPolicy(Enum):
BEST_SCORE = 1
BEST_IOU = 2
ALL = 3


class SimpleConfusionMatrix(Metric):
Expand Down Expand Up @@ -41,27 +42,27 @@ def accumulate(self, preds: Collection[Prediction]):
# if not target_record.detection.bboxes:
# continue
# create matches based on iou
matches = match_records(
register = match_predictions_to_targets(
target=target_record,
prediction=prediction_record,
iou_threshold=self._iou_threshold,
)

target_labels, predicted_labels = [], []
# iterate over multiple targets and preds in a record
for target_item, prediction_items in matches:
for target_item in register:
if self._policy == MatchingPolicy.BEST_SCORE:
predicted_item = get_best_score_item(
prediction_items=prediction_items,
match, iou = get_best_score_match(
prediction_items=register[target_item]
)
elif self._policy == MatchingPolicy.BEST_IOU:
raise NotImplementedError
else:
raise RuntimeError(f"policy must be one of {list(MatchingPolicy)}")

# using label_id instead of named label to save memory
target_label = target_item["target_label_id"]
predicted_label = predicted_item["predicted_label_id"]
target_label = target_item.label_id
predicted_label = match.label_id
target_labels.append(target_label)
predicted_labels.append(predicted_label)

Expand Down Expand Up @@ -144,8 +145,8 @@ def _maybe_normalize(self, cm, normalize):

def log(self, logger_object) -> None:
# TODO: Disabled for now, need to design for metric logging for this to work + pl dependency
# if isinstance(logger_object, pl_loggers.WandbLogger):
# fig = self.plot()
# image = self._fig2img(fig)
# logger_object.experiment.log({"Confusion Matrix": wandb.Image(image)})
if isinstance(logger_object, pl_loggers.WandbLogger):
fig = self.plot()
image = self._fig2img(fig)
logger_object.experiment.log({"Confusion Matrix": wandb.Image(image)})
return
Loading

0 comments on commit f7770b8

Please sign in to comment.