Skip to content

Commit

Permalink
linting with flake8 and black
Browse files Browse the repository at this point in the history
  • Loading branch information
gauravparajuli committed Oct 6, 2024
1 parent 904558c commit 82b1375
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 83 deletions.
12 changes: 3 additions & 9 deletions pybboxes/annotations/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,7 @@ def load_from_voc(self, labels_dir: str):
provide path to directory that houses xml annotations in pascal voc format
"""
if self._annotation_type != "voc":
raise TypeError(
f"this instance of Annotations can only process {self._annotation_type} annotation file(s)"
)
raise TypeError(f"this instance of Annotations can only process {self._annotation_type} annotation file(s)")

if not os.path.exists(labels_dir):
raise FileNotFoundError(f"{labels_dir} doesn't exists")
Expand Down Expand Up @@ -161,9 +159,7 @@ def load_from_coco(self, json_path: str):
provide path to coco annotation file in json format
"""
if self._annotation_type != "coco":
raise TypeError(
f"this instance of Annotations can only process {self._annotation_type} annotation file(s)"
)
raise TypeError(f"this instance of Annotations can only process {self._annotation_type} annotation file(s)")

if not os.path.exists(json_path):
raise FileNotFoundError(f"{json_path} doesn't exists")
Expand Down Expand Up @@ -210,9 +206,7 @@ def load_from_yolo(self, labels_dir: str, images_dir: str, classes_file: str):
"""

if self._annotation_type != "yolo":
raise TypeError(
f"this instance of Annotations can only process {self._annotation_type} annotation file(s)"
)
raise TypeError(f"this instance of Annotations can only process {self._annotation_type} annotation file(s)")

if not os.path.exists(classes_file):
raise FileNotFoundError(f"{classes_file} doesn't exist")
Expand Down
3 changes: 1 addition & 2 deletions pybboxes/types/box_2d.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from typing import Union, Sequence
from typing import Sequence, Union

import numpy as np


IntegerBox: Union[Sequence[int], Sequence[Sequence[int]]]
FloatBox: Union[Sequence[float], Sequence[Sequence[float]]]

Expand Down
19 changes: 9 additions & 10 deletions pybboxes/types/polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@

import numpy as np

from pybboxes.types.box_2d import IntegerBox, FloatBox
from pybboxes.types.box_2d import FloatBox, IntegerBox


class Polygon:
def __init__(self):
self._points = []
def __init__(self):
self._points = []

@property
def points(self):
return self._points

def add(self, point: Union[IntegerBox, FloatBox]) -> None:
point = np.array(point, dtype=float)
self.points.append(point.tolist())
@property
def points(self):
return self._points

def add(self, point: Union[IntegerBox, FloatBox]) -> None:
point = np.array(point, dtype=float)
self.points.append(point.tolist())
1 change: 0 additions & 1 deletion pybboxes/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import importlib.util

from pathlib import Path
from typing import Union

Expand Down
27 changes: 15 additions & 12 deletions pybboxes/utils/io.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,47 @@
import json
import os
from typing import Dict, List, Optional, Union
import struct
from typing import Dict, List, Optional, Union

import yaml

def get_image_size(file_path:str):

def get_image_size(file_path: str):
"""
Return (width, height) for a given img file content - no external
dependencies except the os and struct modules from Python core
"""
with open(file_path, 'rb') as fhandle:
with open(file_path, "rb") as fhandle:
head = fhandle.read(24)
if len(head) != 24:
return None
if head.startswith(b'\x89PNG\r\n\x1a\n'):
check = struct.unpack('>i', head[16:20])[0]
if check != 0x0d0a1a0a:
if head.startswith(b"\x89PNG\r\n\x1a\n"):
check = struct.unpack(">i", head[16:20])[0]
if check != 0x0D0A1A0A:
return None
width, height = struct.unpack('>ii', head[16:24])
elif head[:2] == b'\xff\xd8':
width, height = struct.unpack(">ii", head[16:24])
elif head[:2] == b"\xff\xd8":
try:
fhandle.seek(0) # Read 0xff next
size = 2
ftype = 0
while not 0xc0 <= ftype <= 0xcf:
while not 0xC0 <= ftype <= 0xCF:
fhandle.seek(size, 1)
byte = fhandle.read(1)
while ord(byte) == 0xff:
while ord(byte) == 0xFF:
byte = fhandle.read(1)
ftype = ord(byte)
size = struct.unpack('>H', fhandle.read(2))[0] - 2
size = struct.unpack(">H", fhandle.read(2))[0] - 2
# We are at a SOFn block
fhandle.seek(1, 1) # Skip `precision' byte.
height, width = struct.unpack('>HH', fhandle.read(4))
height, width = struct.unpack(">HH", fhandle.read(4))
except Exception:
return None
else:
return None
return width, height


class IndentfulDumper(yaml.Dumper):
def increase_indent(self, flow=False, indentless=False):
return super(IndentfulDumper, self).increase_indent(flow, False)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def get_version():
"pytest-cov>=3.0.0",
"pytest-timeout>=2.1.0",
"pytest-depends>=1.0.1",
"huggingface-hub>=0.25.0"
"huggingface-hub>=0.25.0",
]

extras = {
Expand Down
126 changes: 78 additions & 48 deletions tests/pybboxes/annotations/test_annotations_conversion.py
Original file line number Diff line number Diff line change
@@ -1,77 +1,93 @@
import pytest
from pybboxes.annotations import Annotations
import glob
import os
import shutil
import glob
from functools import partial
from collections import Counter
from pycocotools.coco import COCO
from huggingface_hub import HfApi, hf_hub_download
from concurrent.futures import ThreadPoolExecutor, as_completed
from functools import partial

import pytest
from huggingface_hub import HfApi, hf_hub_download
from pycocotools.coco import COCO
from tqdm import tqdm

from pybboxes.annotations import Annotations

# hugging face repo from where we will be downloading our fixture for unit testing
repo_id = "gauravparajuli/coco_test_set_pybboxes"

sample_yolo_dataset_path = str(os.path.join('tests', 'pybboxes', 'annotations', 'testing_data_yolo'))
sample_voc_dataset_path = str(os.path.join('tests', 'pybboxes', 'annotations', 'testing_data_voc'))
sample_coco_dataset_path = str(os.path.join('tests', 'pybboxes', 'annotations', 'testing_data_coco', 'annotations_coco.json')) # source
persist_coco_test_path = str(os.path.join('tests', 'pybboxes', 'annotations', 'persist_as_coco_test.json')) # file generated during test_persist_as_coco
sample_yolo_dataset_path = str(os.path.join("tests", "pybboxes", "annotations", "testing_data_yolo"))
sample_voc_dataset_path = str(os.path.join("tests", "pybboxes", "annotations", "testing_data_voc"))
sample_coco_dataset_path = str(
os.path.join("tests", "pybboxes", "annotations", "testing_data_coco", "annotations_coco.json")
) # source
persist_coco_test_path = str(
os.path.join("tests", "pybboxes", "annotations", "persist_as_coco_test.json")
) # file generated during test_persist_as_coco

sample_images = str(os.path.join("tests", "pybboxes", "annotations", "testing_data_images"))

sample_images = str(os.path.join('tests', 'pybboxes', 'annotations', 'testing_data_images'))

def downloadfile(filename, local_dir):
hf_hub_download(
repo_id=repo_id,
repo_type='dataset',
repo_type="dataset",
filename=filename,
local_dir=local_dir,
)


def count_files(directory, extensions):
all_files = []
for ext in extensions:
all_files.extend(glob.glob(f"{directory}/*{ext}"))
return Counter(file.split('.')[-1] for file in all_files)
return Counter(file.split(".")[-1] for file in all_files)


sample_coco_dataset = Annotations(annotation_type="coco")

sample_coco_dataset = Annotations(annotation_type='coco')

def test_import_from_fiftyone():
anns = Annotations(annotation_type='fiftyone')
anns = Annotations(annotation_type="fiftyone")
with pytest.raises(NotImplementedError):
anns.load_from_fiftyone()


def test_import_from_albumentations():
anns = Annotations(annotation_type='albumentations')
anns = Annotations(annotation_type="albumentations")
with pytest.raises(NotImplementedError):
anns.load_from_albumentations()


def test_save_as_fiftyone():
anns = Annotations(annotation_type='albumentations')
anns = Annotations(annotation_type="albumentations")
with pytest.raises(NotImplementedError):
anns.save_as_fiftyone()


def test_save_as_albumentations():
anns = Annotations(annotation_type='fiftyone')
anns = Annotations(annotation_type="fiftyone")
with pytest.raises(NotImplementedError):
anns.save_as_albumentations()


def test_annotations_initialization():
# annotation_type should be either: yolo, coco, voc, albumentations or fiftyone
with pytest.raises(ValueError):
anns = Annotations(annotation_type='not_this_type')
anns = Annotations(annotation_type="not_this_type")


def test_annotations_only_appropriate_loading_method_allowed():
# tests if unappropriate method is used to load annotations
anns = Annotations('yolo')
anns = Annotations("yolo")
with pytest.raises(TypeError):
anns.load_from_voc(labels_dir='./labels')
anns.load_from_voc(labels_dir="./labels")
with pytest.raises(TypeError):
anns.load_from_coco(json_path='./sample.json')
anns.load_from_coco(json_path="./sample.json")

anns = Annotations('coco')
anns = Annotations("coco")
with pytest.raises(TypeError):
anns.load_from_yolo(labels_dir='./labels', images_dir='./images', classes_file='./classes.txt')
anns.load_from_yolo(labels_dir="./labels", images_dir="./images", classes_file="./classes.txt")


def test_import_from_coco():
anns = sample_coco_dataset
Expand All @@ -81,70 +97,84 @@ def test_import_from_coco():
assert anns.names_mapping == dict(raccoons=0, raccoon=1)

# randomly test the accuracy of annotations here

@pytest.mark.depends(on=['test_save_as_yolo'])


@pytest.mark.depends(on=["test_save_as_yolo"])
def test_import_from_yolo():
anns = Annotations(annotation_type='yolo')
anns.load_from_yolo(labels_dir=sample_yolo_dataset_path, images_dir=sample_images, classes_file=str(os.path.join(sample_yolo_dataset_path, 'classes.txt')))
anns = Annotations(annotation_type="yolo")
anns.load_from_yolo(
labels_dir=sample_yolo_dataset_path,
images_dir=sample_images,
classes_file=str(os.path.join(sample_yolo_dataset_path, "classes.txt")),
)

assert (type(anns.names_mapping)) == dict
assert anns.names_mapping == dict(raccoons=0, raccoon=1)

@pytest.mark.depends(on=['test_save_as_voc'])

@pytest.mark.depends(on=["test_save_as_voc"])
def test_import_from_voc():
anns = Annotations(annotation_type='voc')
anns = Annotations(annotation_type="voc")
anns.load_from_voc(labels_dir=sample_voc_dataset_path)

assert (type(anns.names_mapping)) == dict
assert anns.names_mapping == dict(raccoon=0) # as raccoons label was not used in any bounding boxes,
# plus there is not a file that lists all the available class in voc format
# there was a loss of information
# when converting from coco format to voc format
assert anns.names_mapping == dict(raccoon=0) # as raccoons label was not used in any bounding boxes,
# plus there is not a file that lists all the available class in voc format
# there was a loss of information
# when converting from coco format to voc format

@pytest.mark.depends(on=['test_import_from_coco'])

@pytest.mark.depends(on=["test_import_from_coco"])
def test_save_as_coco():
persist_coco_path = str(os.path.join('tests', 'pybboxes', 'annotations', 'persist_as_coco_test.json'))
persist_coco_path = str(os.path.join("tests", "pybboxes", "annotations", "persist_as_coco_test.json"))
sample_coco_dataset.save_as_coco(export_file=persist_coco_path)

coco = COCO(persist_coco_path)

assert len(coco.getImgIds()) == 196
assert len(coco.getCatIds()) == 2

@pytest.mark.depends(on=['test_import_from_coco'])

@pytest.mark.depends(on=["test_import_from_coco"])
def test_save_as_yolo():
sample_coco_dataset.save_as_yolo(sample_yolo_dataset_path)

assert count_files(sample_yolo_dataset_path, extensions=['.txt'])['txt'] == 197 # 196 annotation files, 1 classes.txt file
assert (
count_files(sample_yolo_dataset_path, extensions=[".txt"])["txt"] == 197
) # 196 annotation files, 1 classes.txt file

@pytest.mark.depends(on=['test_import_from_coco'])

@pytest.mark.depends(on=["test_import_from_coco"])
def test_save_as_voc():
sample_coco_dataset.save_as_voc(sample_voc_dataset_path)

assert count_files(sample_voc_dataset_path, extensions=['.xml'])['xml'] == 196 # 196 annotation files
assert count_files(sample_voc_dataset_path, extensions=[".xml"])["xml"] == 196 # 196 annotation files


@pytest.fixture(scope='session', autouse=True)
@pytest.fixture(scope="session", autouse=True)
def cleanup():
# setup code here
api = HfApi()
files = api.list_repo_files(repo_id=repo_id, repo_type='dataset')
files = [file for file in files if ('.json' in file or '.jpg' in file)] # filter .gitattributes and README.md
files = api.list_repo_files(repo_id=repo_id, repo_type="dataset")
files = [file for file in files if (".json" in file or ".jpg" in file)] # filter .gitattributes and README.md

annotationfilename = files.pop(0) # annotations_coco.json
downloadfile(annotationfilename, local_dir=os.path.dirname(sample_coco_dataset_path)) # download annotation file in a separate folder
annotationfilename = files.pop(0) # annotations_coco.json
downloadfile(
annotationfilename, local_dir=os.path.dirname(sample_coco_dataset_path)
) # download annotation file in a separate folder

# now download test dataset images
with ThreadPoolExecutor() as executor:
partial_downloadfile = partial(downloadfile, local_dir=sample_images)
futures = [executor.submit(partial_downloadfile, filename) for filename in files]
with tqdm(total=len(futures), desc='downloading test set for unit testing', unit='file') as pbar:
with tqdm(total=len(futures), desc="downloading test set for unit testing", unit="file") as pbar:
for future in as_completed(futures):
pbar.set_description_str = future.result()
pbar.update(1) # update the progress bar for each completed download
pbar.update(1) # update the progress bar for each completed download

yield

# clean up the folders that we created after all the tests have ran
shutil.rmtree(sample_voc_dataset_path)
shutil.rmtree(sample_yolo_dataset_path)
os.remove(persist_coco_test_path) # remove the test file
os.remove(persist_coco_test_path) # remove the test file

0 comments on commit 82b1375

Please sign in to comment.