Transferring project.
This commit is contained in:
Родитель
7abf3decfe
Коммит
7d1de0f213
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
|
@ -0,0 +1,81 @@
|
|||
# Introduction
|
||||
|
||||
Annotation-Factory Python SDK. This package works specifically with Microsoft Cognitive Services detection results. `AnnotationWriter` takes a JSON object received from Cognitive Services and produces annotation files in both VOC and YOLO formats for use in training machine learning models.
|
||||
|
||||
[![Build Status](https://dev.azure.com/aussiedevcrew/Annotation-Factory/_apis/build/status/Microsoft.Annotation-Factory?branchName=master)](https://dev.azure.com/aussiedevcrew/Annotation-Factory/_build/latest?definitionId=9&branchName=master)
|
||||
|
||||
# Getting Started
|
||||
|
||||
1. Install `annotationfactory` package via pip:
|
||||
|
||||
```
|
||||
pip install annotationfactory
|
||||
```
|
||||
|
||||
# Sample to use
|
||||
|
||||
```python
|
||||
from annotationfactory.annotationwriter import AnnotationWriter
|
||||
import annotationfactory.annotationconverter as converter
|
||||
|
||||
example = {
|
||||
'tagId': 0,
|
||||
'tagName': 'Apples',
|
||||
'region': {
|
||||
'left': 0.288039029,
|
||||
'top': 0.411838,
|
||||
'width': 0.291451037,
|
||||
'height': 0.4237842
|
||||
}
|
||||
}
|
||||
|
||||
# Initialise AnnotationWriter.
|
||||
writer = AnnotationWriter()
|
||||
|
||||
# Initialise annotation handlers.
|
||||
writer.initVoc("test.jpg", 608, 608)
|
||||
writer.initYolo()
|
||||
|
||||
# Add VOC object to writer.
|
||||
writer.addVocObject(example)
|
||||
writer.addVocObject(example)
|
||||
|
||||
# Add YOLO object to writer.
|
||||
writer.addYoloObject(example)
|
||||
writer.addYoloObject(example)
|
||||
|
||||
# Output VOC annotations to file.
|
||||
writer.saveVoc("myannotation.xml")
|
||||
|
||||
# Output YOLO annotations to file.
|
||||
writer.saveYolo("myannotation.txt")
|
||||
|
||||
# Converts VOC annotations back to CustomVision annotation format.
|
||||
voc2cv = converter.convertVocFromPath("myannotation.xml")
|
||||
|
||||
# Converts YOLO annotations back to CustomVision annotation format.
|
||||
# Requires a txt file with list of label names as an input.
|
||||
yolo2cv = converter.convertYoloFromPath("myannotation.txt", "class.names")
|
||||
|
||||
```
|
||||
|
||||
# Run locally
|
||||
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
python example/test.py
|
||||
```
|
||||
|
||||
# Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
@ -0,0 +1,150 @@
|
|||
import xmltodict
|
||||
from annotationfactory.models.annotation import Annotation
|
||||
from annotationfactory.models.region import Region
|
||||
|
||||
|
||||
class AnnotationConverter:
|
||||
|
||||
'''
|
||||
AnnotationConvert helps converts Pascal VOC and YOLO to
|
||||
CustomVision annotations.
|
||||
|
||||
Optional:
|
||||
classes ([str]): Object class list for use when
|
||||
converting YOLO annotations.
|
||||
'''
|
||||
|
||||
def __init__(self, classes=None):
|
||||
|
||||
self.classes = classes
|
||||
|
||||
def convertVoc(self, area: dict, dim: [2]) -> Annotation:
|
||||
|
||||
'''
|
||||
Convert VOC object to CustomVision annotations.
|
||||
|
||||
Args:
|
||||
area (dict): VOC annotation of a single region.
|
||||
dim ([float, float]): Width and height of target
|
||||
image.
|
||||
|
||||
Returns:
|
||||
~annotationfactory.models.annotation
|
||||
'''
|
||||
|
||||
(w, h) = dim
|
||||
|
||||
region = area["bndbox"]
|
||||
tag = area["name"]
|
||||
|
||||
left = float(region['xmin']) / w
|
||||
top = float(region['ymin']) / h
|
||||
width = float(region["xmax"]) / w
|
||||
height = float(region["ymax"]) / h
|
||||
|
||||
region = Region(left, top, width, height)
|
||||
|
||||
return Annotation(tagName=tag, region=region)
|
||||
|
||||
def convertYolo(self, line: str) -> Annotation:
|
||||
|
||||
'''
|
||||
Convert YOLO object to CustomVision annotations.
|
||||
|
||||
Args:
|
||||
line (str): A single line from YOLO annotation.txt
|
||||
file.
|
||||
|
||||
Returns:
|
||||
~annotationfactory.models.annotation
|
||||
'''
|
||||
|
||||
region = line.split()
|
||||
tag = None
|
||||
|
||||
(x_centre, y_centre, w, h) = region[1:]
|
||||
|
||||
left = float(x_centre) - (float(w) / 2)
|
||||
top = float(y_centre) - (float(h) / 2)
|
||||
width = float(w)
|
||||
height = float(h)
|
||||
|
||||
if self.classes is not None:
|
||||
tag = self.classes[int(region[0])]
|
||||
|
||||
region = Region(left, top, width, height)
|
||||
|
||||
return Annotation(tagName=tag, region=region)
|
||||
|
||||
|
||||
def convertVocFromPath(path: str) -> [Annotation]:
|
||||
|
||||
'''
|
||||
Convert VOC annotation.xml file to CustomVision annotations.
|
||||
|
||||
Args:
|
||||
path (str): Path directory to annotation.xml file on local machine.
|
||||
|
||||
Returns:
|
||||
list[~annotationfactory.models.annotation]
|
||||
'''
|
||||
|
||||
converter = AnnotationConverter()
|
||||
|
||||
with open(path) as f:
|
||||
voc = xmltodict.parse(f.read())["annotation"]
|
||||
|
||||
w = float(voc["size"]["width"])
|
||||
h = float(voc["size"]["height"])
|
||||
|
||||
annotations = []
|
||||
|
||||
for i in voc["object"]:
|
||||
annotations.append(converter.convertVoc(i, (w, h)))
|
||||
|
||||
return annotations
|
||||
|
||||
|
||||
def convertYoloFromPath(path: str, classPath: str = None) -> [Annotation]:
|
||||
|
||||
'''
|
||||
Convert YOLO annotation.txt file to CustomVision annotations.
|
||||
|
||||
Args:
|
||||
path (str): Path directory to annotation.xml file on local machine.
|
||||
classPath (str): Path directory to class.names file on local machine.
|
||||
|
||||
Returns:
|
||||
list[~annotationfactory.models.annotation]
|
||||
'''
|
||||
|
||||
converter = AnnotationConverter(parseFile(classPath))
|
||||
|
||||
yolo = parseFile(path)
|
||||
|
||||
annotations = []
|
||||
|
||||
for i in yolo:
|
||||
annotations.append(converter.convertYolo(i))
|
||||
|
||||
return annotations
|
||||
|
||||
|
||||
def parseFile(path: str) -> [str]:
|
||||
|
||||
'''
|
||||
Reads file from path line-by-line into list.
|
||||
|
||||
Args:
|
||||
path (str): Path directory to any.txt file on local machine.
|
||||
|
||||
Returns:
|
||||
list[str]
|
||||
'''
|
||||
|
||||
with open(path) as f:
|
||||
result = f.read().split("\n")
|
||||
if result[-1] == "":
|
||||
result = result[:-1]
|
||||
|
||||
return result
|
|
@ -0,0 +1,186 @@
|
|||
from jinja2 import Environment, PackageLoader
|
||||
from jsonschema import validate
|
||||
import json
|
||||
|
||||
|
||||
class AnnotationWriter:
|
||||
|
||||
'''
|
||||
AnnotationWriter is a lightweight VOC/YOLO annotation generator for use in
|
||||
machine learning.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
|
||||
environment = Environment(
|
||||
loader=PackageLoader('annotationfactory', 'templates'),
|
||||
keep_trailing_newline=True)
|
||||
|
||||
self.annotation_voc_template = environment.get_template(
|
||||
'annotation.xml')
|
||||
self.annotation_yolo_template = environment.get_template(
|
||||
'annotation.txt')
|
||||
self.schema = json.loads(
|
||||
environment.get_template("schema.json").render())
|
||||
|
||||
self.template_voc_parameters = None
|
||||
self.template_yolo_parameters = None
|
||||
|
||||
def initVoc(self, file, width, height,
|
||||
depth=3, database='Unknown', segmented=0):
|
||||
|
||||
'''
|
||||
Initialised AnnotationWriter to generate VOC annotations.
|
||||
|
||||
Args:
|
||||
file (str): Name of image associated to annotation.
|
||||
width (int): Width of image.
|
||||
height (int): Height of image.
|
||||
|
||||
Optional:
|
||||
depth (int): Depth of image.
|
||||
database (str): Name of database image belong to.
|
||||
segmented (int): Number of segmentations.
|
||||
'''
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.file = file
|
||||
|
||||
self.template_voc_parameters = {
|
||||
'filename': file,
|
||||
'folder': "images",
|
||||
'width': self.width,
|
||||
'height': self.height,
|
||||
'depth': depth,
|
||||
'database': database,
|
||||
'segmented': segmented,
|
||||
'objects': []
|
||||
}
|
||||
|
||||
def initYolo(self):
|
||||
|
||||
'''
|
||||
Initialised AnnotationWriter to generate YOLO annotations.
|
||||
'''
|
||||
|
||||
self.template_yolo_parameters = []
|
||||
|
||||
def addVocObject(
|
||||
self, annotation, pose='Unspecified', truncated=0, difficult=0):
|
||||
|
||||
'''
|
||||
Add object annotation to VOC writer.
|
||||
|
||||
Args:
|
||||
annotation (dict): Annotation object used to
|
||||
generate VOC annotation.
|
||||
|
||||
Optional:
|
||||
pose (str): Specify the pose of the annotation.
|
||||
truncated (int): Set the truncated vaule of annotation.
|
||||
difficult (int): Set difficulty index of annotation.
|
||||
|
||||
Raises:
|
||||
NotImplementedError
|
||||
jsonschema.exceptions.ValidationError
|
||||
'''
|
||||
|
||||
if self.template_voc_parameters is None:
|
||||
raise NotImplementedError("VOC has not been initialised.")
|
||||
|
||||
validate(annotation, self.schema)
|
||||
|
||||
box = annotation["region"]
|
||||
name = annotation["tagName"]
|
||||
|
||||
xmin = float(box["left"]) * self.width
|
||||
ymin = float(box["top"]) * self.height
|
||||
xmax = float(box["width"]) * self.width
|
||||
ymax = float(box["height"]) * self.height
|
||||
|
||||
self.template_voc_parameters['objects'].append({
|
||||
'name': name,
|
||||
'xmin': xmin,
|
||||
'ymin': ymin,
|
||||
'xmax': xmax,
|
||||
'ymax': ymax,
|
||||
'pose': pose,
|
||||
'truncated': truncated,
|
||||
'difficult': difficult,
|
||||
})
|
||||
|
||||
def addYoloObject(self, annotation):
|
||||
|
||||
'''
|
||||
Add object annotation to YOLO writer.
|
||||
|
||||
Args:
|
||||
annotation (dict): Annotation object used to
|
||||
generate YOLO annotation.
|
||||
|
||||
Raises:
|
||||
NotImplementedError
|
||||
jsonschema.exceptions.ValidationError
|
||||
'''
|
||||
|
||||
if self.template_yolo_parameters is None:
|
||||
raise NotImplementedError("YOLO has not been initialised.")
|
||||
|
||||
validate(annotation, self.schema)
|
||||
|
||||
box = annotation["region"]
|
||||
tag = annotation["tagId"]
|
||||
|
||||
x_centre = float(box["left"] + (box["width"] / 2))
|
||||
y_centre = float(box["top"] + (box["height"] / 2))
|
||||
x_width = float(box["width"])
|
||||
y_height = float(box["height"])
|
||||
|
||||
self.template_yolo_parameters.append({
|
||||
'id': tag,
|
||||
'x_centre': x_centre,
|
||||
'y_centre': y_centre,
|
||||
'x_width': x_width,
|
||||
'y_height': y_height
|
||||
})
|
||||
|
||||
def saveVoc(self, annotation_path):
|
||||
|
||||
'''
|
||||
Output VOC annotations to file.
|
||||
|
||||
Args:
|
||||
annotation_path (str): Path of file to write annotation to.
|
||||
|
||||
Raises:
|
||||
NotImplementedError
|
||||
'''
|
||||
|
||||
if self.template_voc_parameters is None:
|
||||
raise NotImplementedError("VOC has not been initialised.")
|
||||
|
||||
with open(annotation_path, 'w') as file:
|
||||
content = self.annotation_voc_template.render(
|
||||
**self.template_voc_parameters)
|
||||
file.write(content)
|
||||
|
||||
def saveYolo(self, annotation_path):
|
||||
|
||||
'''
|
||||
Output YOLO annotations to file.
|
||||
|
||||
Args:
|
||||
annotation_path (str): Path of file to write annotation to.
|
||||
|
||||
Raises:
|
||||
NotImplementedError
|
||||
'''
|
||||
|
||||
if self.template_yolo_parameters is None:
|
||||
raise NotImplementedError("YOLO has not been initialised.")
|
||||
|
||||
with open(annotation_path, 'w') as file:
|
||||
content = self.annotation_yolo_template.render(
|
||||
annotation=self.template_yolo_parameters)
|
||||
file.write(content)
|
|
@ -0,0 +1,11 @@
|
|||
import annotationfactory.models.region as Region
|
||||
|
||||
|
||||
class Annotation:
|
||||
tagName = None
|
||||
region = None
|
||||
|
||||
def __init__(self, tagName: str = None, region: Region = None):
|
||||
|
||||
self.tagName = tagName
|
||||
self.region = region
|
|
@ -0,0 +1,12 @@
|
|||
class Region:
|
||||
left = 0
|
||||
top = 0
|
||||
width = 0
|
||||
height = 0
|
||||
|
||||
def __init__(self, left: float, top: float, width: float, height: float):
|
||||
|
||||
self.left = left
|
||||
self.top = top
|
||||
self.width = width
|
||||
self.height = height
|
|
@ -0,0 +1,2 @@
|
|||
{% for item in annotation %}{{ item.id }} {{ item.x_centre }} {{ item.y_centre }} {{ item.x_width }} {{ item.y_height }}
|
||||
{% endfor %}
|
|
@ -0,0 +1,25 @@
|
|||
<annotation>
|
||||
<folder>{{ folder }}</folder>
|
||||
<filename>{{ filename }}</filename>
|
||||
<source>
|
||||
<database>{{ database }}</database>
|
||||
</source>
|
||||
<size>
|
||||
<width>{{ width }}</width>
|
||||
<height>{{ height }}</height>
|
||||
<depth>{{ depth }}</depth>
|
||||
</size>
|
||||
<segmented>{{ segmented }}</segmented>{% for object in objects %}
|
||||
<object>
|
||||
<name>{{ object.name }}</name>
|
||||
<pose>{{ object.pose }}</pose>
|
||||
<truncated>{{ object.truncated }}</truncated>
|
||||
<difficult>{{ object.difficult }}</difficult>
|
||||
<bndbox>
|
||||
<xmin>{{ object.xmin }}</xmin>
|
||||
<ymin>{{ object.ymin }}</ymin>
|
||||
<xmax>{{ object.xmax }}</xmax>
|
||||
<ymax>{{ object.ymax }}</ymax>
|
||||
</bndbox>
|
||||
</object>{% endfor %}
|
||||
</annotation>
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tagId": {"type": "number"},
|
||||
"tagName": {"type": "string"},
|
||||
"region": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"left": {"type": "number"},
|
||||
"top": {"type": "number"},
|
||||
"width": {"type": "number"},
|
||||
"height": {"type": "number"}
|
||||
},
|
||||
"required": ["left", "top", "width", "height"]
|
||||
}
|
||||
},
|
||||
"required": ["tagId", "tagName", "region"]
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
paths:
|
||||
include:
|
||||
- annotationfactory/*
|
||||
- azure-pipelines.yml
|
||||
|
||||
steps:
|
||||
|
||||
- task: UsePythonVersion@0
|
||||
displayName: 'Use Python >= 3.5'
|
||||
inputs:
|
||||
versionSpec: '>= 3.5'
|
||||
addToPath: true
|
||||
architecture: 'x64' # only applies to windows agents
|
||||
|
||||
- script: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools
|
||||
pip install wheel
|
||||
pip install twine
|
||||
pip install --user --upgrade setuptools wheel
|
||||
pip install --user --upgrade twine
|
||||
displayName: 'Install dependencies'
|
||||
|
||||
- script: 'python setup.py bdist_wheel -v $(build.buildId)'
|
||||
displayName: 'Build package'
|
||||
|
||||
- task: TwineAuthenticate@0
|
||||
displayName: 'Twine Authenticate'
|
||||
inputs:
|
||||
externalFeeds: PyPi
|
||||
|
||||
- script: 'twine upload -r annotationfactory --config-file $(PYPIRC_PATH) dist/*'
|
||||
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||
displayName: Upload
|
|
@ -0,0 +1 @@
|
|||
apple
|
|
@ -0,0 +1,30 @@
|
|||
from annotationfactory.annotationwriter import AnnotationWriter
|
||||
import annotationfactory.annotationconverter as converter
|
||||
|
||||
example = {
|
||||
'tagId': 0,
|
||||
'tagName': 'apples',
|
||||
'region': {
|
||||
'left': 0.288039029,
|
||||
'top': 0.411838,
|
||||
'width': 0.291451037,
|
||||
'height': 0.4237842
|
||||
}
|
||||
}
|
||||
|
||||
writer = AnnotationWriter()
|
||||
|
||||
writer.initVoc("test.jpg", 608, 608)
|
||||
writer.initYolo()
|
||||
|
||||
writer.addVocObject(example)
|
||||
writer.addVocObject(example)
|
||||
|
||||
writer.addYoloObject(example)
|
||||
writer.addYoloObject(example)
|
||||
|
||||
writer.saveVoc("myannotation.xml")
|
||||
writer.saveYolo("myannotation.txt")
|
||||
|
||||
converter.convertVocFromPath("myannotation.xml")
|
||||
converter.convertYoloFromPath("myannotation.txt", "class.names")
|
|
@ -0,0 +1,3 @@
|
|||
jinja2
|
||||
jsonschema
|
||||
xmltodict
|
|
@ -0,0 +1,35 @@
|
|||
import setuptools
|
||||
import sys
|
||||
|
||||
with open("README.md", "r", encoding="utf8") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
version = "0.0.1"
|
||||
|
||||
if '-v' in sys.argv:
|
||||
index = sys.argv.index('-v')
|
||||
sys.argv.pop(index)
|
||||
version += '.dev' + sys.argv.pop(index)
|
||||
|
||||
setuptools.setup(
|
||||
name='annotationfactory',
|
||||
version=version,
|
||||
description="annotation factory python sdk",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/Microsoft/Annotation-Factory",
|
||||
license='MIT',
|
||||
packages=setuptools.find_packages(),
|
||||
install_requires=[
|
||||
'jinja2',
|
||||
'jsonschema',
|
||||
'xmltodict'
|
||||
],
|
||||
package_data={
|
||||
'annotationfactory': ['templates/*', 'models/*']
|
||||
},
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
)
|
Загрузка…
Ссылка в новой задаче