Skip to content

Commit

Permalink
Merge pull request #50 from kurusugawa-computer/fix/annotation-image
Browse files Browse the repository at this point in the history
common/image.pyにアノテーションzipから画像を生成するメソッドを追加
  • Loading branch information
yuji38kwmt authored Aug 29, 2019
2 parents be7afe8 + fb3c296 commit 059d0f0
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 79 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: "[REQ][feature] Description"
labels: ''
labels: enhancement
assignees: ''

---
Expand Down
6 changes: 3 additions & 3 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 31 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,7 @@ annofabapiを使ったCLI(Command Line Interface)ツールです。


## 廃止予定

* 2019/08/31廃止予定: Pythonの最低バージョンを3.6から3.7に変更

| 廃止予定のコマンド | 廃止予定日 |代替コマンド|
|-------------------------------|----------------------------------------------------------------------------------------------------------|------------|
| print_label_color | 2019/08/09 |annotation_specs list_label_color|

なし

# Requirements
* Python 3.6+
Expand Down Expand Up @@ -89,7 +83,7 @@ $ docker run -it -e ANNOFAB_USER_ID=XXXX -e ANNOFAB_PASSWORD=YYYYY annofab-cli a
|annotation| list_count | task_idまたはinput_data_idで集約したアノテーションの個数を出力します |-|
|annotation_specs| list_label | アノテーション仕様のラベル情報を出力する |チェッカー/オーナ|
|annotation_specs| list_label_color | アノテーション仕様から、label_nameとRGBを対応付けたJSONを出力する。 |チェッカー/オーナ|
|| write_annotation_image | アノテーションzipを展開したディレクトリから、アノテーションの画像(Semantic Segmentation用)を生成する。 |-|
|filesystem| write_annotation_image | アノテーションzip、またはそれを展開したディレクトリから、アノテーションの画像(Semantic Segmentation用)を生成する。 |-|


# Usage
Expand Down Expand Up @@ -593,30 +587,37 @@ $ annofabcli annotation_specs list_label_color --project_id prj1



### write_annotation_image
アノテーションzipを展開したディレクトリから、アノテーションの画像(Semantic Segmentation用)を生成します。
アノテーション種類が矩形、ポリゴン、塗りつぶし、塗りつぶしv2のアノテーションが生成対象です。
複数のアノテーションディレクトリを指定して、画像をマージすることも可能です。ただし、各プロジェクトでtask_id, input_data_idが一致している必要があります。
### filesystem write_annotation_image
アノテーションzip、またはそれを展開したディレクトリから、アノテーションの画像(Semantic Segmentation用)を生成します。
以下のアノテーションが画像化対象です。
* 矩形
* ポリゴン
* 塗りつぶし
* 塗りつぶしv2


```
# af-annotation-xxxx ディレクトリからアノテーションの画像を生成する。タスクのstatusがcompleteのみ画像を生成する。
$ annofabcli write_annotation_image --annotation_dir af-annotation-xxxx \
--input_data_size 1280x720 \
--label_color_file label_color.json \
--output_dir output \
--task_status_complete
--image_extension png
# af-annotation-xxxx ディレクトリに、af-annotation-1、af-annotation-2ディレクトリをマージしたアノテーションの画像を生成する。
# af-annotation-xxxxに存在するすべてのタスクに対して、画像を生成する。
$ python -m annofabcli.write_semantic_segmentation_images write --annotation_dir af-annotation-xxxx \
--input_data_size 1280x720 \
--label_color_file label_color.json \
--output_dir output \
--sub_annotation_dir af-annotation-1 af-annotation-2
```
# アノテーションzipをダウンロードする。
$ annofabcli project download simple_annotation --project_id prj1 --output annotation.zip
* `label_color.json`は、`label_name`とRGBを対応付けたJSONファイルです。ファイルのフォーマットは、`annotation_specs list_label_color`の出力結果と同じです。
# label_nameとRGBを対応付けたファイルを生成する
$ annofabcli annotation_specs list_label_color --project_id prj1 --output label_color.json
# annotation.zip から、アノテーション画像を生成する
$ annofabcli filesystem write_annotation_image --annotation annotation.zip \
--image_size 1280x720 \
--label_color file://label_color.json \
--output_dir /tmp/output
# annotation.zip から、アノテーション画像を生成する。ただしタスクのステータスが"完了"で、task.txtに記載れたタスクのみ画像化する。
$ annofabcli filesystem write_annotation_image --annotation annotation.zip \
--image_size 1280x720 \
--label_color file://label_color.json \
--output_dir /tmp/output \
--task_status_complete \
--task_id file://task.txt
```

12 changes: 5 additions & 7 deletions annofabcli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import annofabcli.project.subcommand_project
import annofabcli.project_member.subcommand_project_member
import annofabcli.task.subcommand_task
import annofabcli.write_annotation_image

logger = logging.getLogger(__name__)

Expand All @@ -33,16 +32,15 @@ def main(arguments: Optional[Sequence[str]] = None):
subparsers = parser.add_subparsers(dest='command_name')

annofabcli.annotation.subcommand_annotation.add_parser(subparsers)
annofabcli.instruction.subcommand_instruction.add_parser(subparsers)
annofabcli.inspection_comment.subcommand_inspection_comment.add_parser(subparsers)
annofabcli.annotation_specs.subcommand_annotation_specs.add_parser(subparsers)
annofabcli.input_data.subcommand_input_data.add_parser(subparsers)
annofabcli.task.subcommand_task.add_parser(subparsers)
annofabcli.inspection_comment.subcommand_inspection_comment.add_parser(subparsers)
annofabcli.instruction.subcommand_instruction.add_parser(subparsers)
annofabcli.project.subcommand_project.add_parser(subparsers)
annofabcli.project_member.subcommand_project_member.add_parser(subparsers)
annofabcli.annotation_specs.subcommand_annotation_specs.add_parser(subparsers)
annofabcli.filesystem.subcommand_filesystem.add_parser(subparsers)
annofabcli.task.subcommand_task.add_parser(subparsers)

annofabcli.write_annotation_image.add_parser(subparsers)
annofabcli.filesystem.subcommand_filesystem.add_parser(subparsers)

if arguments is None:
args = parser.parse_args()
Expand Down
2 changes: 1 addition & 1 deletion annofabcli/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.5.0'
__version__ = '1.5.1'
65 changes: 63 additions & 2 deletions annofabcli/common/image.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
"""
アノテーション情報を画像化するメソッドです。
外部に公開しています。
"""
import logging
import zipfile
from pathlib import Path
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple # pylint: disable=unused-import

Expand All @@ -7,12 +12,15 @@
import PIL.ImageDraw
from annofabapi.dataclass.annotation import SimpleAnnotationDetail
from annofabapi.exceptions import AnnotationOuterFileNotFoundError
from annofabapi.parser import SimpleAnnotationParser
from annofabapi.parser import SimpleAnnotationParser, lazy_parse_simple_annotation_dir, lazy_parse_simple_annotation_zip

from annofabcli.common.typing import RGB, InputDataSize

logger = logging.getLogger(__name__)

IsParserFunc = Callable[[SimpleAnnotationParser], bool]
"""アノテーションparserに対してboolを返す関数"""


def get_data_uri_of_outer_file(annotation: SimpleAnnotationDetail) -> Optional[str]:
"""
Expand Down Expand Up @@ -122,7 +130,6 @@ def write_annotation_image(parser: SimpleAnnotationParser, image_size: InputData
Args:
parser: parser: Simple Annotationのparser
image_size: 画像のサイズ. Tuple[width, height]
parser: annotationの遅延Parser
label_color_dict: label_nameとRGBを対応付けたdict
output_image_file: 出力先の画像ファイルのパス
background_color: アノテーション画像の背景色.
Expand Down Expand Up @@ -151,3 +158,57 @@ def write_annotation_image(parser: SimpleAnnotationParser, image_size: InputData

output_image_file.parent.mkdir(parents=True, exist_ok=True)
image.save(output_image_file)


def write_annotation_images_from_path(annotation_path: Path, image_size: InputDataSize,
label_color_dict: Dict[str, RGB], output_dir_path: Path,
output_image_extension: str = "png", background_color: Optional[Any] = None,
is_target_parser_func: Optional[IsParserFunc] = None) -> bool:
"""
AnnoFabからダウンロードしたアノテーションzipファイル、またはそのzipを展開したディレクトリから、アノテーション情報を画像化します。
Args:
annotation_path: AnnoFabからダウンロードしたアノテーションzipファイル、またはそのzipを展開したディレクトリのパス
image_size: 画像のサイズ. Tuple[width, height]
label_color_dict: label_nameとRGBを対応付けたdict. Key:label_name, Value: Tuple(R,G,B)
output_dir_path: 画像の出力先のディレクトリのパス
output_image_extension: 出力する画像ファイルの拡張子. 指定しない場合は"png"です。
background_color: アノテーション画像の背景色.
(ex) "rgb(173, 216, 230)", "#add8e6", "lightgray", (173,216,230)
フォーマットは`ImageColor Module <https://hhsprings.bitbucket.io/docs/programming/examples/python/PIL/ImageColor.html>`_ # noqa: E501
'指定しない場合は、黒(rgb(0,0,0))になります。'))
is_target_parser_func: 画像化するparserかどうかを判定する関数。戻り値がTrueなら、画像化します。指定しない場合は、すべてのparserに対して画像化します。
Returns:
True: アノテーション情報の画像化に成功した。False: アノテーション情報の画像化に失敗した。
"""

if not annotation_path.exists():
logger.warning(f"annotation_path: '{annotation_path}' は存在しません。")
return False

if annotation_path.is_dir():
iter_lazy_parser = lazy_parse_simple_annotation_dir(annotation_path)
elif zipfile.is_zipfile(str(annotation_path)):
iter_lazy_parser = lazy_parse_simple_annotation_zip(annotation_path)
else:
logger.warning(f"annotation_path: '{annotation_path}' は、zipファイルまたはディレクトリではありませんでした。")
return False

count_created_image = 0
for parser in iter_lazy_parser:
logger.debug(f"{parser.json_file_path} を読み込みます。")
if is_target_parser_func is not None and not is_target_parser_func(parser):
logger.debug(f"{parser.json_file_path} の画像化をスキップします。")
continue

output_image_file = output_dir_path / f"{parser.json_file_path}.{output_image_extension}"
write_annotation_image(parser, image_size=image_size, label_color_dict=label_color_dict,
background_color=background_color, output_image_file=output_image_file)
logger.debug(f"画像ファイル '{str(output_image_file)}' を生成しました。")
count_created_image += 1

logger.info(f"{str(output_dir_path)} に、{count_created_image} 件の画像ファイルを生成しました。")
return True
File renamed without changes.
81 changes: 48 additions & 33 deletions annofabcli/filesystem/write_annotation_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@
"""

import argparse
import json
import logging
import zipfile
from pathlib import Path
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple # pylint: disable=unused-import

from annofabapi.models import TaskStatus
from annofabapi.parser import SimpleAnnotationParser, lazy_parse_simple_annotation_dir, lazy_parse_simple_annotation_zip
from annofabapi.parser import SimpleAnnotationParser

import annofabcli
import annofabcli.common.cli
from annofabcli.common.cli import ArgumentParser
from annofabcli.common.image import write_annotation_image
from annofabcli.common.image import IsParserFunc, write_annotation_image, write_annotation_images_from_path
from annofabcli.common.typing import RGB, InputDataSize

logger = logging.getLogger(__name__)

zipfile.is_zipfile("ab")


class WriteAnnotationImage:
@staticmethod
Expand Down Expand Up @@ -48,40 +50,53 @@ def write_annotation_images(iter_lazy_parser: Iterator[SimpleAnnotationParser],

logger.debug(f"{str(output_image_file)} の生成完了.")

@staticmethod
def create_is_target_parser_func(task_status_complete: bool = False,
task_id_list: Optional[List[str]] = None) -> IsParserFunc:
def is_target_parser(parser: SimpleAnnotationParser) -> bool:
simple_annotation = parser.parse()
if task_status_complete:
if simple_annotation.task_status != TaskStatus.COMPLETE:
logger.debug(f"task_statusがcompleteでない( {simple_annotation.task_status.value})ため、"
f"{simple_annotation.task_id}, {simple_annotation.input_data_name} はスキップします。")
return False

if task_id_list is not None and len(task_id_list) > 0:
if simple_annotation.task_id not in task_id_list:
logger.debug(
f"画像化対象外のタスク {simple_annotation.task_id} であるため、 {simple_annotation.input_data_name} はスキップします。")
return False

return True

return is_target_parser

def main(self, args):
annofabcli.common.cli.load_logging_config_from_args(args)
logger.info(f"args: {args}")

default_input_data_size = annofabcli.common.cli.get_input_data_size(args.input_data_size)
if default_input_data_size is None:
logger.error("--default_input_data_size のフォーマットが不正です")
image_size = annofabcli.common.cli.get_input_data_size(args.image_size)
if image_size is None:
logger.error("--image_size のフォーマットが不正です")
return

try:
with open(args.label_color_file) as f:
label_color_dict = json.load(f)
label_color_dict = {k: tuple(v) for k, v in label_color_dict.items()}

except Exception as e:
logger.error("--label_color_json_file のJSON Parseに失敗しました。")
raise e
# label_color_dict を取得する
label_color_dict = annofabcli.common.cli.get_json_from_args(args.label_color)
label_color_dict = {k: tuple(v) for k, v in label_color_dict.items()}

annotation_path = Path(args.annotation)
if annotation_path.is_dir():
iter_lazy_parser = lazy_parse_simple_annotation_dir(annotation_path)
elif annotation_path.suffix.lower() == ".zip":
iter_lazy_parser = lazy_parse_simple_annotation_zip(annotation_path)
else:
logger.error("--annotation には、アノテーションzip、またはzipを展開したディレクトリのパスを渡してください。")
return

task_id_list = annofabcli.common.cli.get_list_from_args(args.task_id)
is_target_parser_func = self.create_is_target_parser_func(args.task_status_complete, task_id_list)

self.write_annotation_images(iter_lazy_parser=iter_lazy_parser, default_input_data_size=default_input_data_size,
label_color_dict=label_color_dict, output_dir=Path(args.output_dir),
output_image_extension=args.image_extension,
task_status_complete=args.task_status_complete, task_id_list=task_id_list,
background_color=args.background_color)
# 画像生成
result = write_annotation_images_from_path(annotation_path, image_size=image_size,
label_color_dict=label_color_dict,
output_dir_path=Path(args.output_dir),
output_image_extension=args.image_extension,
background_color=args.background_color,
is_target_parser_func=is_target_parser_func)
if not result:
logger.error(f"'{annotation_path}' のアノテーション情報の画像化に失敗しました。")


def main(args):
Expand All @@ -93,10 +108,12 @@ def parse_args(parser: argparse.ArgumentParser):

parser.add_argument('--annotation', type=str, required=True, help='アノテーションzip、またはzipを展開したディレクトリ')

parser.add_argument('--input_data_size', type=str, required=True, help='入力データ画像のサイズ。{width}x{height}。ex. 1280x720')
parser.add_argument('--image_size', type=str, required=True, help='入力データ画像のサイズ。{width}x{height}。ex) 1280x720')

parser.add_argument('--label_color_file', type=str, required=True,
help='label_nameとRGBを対応付けたJSONファイルのパス. key: label_name, value:[R,G,B]')
parser.add_argument(
'--label_color', type=str, required=True,
help='label_nameとRGBの関係をJSON形式で指定します。ex) `{"dog":[255,128,64], "cat":[0,0,255]}`'
'`file://`を先頭に付けると、JSON形式のファイルを指定できます。')

parser.add_argument('--output_dir', type=str, required=True, help='出力ディレクトリのパス')

Expand Down Expand Up @@ -126,9 +143,7 @@ def add_parser(subparsers: argparse._SubParsersAction):

subcommand_help = "アノテーションzipを展開したディレクトリから、アノテーションの画像(Semantic Segmentation用)を生成する。"

description = ("アノテーションzipを展開したディレクトリから、アノテーションの画像(Semantic Segmentation用)を生成する。"
"矩形、ポリゴン、塗りつぶし、塗りつぶしv2が対象。"
"複数のアノテーションディレクトリを指定して、画像をマージすることもできる。")
description = ("アノテーションzipを展開したディレクトリから、アノテーションの画像(Semantic Segmentation用)を生成する。" "矩形、ポリゴン、塗りつぶし、塗りつぶしv2が対象。")

parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description)
parse_args(parser)
Loading

0 comments on commit 059d0f0

Please sign in to comment.