Skip to content

Commit f7770b8

Browse files
authored
Fix icevision master branch (#1174)
* 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
1 parent ffc0815 commit f7770b8

37 files changed

+407
-344
lines changed

.github/workflows/build-pkg.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@ jobs:
1616
- uses: actions/checkout@master
1717
- uses: actions/setup-python@v2
1818
with:
19-
python-version: 3.7
19+
python-version: 3.9
2020

2121
- name: Build package
2222
run: python setup.py sdist
2323

2424
- name: Install package
2525
run: |
26-
# pip install numpy
2726
pip install -e .
2827
python -c "from icevision.all import *"

.github/workflows/ci-all-testing.yml

+9-3
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ jobs:
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
os: [ubuntu-18.04]
16-
python-version: [3.7, 3.8]
15+
os: [ubuntu-20.04]
16+
python-version: [3.9]
1717

1818
steps:
1919
- uses: actions/checkout@v2
@@ -25,7 +25,7 @@ jobs:
2525
- name: Install package
2626
run: |
2727
sh ./icevision_install.sh cpu
28-
pip install -e ".[all,dev]"
28+
pip install -e .[dev]
2929
pip install fiftyone
3030
3131
- name: Lint with flake8
@@ -34,6 +34,12 @@ jobs:
3434
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
3535
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
3636
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
37+
38+
- name: Set Swap Space
39+
uses: pierotofy/set-swap-space@master
40+
with:
41+
swap-size-gb: 10
42+
3743
- name: Unit tests
3844
run: pytest tests -m "not cuda" --cov=icevision --cov-report=xml --color=yes
3945

.github/workflows/mk-docs-build.yml

+3-7
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ env:
1313

1414
jobs:
1515
build-docs:
16-
runs-on: ubuntu-18.04
16+
runs-on: ubuntu-20.04
1717
steps:
1818
- uses: actions/checkout@v2
1919
with:
@@ -22,14 +22,10 @@ jobs:
2222
- name: Set up Python
2323
uses: actions/setup-python@v2
2424
with:
25-
python-version: 3.8
25+
python-version: 3.9
2626

2727
- name: Install package
28-
run: |
29-
sh ./icevision_install.sh cpu
30-
pip install -e ".[all,dev]"
31-
32-
28+
run: pip install -e .[dev]
3329
- name: Prepare the docs
3430
run: |
3531
cd docs

icevision/core/bbox.py

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ def __eq__(self, other) -> bool:
3636
return self.xyxy == other.xyxy
3737
return False
3838

39+
def __hash__(self):
40+
return hash(self.xyxy)
41+
3942
@property
4043
def width(self):
4144
return self.xmax - self.xmin

icevision/core/record_components.py

+1
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ def _autofix(self) -> Dict[str, bool]:
269269

270270
def _remove_annotation(self, i):
271271
self.label_ids.pop(i)
272+
self.labels.pop(i)
272273

273274
def _aggregate_objects(self) -> Dict[str, List[dict]]:
274275
return {**super()._aggregate_objects(), "labels": self.label_ids}

icevision/data/convert_records_to_coco_style.py

-134
Original file line numberDiff line numberDiff line change
@@ -6,147 +6,13 @@
66
"coco_api_from_records",
77
"coco_api_from_preds",
88
"create_coco_eval",
9-
"export_batch_inferences_as_coco_annotations",
109
]
1110

1211
from icevision.imports import *
1312
from icevision.utils import *
1413
from icevision.core import *
1514
from pycocotools.coco import COCO
1615
from pycocotools.cocoeval import COCOeval
17-
import json
18-
import numpy as np
19-
import PIL
20-
21-
22-
class NpEncoder(json.JSONEncoder):
23-
"""
24-
Smooths out datatype conversions for IceVision preds to JSON export
25-
"""
26-
27-
def default(self, obj):
28-
if isinstance(obj, np.integer):
29-
return int(obj)
30-
if isinstance(obj, np.floating):
31-
return float(obj)
32-
if isinstance(obj, np.ndarray):
33-
return obj.tolist()
34-
return json.JSONEncoder.default(self, obj)
35-
36-
37-
def export_batch_inferences_as_coco_annotations(
38-
preds,
39-
img_files,
40-
transforms,
41-
class_map,
42-
output_filepath="inference_results_as_coco_annotations.json",
43-
info=None,
44-
licenses=None,
45-
):
46-
"""
47-
For converting object detection predictions to COCO annotation format.
48-
Useful for e.g. leveraging partly-trained models to help annotate
49-
unlabeled data and make round trips back into annotation programs.
50-
51-
Parameters
52-
----------
53-
preds : List[Prediction]
54-
The result of predict_from_dl()
55-
img_files : fastcore.foundation.L (i.e. 'Paths')
56-
References to the original image filepaths in array-like form.
57-
transforms : Albumentations Adapter
58-
Transforms that were applied to the original images (to be reversed)
59-
class_map : icevision.core.class_map.ClassMap
60-
The map of classes your model is familiar with
61-
output_filepath : str, optional
62-
The filepath (including filename) where you want the json results
63-
to be serialized, by default
64-
"new_pseudo_labels_for_further_training.json"
65-
info: dict, optional
66-
Option to manually create the info dict containing annotation metadata
67-
including year, version, description, contributor, url, and date created
68-
For example:
69-
"info": {
70-
"year": "2022",
71-
"version": "1",
72-
"description": "Exported from IceVision",
73-
"contributor": "Awesome contributor",
74-
"url": "https://lazyannotator.fun",
75-
"date_created": "2022-08-05T20:13:09+00:00"
76-
}
77-
licenses: List[dict], optional
78-
Option to manually create the license metadata for the annotations, e.g.
79-
licenses = [
80-
{
81-
"name": "Creative Commons Attribution 4.0",
82-
"id": 0,
83-
"url": "https://creativecommons.org/licenses/by/4.0/legalcode",
84-
}
85-
]
86-
87-
Returns
88-
-------
89-
None
90-
This just spits out a serialized .json file and returns nothing.
91-
"""
92-
object_category_list = [
93-
{"id": v, "name": k, "supercategory": ""}
94-
for k, v in class_map._class2id.items()
95-
]
96-
97-
if info is None:
98-
# Then automatically generate COCO annotation metadata:
99-
info = {
100-
"contributor": "",
101-
"date_created": "",
102-
"description": "",
103-
"url": "",
104-
"version": "",
105-
"year": "",
106-
}
107-
108-
if licenses is None:
109-
licenses = [
110-
{
111-
"name": "",
112-
"id": 0,
113-
"url": "",
114-
}
115-
]
116-
117-
addl_info = {
118-
"licenses": licenses,
119-
"info": info,
120-
"categories": object_category_list,
121-
}
122-
123-
# Each entry needs a filepath
124-
[pred.add_component(FilepathRecordComponent()) for pred in preds]
125-
[preds[_].set_filepath(img_files[_]) for _ in range(len(preds))]
126-
127-
# process_bbox_predictions happens inplace, thus no new variable
128-
for p in preds:
129-
process_bbox_predictions(
130-
p, PIL.Image.open(Path(p.pred.filepath)), transforms.tfms_list
131-
)
132-
133-
coco_style_preds = convert_preds_to_coco_style(preds)
134-
imgs_array = [PIL.Image.open(Path(fname)) for fname in img_files]
135-
136-
sizes = [{"x": img._size[0], "y": img._size[1]} for img in imgs_array]
137-
138-
for idx, image in enumerate(coco_style_preds["images"]):
139-
coco_style_preds["images"][idx]["width"] = sizes[idx]["x"]
140-
coco_style_preds["images"][idx]["height"] = sizes[idx]["y"]
141-
142-
finalized_pseudo_labels = {**addl_info, **coco_style_preds}
143-
144-
# Serialize
145-
with open(output_filepath, "w") as jfile:
146-
json.dump(finalized_pseudo_labels, jfile, cls=NpEncoder)
147-
148-
# Print confirmation message
149-
print(f"New COCO annotation file saved to {output_filepath}")
15016

15117

15218
def create_coco_api(coco_records) -> COCO:

icevision/data/data_splitter.py

+27
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"RandomSplitter",
55
"FixedSplitter",
66
"FuncSplitter",
7+
"FolderSplitter",
78
]
89

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

133134
def split(self, records: Sequence[BaseRecord]):
134135
return self.func(records)
136+
137+
138+
class FolderSplitter(DataSplitter):
139+
"""
140+
Split items into subsets based on provided keywords.
141+
Set of items not containing any of the keywords is returned as first.
142+
"""
143+
144+
def __init__(self, keywords=Collection[str]):
145+
self.keywords = keywords
146+
147+
def split(self, records: Sequence[BaseRecord]):
148+
"""
149+
Splits records based on the provided keywords by their filepaths.
150+
If some records don't match any keywords, they are returned as an additional split.
151+
"""
152+
remainder_set = {record.record_id for record in records}
153+
keyword_sets = [set() for _ in self.keywords]
154+
for record in records:
155+
for keyword, other_set in zip(self.keywords, keyword_sets):
156+
if keyword in record.filepath.as_posix():
157+
other_set.add(record.record_id)
158+
remainder_set -= set.union(*keyword_sets)
159+
if remainder_set:
160+
keyword_sets.append(remainder_set)
161+
return keyword_sets

icevision/data/dataset.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ def __len__(self):
3232
return len(self.records)
3333

3434
def __getitem__(self, i):
35-
record = self.records[i].load()
36-
if self.tfm is not None:
37-
record = self.tfm(record)
35+
if isinstance(i, slice):
36+
return self.__class__(self.records[i], self.tfm)
3837
else:
39-
# HACK FIXME
40-
record.set_img(np.array(record.img))
41-
return record
38+
record = self.records[i].load()
39+
if self.tfm is not None:
40+
record = self.tfm(record)
41+
else:
42+
# HACK FIXME
43+
record.set_img(np.array(record.img))
44+
return record
4245

4346
def __repr__(self):
4447
return f"<{self.__class__.__name__} with {len(self.records)} items>"

icevision/metrics/confusion_matrix/confusion_matrix.py

+11-10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
class MatchingPolicy(Enum):
1111
BEST_SCORE = 1
1212
BEST_IOU = 2
13+
ALL = 3
1314

1415

1516
class SimpleConfusionMatrix(Metric):
@@ -41,27 +42,27 @@ def accumulate(self, preds: Collection[Prediction]):
4142
# if not target_record.detection.bboxes:
4243
# continue
4344
# create matches based on iou
44-
matches = match_records(
45+
register = match_predictions_to_targets(
4546
target=target_record,
4647
prediction=prediction_record,
4748
iou_threshold=self._iou_threshold,
4849
)
4950

5051
target_labels, predicted_labels = [], []
5152
# iterate over multiple targets and preds in a record
52-
for target_item, prediction_items in matches:
53+
for target_item in register:
5354
if self._policy == MatchingPolicy.BEST_SCORE:
54-
predicted_item = get_best_score_item(
55-
prediction_items=prediction_items,
55+
match, iou = get_best_score_match(
56+
prediction_items=register[target_item]
5657
)
5758
elif self._policy == MatchingPolicy.BEST_IOU:
5859
raise NotImplementedError
5960
else:
6061
raise RuntimeError(f"policy must be one of {list(MatchingPolicy)}")
6162

6263
# using label_id instead of named label to save memory
63-
target_label = target_item["target_label_id"]
64-
predicted_label = predicted_item["predicted_label_id"]
64+
target_label = target_item.label_id
65+
predicted_label = match.label_id
6566
target_labels.append(target_label)
6667
predicted_labels.append(predicted_label)
6768

@@ -144,8 +145,8 @@ def _maybe_normalize(self, cm, normalize):
144145

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

0 commit comments

Comments
 (0)