Skip to content

Commit 0bfb933

Browse files
committed
add support for global csv annotation files
1 parent bda8d5d commit 0bfb933

File tree

10 files changed

+64
-13
lines changed

10 files changed

+64
-13
lines changed

roboflow/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from roboflow.models import CLIPModel, GazeModel # noqa: F401
1515
from roboflow.util.general import write_line
1616

17-
__version__ = "1.1.26"
17+
__version__ = "1.1.27"
1818

1919

2020
def check_key(api_key, model, notebook, num_retries=0):

roboflow/core/project.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -528,9 +528,9 @@ def single_upload(
528528

529529
def _annotation_params(self, annotation_path):
530530
annotation_name, annotation_string = None, None
531-
if isinstance(annotation_path, dict):
531+
if isinstance(annotation_path, dict) and annotation_path.get("rawText"):
532532
annotation_name = annotation_path["name"]
533-
annotation_string = json.dumps(annotation_path["parsed"])
533+
annotation_string = annotation_path["rawText"]
534534
elif os.path.exists(annotation_path):
535535
with open(annotation_path, "r"):
536536
annotation_string = open(annotation_path, "r").read()

roboflow/core/workspace.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,8 @@ def _upload_image(imagedesc):
341341
labelmap = None
342342
annotationdesc = imagedesc.get("annotationfile")
343343
if annotationdesc:
344-
if annotationdesc.get("parsed"):
345-
annotation_path = {"name": annotationdesc["name"], "parsed": annotationdesc["parsed"]}
344+
if annotationdesc.get("rawText"):
345+
annotation_path = annotationdesc
346346
else:
347347
annotation_path = f"{location}{annotationdesc['file']}"
348348
labelmap = annotationdesc.get("labelmap")

roboflow/util/folderparser.py

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .image_utils import load_labelmap
66

77
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".bmp"}
8-
ANNOTATION_EXTENSIONS = {".txt", ".json", ".xml"}
8+
ANNOTATION_EXTENSIONS = {".txt", ".json", ".xml", ".csv"}
99
LABELMAPS_EXTENSIONS = {".labels", ".yaml", ".yml"}
1010

1111

@@ -138,6 +138,7 @@ def _filterIndividualAnnotations(image, annotation, format):
138138
or [fake_annotation],
139139
},
140140
}
141+
_annotation["rawText"] = json.dumps(_annotation["parsed"])
141142
return _annotation
142143
elif format == "createml":
143144
imgReferences = [i for i in parsed if i["image"] == image["name"]]
@@ -149,25 +150,58 @@ def _filterIndividualAnnotations(image, annotation, format):
149150
"name": "annotation.createml.json",
150151
"parsedType": "createml",
151152
"parsed": [imgReference],
153+
"rawText": json.dumps([imgReference]),
152154
}
153155
return _annotation
156+
elif format == "csv":
157+
imgLines = [l["line"] for l in parsed["lines"] if l["file_name"] == image["name"]]
158+
if imgLines:
159+
headers = parsed["headers"]
160+
_annotation = {
161+
"name": "annotation.csv",
162+
"parsedType": "csv",
163+
"parsed": {
164+
"headers": headers,
165+
"lines": imgLines,
166+
},
167+
"rawText": "".join([headers] + imgLines),
168+
}
169+
return _annotation
170+
else:
171+
return None
154172
return None
155173

156174

157175
def _loadAnnotations(folder, annotations):
158-
valid_extensions = {".json"}
176+
valid_extensions = {".json", ".csv"}
159177
annotations = [a for a in annotations if a["extension"] in valid_extensions]
160178
for ann in annotations:
161179
extension = ann["extension"]
162-
with open(f"{folder}{ann['file']}", "r") as f:
163-
parsed = json.load(f)
164-
parsedType = _guessAnnotationFileFormat(parsed, extension)
165-
if parsedType:
166-
ann["parsed"] = parsed
167-
ann["parsedType"] = parsedType
180+
if extension == ".json":
181+
with open(f"{folder}{ann['file']}", "r") as f:
182+
parsed = json.load(f)
183+
parsedType = _guessAnnotationFileFormat(parsed, extension)
184+
if parsedType:
185+
ann["parsed"] = parsed
186+
ann["parsedType"] = parsedType
187+
elif extension == ".csv":
188+
ann["parsedType"] = "csv"
189+
ann["parsed"] = _parseAnnotationCSV(f"{folder}{ann['file']}")
168190
return annotations
169191

170192

193+
def _parseAnnotationCSV(filename):
194+
# TODO: use a proper CSV library?
195+
with open(filename, "r") as f:
196+
lines = f.readlines()
197+
headers = lines[0]
198+
lines = [{"file_name": l.split(",")[0].strip(), "line": l} for l in lines[1:]]
199+
return {
200+
"headers": headers,
201+
"lines": lines,
202+
}
203+
204+
171205
def _guessAnnotationFileFormat(parsed, extension):
172206
if extension == ".json":
173207
if isinstance(parsed, dict):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
img_fName,img_w,img_h,class_label,bbx_xtl,bbx_ytl,bbx_xbr,bbx_ybr
2+
train_10307.jpeg,3024,4032,culex,1459,1389,1826,2062
3+
train_10308.jpeg,1058,943,japonicus/koreicus,28,187,908,815
4+
train_10309.jpeg,1024,1365,culex,304,438,614,785
5+
train_10310.jpeg,2976,3968,albopictus,1900,1280,2163,1653
3.38 MB
Loading
199 KB
Loading
37.2 KB
Loading

tests/manual/debugme.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@
4040
# f"import {thisdir}/data/cultura-pepino-yolov8 -w wolfodorpythontests -p yellow-auto -c 100".split() # noqa: E501 // docs
4141
# f"import {thisdir}/data/cultura-pepino-yolov8_voc -w wolfodorpythontests -p yellow-auto -c 100".split() # noqa: E501 // docs
4242
f"import {thisdir}/data/cultura-pepino-yolov5pytorch -w wolfodorpythontests -p yellow-auto -c 100 -n papaiasso".split() # noqa: E501 // docs
43+
# f"import {thisdir}/../datasets/mosquitos -w wolfodorpythontests -p yellow-auto -n papaiasso".split() # noqa: E501 // docs
4344
)
4445
args.func(args)

tests/util/test_folderparser.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ def test_parse_sharks_yolov9(self):
3838
assert testImage["annotationfile"]["file"] == expectAnnotationFile
3939
assert testImage["annotationfile"]["labelmap"] == {0: "fish", 1: "primary", 2: "shark"}
4040

41+
def test_parse_mosquitos_csv(self):
42+
sharksfolder = f"{thisdir}/../datasets/mosquitos"
43+
parsed = folderparser.parsefolder(sharksfolder)
44+
testImagePath = "/train_10308.jpeg"
45+
testImage = [i for i in parsed["images"] if i["file"] == testImagePath][0]
46+
assert testImage["annotationfile"]["name"] == "annotation.csv"
47+
headers = testImage["annotationfile"]["parsed"]["headers"]
48+
lines = testImage["annotationfile"]["parsed"]["lines"]
49+
assert headers == "img_fName,img_w,img_h,class_label,bbx_xtl,bbx_ytl,bbx_xbr,bbx_ybr\n"
50+
assert lines == ["train_10308.jpeg,1058,943,japonicus/koreicus,28,187,908,815\n"]
51+
4152

4253
def _assertJsonMatchesFile(actual, filename):
4354
with open(filename, "r") as file:

0 commit comments

Comments
 (0)