├── requirements.txt ├── tests ├── __init__.py ├── data_dummy │ ├── books.txt │ └── annotations │ │ ├── TitleB.xml │ │ ├── TitleC_with_custom_tag.xml │ │ └── TitleA.xml ├── test_func.py └── test_data_type.py ├── MANIFEST.in ├── manga109api ├── __init__.py └── manga109api.py ├── Makefile ├── setup.py ├── LICENSE ├── .github └── workflows │ └── python-package.yml ├── .gitignore └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | -------------------------------------------------------------------------------- /tests/data_dummy/books.txt: -------------------------------------------------------------------------------- 1 | TitleA 2 | TitleB 3 | TitleC_with_custom_tag -------------------------------------------------------------------------------- /manga109api/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['Parser'] 2 | __version__ = '0.3.1' 3 | from .manga109api import Parser 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test clean build test_deploy deploy 2 | 3 | test: 4 | pytest tests/* 5 | 6 | clean: 7 | rm -rf build dist *.egg-info 8 | 9 | build: 10 | python setup.py sdist bdist_wheel 11 | 12 | test_deploy: clean build 13 | twine upload --repository-url https://test.pypi.org/legacy/ dist/* 14 | 15 | deploy: clean build 16 | twine upload dist/* 17 | -------------------------------------------------------------------------------- /tests/test_func.py: -------------------------------------------------------------------------------- 1 | import manga109api 2 | from pathlib import Path 3 | 4 | 5 | def test_img_path(): 6 | manga109_root_dir = "tests/data_dummy/" 7 | p = manga109api.Parser(root_dir=manga109_root_dir) 8 | 9 | img1 = Path(p.img_path(book="TitleA", index=0)).absolute() 10 | img2 = Path("tests/data_dummy/images/TitleA/000.jpg").absolute() 11 | assert(img1 == img2) 12 | -------------------------------------------------------------------------------- /tests/data_dummy/annotations/TitleB.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import re 3 | 4 | with open('README.md') as f: 5 | readme = f.read() 6 | 7 | with open('requirements.txt') as f: 8 | requirements = [] 9 | for line in f: 10 | requirements.append(line.rstrip()) 11 | 12 | with open('manga109api/__init__.py') as f: 13 | # Version is written in manga109api/__init__.py 14 | # This function simply reads it 15 | version = re.search(r'__version__ = \'(.*?)\'', f.read()).group(1) 16 | 17 | 18 | setup( 19 | name='manga109api', 20 | version=version, 21 | description='Simple python API to read annotation data of Manga109', 22 | long_description=readme, 23 | long_description_content_type='text/markdown', 24 | author='Yusuke Matsui', 25 | author_email='matsui528@gmail.com', 26 | url='https://github.com/manga109/manga109api', 27 | license='MIT', 28 | packages=find_packages(), 29 | install_requires=requirements, 30 | ) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Yusuke Matsui 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/data_dummy/annotations/TitleC_with_custom_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | dummy_text 13 | dummy_text2 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [macos-latest, ubuntu-latest] 19 | python-version: [3.6, 3.7, 3.8] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | pip install flake8 pytest 31 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 32 | - name: Lint with flake8 33 | run: | 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 38 | - name: Test with pytest 39 | run: | 40 | make test 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # Environments 92 | .env 93 | .venv 94 | env/ 95 | venv/ 96 | ENV/ 97 | env.bak/ 98 | venv.bak/ 99 | 100 | # Spyder project settings 101 | .spyderproject 102 | .spyproject 103 | 104 | # Rope project settings 105 | .ropeproject 106 | 107 | # mkdocs documentation 108 | /site 109 | 110 | # mypy 111 | .mypy_cache/ 112 | .dmypy.json 113 | dmypy.json 114 | -------------------------------------------------------------------------------- /tests/data_dummy/annotations/TitleA.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | え? 25 | 26 | はあ 27 | はあ 28 | 29 | 30 | 31 | 32 | 33 | よ…… 34 | わっ! 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/test_data_type.py: -------------------------------------------------------------------------------- 1 | import manga109api 2 | 3 | 4 | def test_data_type(): 5 | manga109_root_dir = "tests/data_dummy/" 6 | p = manga109api.Parser(root_dir=manga109_root_dir) 7 | 8 | for book in p.books: 9 | annotation = p.get_annotation(book=book, separate_by_tag=False) 10 | 11 | # title 12 | assert isinstance(annotation["title"], str) 13 | 14 | # character 15 | assert isinstance(annotation["character"], list) 16 | for character in annotation["character"]: 17 | assert isinstance(character["@id"], str) 18 | assert isinstance(character["@name"], str) 19 | 20 | # page 21 | assert isinstance(annotation["page"], list) 22 | for page in annotation["page"]: 23 | assert isinstance(page["@index"], int) 24 | assert isinstance(page["@width"], int) 25 | assert isinstance(page["@height"], int) 26 | 27 | assert isinstance(page["contents"], list) 28 | for obj in page["contents"]: 29 | if obj["type"] in {"body", "face", "frame", "text"}: 30 | assert isinstance(obj["@id"], str) 31 | assert isinstance(obj["@xmin"], int) 32 | assert isinstance(obj["@xmax"], int) 33 | assert isinstance(obj["@ymin"], int) 34 | assert isinstance(obj["@ymax"], int) 35 | assert isinstance(obj["type"], str) 36 | 37 | if obj["type"] == "text": 38 | assert isinstance(obj["#text"], str) 39 | 40 | # custom tag test 41 | else: 42 | assert isinstance(obj["@id"], str) 43 | assert isinstance(obj["@attr_num"], int) 44 | assert isinstance(obj["@attr_str"], str) 45 | assert isinstance(obj["@attr_mix"], str) 46 | assert isinstance(obj["type"], str) 47 | 48 | for key in (obj.keys() - {"@id", "@attr_num", "@attr_str", "@attr_mix", "type"}): 49 | assert isinstance(obj[key], (int, str)) 50 | 51 | if "#text" in obj.keys(): 52 | assert isinstance(obj["#text"], str) 53 | 54 | 55 | def test_data_type_separated(): 56 | manga109_root_dir = "tests/data_dummy/" 57 | p = manga109api.Parser(root_dir=manga109_root_dir) 58 | 59 | for book in p.books: 60 | annotation = p.get_annotation(book=book, separate_by_tag=True) 61 | 62 | # title 63 | assert isinstance(annotation["title"], str) 64 | 65 | # character 66 | assert isinstance(annotation["character"], list) 67 | for character in annotation["character"]: 68 | assert isinstance(character["@id"], str) 69 | assert isinstance(character["@name"], str) 70 | 71 | # page 72 | assert isinstance(annotation["page"], list) 73 | for page in annotation["page"]: 74 | assert isinstance(page["@index"], int) 75 | assert isinstance(page["@width"], int) 76 | assert isinstance(page["@height"], int) 77 | 78 | for obj_type in page.keys(): 79 | if obj_type in {"body", "face", "frame", "text"}: 80 | assert isinstance(page[obj_type], list) 81 | for obj in page[obj_type]: 82 | assert isinstance(obj["@id"], str) 83 | assert isinstance(obj["@xmin"], int) 84 | assert isinstance(obj["@xmax"], int) 85 | assert isinstance(obj["@ymin"], int) 86 | assert isinstance(obj["@ymax"], int) 87 | assert obj["type"] == obj_type 88 | 89 | if obj_type == "text": 90 | assert isinstance(obj["#text"], str) 91 | 92 | # custom tag test 93 | elif obj_type not in {"@index", "@width", "@height"}: 94 | for obj in page[obj_type]: 95 | assert isinstance(obj["@id"], str) 96 | assert isinstance(obj["@attr_num"], int) 97 | assert isinstance(obj["@attr_str"], str) 98 | assert isinstance(obj["@attr_mix"], str) 99 | assert obj["type"] == obj_type 100 | 101 | for key in (obj.keys() - {"@id", "@attr_num", "@attr_str", "@attr_mix", "type"}): 102 | assert isinstance(obj[key], (int, str)) 103 | 104 | if "#text" in obj.keys(): 105 | assert isinstance(obj["#text"], str) 106 | -------------------------------------------------------------------------------- /manga109api/manga109api.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import xml.etree.ElementTree as ET 3 | 4 | 5 | class Parser(object): 6 | annotation_tags = ["frame", "face", "body", "text"] 7 | 8 | def __init__(self, root_dir): 9 | """ 10 | Manga109 annotation parser 11 | 12 | Args: 13 | root_dir (str): The path of the root directory of Manga109 data, e.g., 'YOUR_PATH/Manga109_2017_09_28' 14 | """ 15 | self.root_dir = pathlib.Path(root_dir) 16 | self.books = [] # book titles 17 | 18 | with (self.root_dir / "books.txt").open("rt", encoding='utf-8') as f: 19 | self.books = [line.rstrip() for line in f] 20 | 21 | def get_annotation(self, book, annotation_type="annotations", separate_by_tag=True): 22 | """ 23 | Given a book title, return its annotations as a dict. 24 | 25 | Args: 26 | book (str): The title of the book to get the annotations of. 27 | The title must be contained in the list `self.books`. 28 | annotation_type (str) default `"annotations"` : The directory to load the xml data from. 29 | separate_by_tag (bool) default `True` : When set to `True`, each annotation data type 30 | ("frame", "face", "body", "text") will be stored in a different list in the output 31 | dictionary. When set to `False`, all of the annotation data will be stored in a 32 | single list in the output dictionary. In the latter case, the data in the list will 33 | appear in the same order as in the original XML file. 34 | 35 | Returns: 36 | annotation (dict): The annotation data 37 | """ 38 | assert book in self.books 39 | 40 | def int_literals_to_int(t): 41 | """ 42 | Convert integer literal strings to integers, 43 | if the stringified result of the integer expression 44 | matches the original string. 45 | The following keys will be affected with this function: 46 | '@index', '@width', '@height', '@xmax', '@ymax', '@xmin', '@ymin' 47 | """ 48 | try: 49 | if str(t) == str(int(t)): 50 | return int(t) # Example case: t == "42" 51 | else: 52 | return t # Example case: t == "00001234" 53 | except ValueError as e: 54 | return t # Example case: t == "some text" or t == "000012ab" 55 | 56 | def formatted_dict(d): 57 | """ 58 | - Prepends an "@" in front of each key of a given dict. 59 | - Also applies `int_literals_to_int` to each value of the given dict. 60 | Example: 61 | input: {"index": "5", "title": "a"} 62 | output: {"@index": 5, "@title": "a"} 63 | """ 64 | return dict([("@" + k, int_literals_to_int(v)) for k, v in d.items()]) 65 | 66 | with (self.root_dir / annotation_type / (book + ".xml")).open("rt", encoding='utf-8') as f: 67 | xml = ET.parse(f).getroot() 68 | annotation = {"title": xml.attrib["title"]} 69 | 70 | characters = [] 71 | try: 72 | for t in xml.find("characters"): 73 | characters.append(formatted_dict(t.attrib)) 74 | except: 75 | pass 76 | annotation["character"] = characters 77 | 78 | pages = [] 79 | for page_xml in xml.find("pages"): 80 | page = formatted_dict(page_xml.attrib) 81 | 82 | if separate_by_tag: 83 | for annotation_tag in self.annotation_tags: 84 | page[annotation_tag] = [] 85 | else: 86 | page["contents"] = [] 87 | 88 | for bb_xml in page_xml: 89 | d = formatted_dict(bb_xml.attrib) 90 | if bb_xml.text is not None: 91 | d["#text"] = bb_xml.text 92 | d["type"] = bb_xml.tag 93 | 94 | if separate_by_tag: 95 | try: 96 | page[bb_xml.tag].append(d) 97 | except: 98 | page[bb_xml.tag] = [d] 99 | else: 100 | page["contents"].append(d) 101 | 102 | pages.append(page) 103 | annotation["page"] = pages 104 | return annotation 105 | 106 | def img_path(self, book, index): 107 | """ 108 | Given a book title and an index of a page, return the correct image path 109 | 110 | Args: 111 | book (str): A title of a book. Should be in self.books. 112 | index (int): An index of a page 113 | 114 | Returns: 115 | str: A path to the selected image 116 | """ 117 | assert book in self.books 118 | assert isinstance(index, int) 119 | return str((self.root_dir / "images" / book / (str(index).zfill(3) + ".jpg")).resolve()) 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Manga109 API 2 | 3 | [![PyPI version](https://badge.fury.io/py/manga109api.svg)](https://badge.fury.io/py/manga109api) 4 | [![Downloads](https://pepy.tech/badge/manga109api)](https://pepy.tech/project/manga109api) 5 | 6 | Simple python API to read annotation data of [Manga109](http://www.manga109.org/en/). 7 | 8 | Manga109 is the largest dataset for manga (Japanese comic) images, 9 | that is made publicly available for academic research purposes with proper copyright notation. 10 | 11 | To download images/annotations of Manga109, please visit [here](http://www.manga109.org/en/download.html) and send an application via the form. 12 | After that, you will receive the password for downloading images (109 titles of manga 13 | as jpeg files) 14 | and annotations (bounding box coordinates of face, body, frame, and speech balloon with texts, 15 | in the form of XML). 16 | 17 | This package provides a simple Python API to read annotation data (i.e., parsing XML) 18 | with some utility functions such as reading an image. 19 | 20 | ## News 21 | - [Oct 6, 2020]: v0.3.0 is now available. We added [a tag-order-preserving option](https://github.com/manga109/manga109api/blob/master/manga109api/manga109api.py#L31) for `get_annotation`. See (4) in [the Example section](https://github.com/manga109/manga109api#example) for instructions. 22 | - [Aug 28, 2020]: v0.2.0 is out. [The API is drastically improved](https://github.com/matsui528/manga109api/pull/8), thanks for [@i3ear](https://github.com/i3ear)! 23 | - [Aug 28, 2020]: The repository is moved to [manga109 organization](https://github.com/manga109) 24 | 25 | ## Links 26 | - [Manga109](http://www.manga109.org/en/) 27 | - [Details of annotation data](http://www.manga109.org/en/annotations.html) 28 | - [[Matsui+, MTAP 2017]](https://link.springer.com/content/pdf/10.1007%2Fs11042-016-4020-z.pdf): The original paper for Manga109. **Please cite this if you use manga109 images** 29 | - [[Aizawa+, IEEE MultiMedia 2020]](https://arxiv.org/abs/2005.04425): The paper introducing (1) the annotation data and (2) a few examples of multimedia processing applications (detection, retrieval, and generation). **Please cite this if you use manga109 annotation data** 30 | 31 | ## Installing 32 | You can install the package via pip. The library works with Python 3.6+ on linux/MacOS 33 | ```bash 34 | pip install manga109api 35 | ``` 36 | 37 | ## Example 38 | 39 | ```python 40 | import manga109api 41 | from pprint import pprint 42 | 43 | # (0) Instantiate a parser with the root directory of Manga109 44 | manga109_root_dir = "YOUR_DIR/Manga109_2017_09_28" 45 | p = manga109api.Parser(root_dir=manga109_root_dir) 46 | 47 | 48 | # (1) Book titles 49 | print(p.books) 50 | # Output: ['ARMS', 'AisazuNihaIrarenai', 'AkkeraKanjinchou', 'Akuhamu', ... 51 | 52 | 53 | # (2) Path to an image (page). 54 | print(p.img_path(book="ARMS", index=3)) # the 4th page of "ARMS" 55 | # Output (str): YOUR_DIR/Manga109_2017_09_28/images/ARMS/003.jpg 56 | 57 | 58 | # (3) The main annotation data 59 | annotation = p.get_annotation(book="ARMS") 60 | 61 | # annotation is a dictionary. Keys are "title", "character", and "page": 62 | # - annotation["title"] : (str) Title 63 | # - annotation["character"] : (list) Characters who appear in the book 64 | # - annotation["page"] : (list) The main annotation data for each page 65 | 66 | # (3-a) title 67 | print(annotation["title"]) # Output (str): ARMS 68 | 69 | # (3-b) character 70 | pprint(annotation["character"]) 71 | # Output (list): 72 | # [{'@id': '00000003', '@name': '女1'}, 73 | # {'@id': '00000010', '@name': '男1'}, 74 | # {'@id': '00000090', '@name': 'ロボット1'}, 75 | # {'@id': '000000fe', '@name': 'エリー'}, 76 | # {'@id': '0000010a', '@name': 'ケイト'}, ... ] 77 | 78 | # (3-c) page 79 | # annotation["page"] is the main annotation data (list of pages) 80 | pprint(annotation["page"][3]) # the data of the 4th page of "ARMS" 81 | # Output (dict): 82 | # {'@height': 1170, <- Height of the img 83 | # '@index': 3, <- The page number 84 | # '@width': 1654, <- Width of the img 85 | # 'body': [{'@character': '00000003', <- Character body annotations 86 | # '@id': '00000006', 87 | # '@xmax': 1352, 88 | # '@xmin': 1229, 89 | # '@ymax': 875, 90 | # '@ymin': 709}, 91 | # {'@character': '00000003', <- character ID 92 | # '@id': '00000008', <- annotation ID (unique) 93 | # '@xmax': 1172, 94 | # '@xmin': 959, 95 | # '@ymax': 1089, 96 | # '@ymin': 820}, ... ], 97 | # 'face': [{'@character': '00000003', <- Character face annotations 98 | # '@id': '0000000a', 99 | # '@xmax': 1072, 100 | # '@xmin': 989, 101 | # '@ymax': 941, 102 | # '@ymin': 890}, 103 | # {'@character': '00000003', 104 | # '@id': '0000000d', 105 | # '@xmax': 453, 106 | # '@xmin': 341, 107 | # '@ymax': 700, 108 | # '@ymin': 615}, ... ], 109 | # 'frame': [{'@id': '00000009', <- Frame annotations 110 | # '@xmax': 1170, 111 | # '@xmin': 899, 112 | # '@ymax': 1085, 113 | # '@ymin': 585}, 114 | # {'@id': '0000000c', 115 | # '@xmax': 826, 116 | # '@xmin': 2, 117 | # '@ymax': 513, 118 | # '@ymin': 0}, ... ], 119 | # 'text': [{'#text': 'キャーッ', <- Speech annotations 120 | # '@id': '00000005', 121 | # '@xmax': 685, 122 | # '@xmin': 601, 123 | # '@ymax': 402, 124 | # '@ymin': 291}, 125 | # {'#text': 'はやく逃げないとまきぞえくっちゃう', <- Text data 126 | # '@id': '00000007', 127 | # '@xmax': 1239, 128 | # '@xmin': 1155, 129 | # '@ymax': 686, 130 | # '@ymin': 595} ... ]} 131 | 132 | # (4) Preserve the raw tag ordering in the output annotation data 133 | annotation_ordered = p.get_annotation(book="ARMS", separate_by_tag=False) 134 | 135 | # In the raw XML in the Manga109 dataset, the bounding box data in the 136 | # `page` tag is not sorted by its annotation type, and each bounding 137 | # box type appears in an arbitrary order. When the `separate_by_tag=False` 138 | # option is set, the output will preserve the ordering of each 139 | # bounding box tag in the raw XML data, mainly for data editing purposes. 140 | # Note that the ordering of the bounding box tags does not carry any 141 | # useful information about the contents of the data. 142 | 143 | # Caution: Due to the aforementioned feature, the format of the output 144 | # dictionary will differ slightly comapred to when the option is not set. 145 | 146 | # Here is an example output of the ordered data: 147 | pprint(annotation_ordered["page"][3]) # the data of the 4th page of "ARMS" 148 | # Output (dict): 149 | # {'@height': 1170, 150 | # '@index': 3, 151 | # '@width': 1654, 152 | # 'contents': [{'#text': 'キャーッ', 153 | # '@id': '00000005', 154 | # '@xmax': 685, 155 | # '@xmin': 601, 156 | # '@ymax': 402, 157 | # '@ymin': 291, 158 | # 'type': 'text'}, 159 | # {'@character': '00000003', 160 | # '@id': '00000006', 161 | # '@xmax': 1352, 162 | # '@xmin': 1229, 163 | # '@ymax': 875, 164 | # '@ymin': 709, 165 | # 'type': 'body'}, 166 | # {'#text': 'はやく逃げないとまきぞえくっちゃう', 167 | # '@id': '00000007', 168 | # '@xmax': 1239, 169 | # '@xmin': 1155, 170 | # '@ymax': 686, 171 | # '@ymin': 595, 172 | # 'type': 'text'}, ... ]} 173 | ``` 174 | 175 | 176 | ## Demo of visualization 177 | ```python 178 | import manga109api 179 | from PIL import Image, ImageDraw 180 | 181 | def draw_rectangle(img, x0, y0, x1, y1, annotation_type): 182 | assert annotation_type in ["body", "face", "frame", "text"] 183 | color = {"body": "#258039", "face": "#f5be41", 184 | "frame": "#31a9b8", "text": "#cf3721"}[annotation_type] 185 | draw = ImageDraw.Draw(img) 186 | draw.rectangle([x0, y0, x1, y1], outline=color, width=10) 187 | 188 | if __name__ == "__main__": 189 | manga109_root_dir = "YOUR_DIR/Manga109_2017_09_28" 190 | book = "ARMS" 191 | page_index = 6 192 | 193 | p = manga109api.Parser(root_dir=manga109_root_dir) 194 | annotation = p.get_annotation(book=book) 195 | img = Image.open(p.img_path(book=book, index=page_index)) 196 | 197 | for annotation_type in ["body", "face", "frame", "text"]: 198 | rois = annotation["page"][page_index][annotation_type] 199 | for roi in rois: 200 | draw_rectangle(img, roi["@xmin"], roi["@ymin"], roi["@xmax"], roi["@ymax"], annotation_type) 201 | 202 | img.save("out.jpg") 203 | ``` 204 | ![](http://yusukematsui.me/project/sketch2manga/img/manga109_api_example.png) 205 | ARMS, (c) Kato Masaki 206 | 207 | 208 | 209 | 210 | ## Maintainers 211 | - [@matsui528](https://github.com/matsui528) 212 | 213 | 214 | ## Citation 215 | When you make use of images in Manga109, please cite the following paper: 216 | 217 | @article{mtap_matsui_2017, 218 | author={Yusuke Matsui and Kota Ito and Yuji Aramaki and Azuma Fujimoto and Toru Ogawa and Toshihiko Yamasaki and Kiyoharu Aizawa}, 219 | title={Sketch-based Manga Retrieval using Manga109 Dataset}, 220 | journal={Multimedia Tools and Applications}, 221 | volume={76}, 222 | number={20}, 223 | pages={21811--21838}, 224 | doi={10.1007/s11042-016-4020-z}, 225 | year={2017} 226 | } 227 | 228 | When you use annotation data of Manga109, please cite this: 229 | 230 | @article{multimedia_aizawa_2020, 231 | author={Kiyoharu Aizawa and Azuma Fujimoto and Atsushi Otsubo and Toru Ogawa and Yusuke Matsui and Koki Tsubota and Hikaru Ikuta}, 232 | title={Building a Manga Dataset ``Manga109'' with Annotations for Multimedia Applications}, 233 | journal={IEEE MultiMedia}, 234 | volume={27}, 235 | number={2}, 236 | pages={8--18}, 237 | doi={10.1109/mmul.2020.2987895}, 238 | year={2020} 239 | } 240 | --------------------------------------------------------------------------------