From 8cd3b876d825f2c373a0d785c20821c77f13a0aa Mon Sep 17 00:00:00 2001 From: Katsuya Ishiyama Date: Fri, 16 Feb 2018 02:12:10 +0900 Subject: [PATCH 01/31] fix import error --- i2v/chainer_i2v.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/i2v/chainer_i2v.py b/i2v/chainer_i2v.py index 30ee815f..388500ce 100644 --- a/i2v/chainer_i2v.py +++ b/i2v/chainer_i2v.py @@ -2,11 +2,16 @@ import json import warnings import numpy as np +from distutils.version import StrictVersion from scipy.ndimage import zoom from skimage.transform import resize +import chainer from chainer import Variable from chainer.functions import average_pooling_2d, sigmoid -from chainer.functions.caffe import CaffeFunction +try: + from chainer.functions.caffe import CaffeFunction +except: + from chainer.links.caffe import CaffeFunction class ChainerI2V(Illustration2VecBase): @@ -47,7 +52,14 @@ def _forward(self, inputs, layername): input_ -= self.mean # subtract mean input_ = input_.transpose((0, 3, 1, 2)) # (N, H, W, C) -> (N, C, H, W) x = Variable(input_) - y, = self.net(inputs={'data': x}, outputs=[layername], train=False) + + # train argument is not supported from Ver2. + if StrictVersion(chainer.__version__) < StrictVersion('2.0.0'): + y, = self.net(inputs={'data': x}, outputs=[layername], train=False) + else: + chainer.using_config('train', False) + y, = self.net(inputs={'data': x}, outputs=[layername]) + return y def _extract(self, inputs, layername): From d0f3da62023b033a31fd59106a27e829fda01123 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Fri, 1 Jun 2018 20:13:40 +0800 Subject: [PATCH 02/31] new: dev: required package --- requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..d7812b67 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +chainer==4.1.0 +ipython==6.4.0 +numpy==1.14.3 +Pillow==5.1.0 +scikit-image==0.14.0 From 630756684b7c1f510e3059e862d8e878f8bae544 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Fri, 1 Jun 2018 20:30:52 +0800 Subject: [PATCH 03/31] new: dev: setup.py --- setup.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..7ef15835 --- /dev/null +++ b/setup.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +from setuptools import setup, find_packages + +with open('README.md') as f: + long_description = f.read() + + +setup( + name="illustration2vec", + version="0.1.0", + packages=find_packages(), + install_requires=[ + 'chainer>=4.1.0', + 'click>=6.7', + 'numpy>=1.14.3', + 'Pillow>=5.1.0', + 'scikit-image>=0.14.0', + ], + author="rezoo", + author_email="rezoolab@gmail.com", + maintainer="Rachmadani Haryono", + maintainer_email="foreturiga@gmail.com", + description="A simple deep learning library for estimating a set of tags " + "and extracting semantic feature vectors from given illustrations.", + license="MIT License", + keywords="machine learning tag image illustration", + url="https://github.com/rezoo/illustration2vec/", # project home page, if any + project_urls={ + "Bug Tracker": "https://github.com/rezoo/illustration2vec/issues", + }, + long_description=long_description, + zip_safe=True, + entry_points={'console_scripts': ['i2v = i2v.__main__:cli', ],}, + extras_require={ + 'dev': [ + 'pdbpp>=0.9.2', + 'ipython>=6.4.0', + ], + }, +) From e02afbf264a75427d5f065f1269001518feec9a6 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Fri, 1 Jun 2018 21:04:41 +0800 Subject: [PATCH 04/31] chg: dev: move required package to setup.py --- requirements.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index d7812b67..9c558e35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1 @@ -chainer==4.1.0 -ipython==6.4.0 -numpy==1.14.3 -Pillow==5.1.0 -scikit-image==0.14.0 +. From 8d052e0f1bc2660384d63aa4a9e6503cdc896a3b Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Fri, 1 Jun 2018 21:05:04 +0800 Subject: [PATCH 05/31] chg: dev: main file --- i2v/__main__.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 i2v/__main__.py diff --git a/i2v/__main__.py b/i2v/__main__.py new file mode 100644 index 00000000..f5af93c5 --- /dev/null +++ b/i2v/__main__.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +from pprint import pprint +import hashlib + +from PIL import Image +import click + +from . import make_i2v_with_chainer + +@click.group() +@click.version_option() +def cli(): + pass + +@cli.command() +@click.option('--output', default='default') +@click.argument('images', nargs=-1) +def estimate_plausible_tags(images, output='default'): + """Estimate plausible tags.""" + illust2vec = make_i2v_with_chainer( + "illust2vec_tag_ver200.caffemodel", "tag_list.json") + image_sets = map(lambda x: {'value': x, 'sha256':sha256_checksum(x)}, images) + for idx, img_set in enumerate(image_sets): + img = Image.open(img_set['value']) + res = illust2vec.estimate_plausible_tags([img], threshold=0.5) + if isinstance(res[idx]['rating'], zip): + res[idx]['rating'] = list(res[idx]['rating']) + print("image: {}\nsha256: {}".format( + img_set['value'], img_set['sha256'])) + if output == 'pprint': + pprint(res) + else: + print(res) + + +def sha256_checksum(filename, block_size=65536): + sha256 = hashlib.sha256() + with open(filename, 'rb') as f: + for block in iter(lambda: f.read(block_size), b''): + sha256.update(block) + return sha256.hexdigest() + + +if __name__ == '__main__': + cli() From c8155954ad627675a79c3a87a5a25d6d07247352 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Fri, 1 Jun 2018 21:19:38 +0800 Subject: [PATCH 06/31] new: dev: config for markdown pypi --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7ef15835..5a75e4b1 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ "Bug Tracker": "https://github.com/rezoo/illustration2vec/issues", }, long_description=long_description, + long_description_content_type='text/markdown', zip_safe=True, entry_points={'console_scripts': ['i2v = i2v.__main__:cli', ],}, extras_require={ From 0da56e3084c886fb9f83547cf832d22f4d0d31df Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Fri, 1 Jun 2018 21:41:40 +0800 Subject: [PATCH 07/31] fix: doc: v2.0.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5a75e4b1..e69e274e 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="illustration2vec", - version="0.1.0", + version="2.0.1", packages=find_packages(), install_requires=[ 'chainer>=4.1.0', From c20a638cdbe179d79277eeb57d7692e399603fc7 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Mon, 11 Jun 2018 13:57:08 +0800 Subject: [PATCH 08/31] new: dev: add flask integration --- i2v/__main__.py | 44 +++++++++++++++++++++++++++++++++++++++++--- setup.py | 6 ++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/i2v/__main__.py b/i2v/__main__.py index f5af93c5..3e7d3838 100644 --- a/i2v/__main__.py +++ b/i2v/__main__.py @@ -1,17 +1,55 @@ #!/usr/bin/env python from pprint import pprint import hashlib +import sys +from flask import Flask, __version__ as flask_version +from flask.cli import FlaskGroup +from flask_admin import Admin from PIL import Image import click -from . import make_i2v_with_chainer +from . import make_i2v_with_chainer, views -@click.group() -@click.version_option() + +__version__ = '0.2.1' + + +class CustomFlaskGroup(FlaskGroup): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.params[0].help = 'Show the program version' + self.params[0].callback = get_custom_version + + +def get_custom_version(ctx, param, value): + if not value or ctx.resilient_parsing: + return + message = 'Illustration2Vec %(app_version)s\nFlask %(version)s\nPython %(python_version)s' + click.echo(message % { + 'app_version': __version__, + 'version': flask_version, + 'python_version': sys.version, + }, color=ctx.color) + ctx.exit() + + +def create_app(): + app = Flask(__name__) + # other setup + admin = Admin( + app, name='Youtube-beets', template_mode='bootstrap3', url='/', + index_view=views.HomeView() + ) + return app + + +@click.group(cls=CustomFlaskGroup, create_app=create_app) def cli(): + """Illustration2Vec.""" pass + @cli.command() @click.option('--output', default='default') @click.argument('images', nargs=-1) diff --git a/setup.py b/setup.py index e69e274e..f1b9a96e 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,12 @@ install_requires=[ 'chainer>=4.1.0', 'click>=6.7', + 'Flask-Admin==1.5.1', + 'Flask-Migrate==2.1.1', + 'flask-shell-ipython==0.3.0', + 'Flask-SQLAlchemy==2.3.2', + 'Flask-WTF==0.14.2', + 'Flask==1.0.2', 'numpy>=1.14.3', 'Pillow>=5.1.0', 'scikit-image>=0.14.0', From 891c78b936e30fc6370ca60e9d014b32eeb734e6 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Mon, 11 Jun 2018 15:08:36 +0800 Subject: [PATCH 09/31] chg: dev: update - databse - image upload - models - bare template for home - views --- i2v/__main__.py | 29 ++++++++++++++++---- i2v/models.py | 53 +++++++++++++++++++++++++++++++++++++ i2v/templates/i2v/home.html | 7 +++++ i2v/views.py | 28 ++++++++++++++++++++ 4 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 i2v/models.py create mode 100644 i2v/templates/i2v/home.html create mode 100644 i2v/views.py diff --git a/i2v/__main__.py b/i2v/__main__.py index 3e7d3838..135d2ef8 100644 --- a/i2v/__main__.py +++ b/i2v/__main__.py @@ -1,15 +1,17 @@ #!/usr/bin/env python from pprint import pprint import hashlib +import os +import os.path as op import sys -from flask import Flask, __version__ as flask_version +from flask import Flask, __version__ as flask_version, send_from_directory from flask.cli import FlaskGroup from flask_admin import Admin from PIL import Image import click -from . import make_i2v_with_chainer, views +from . import make_i2v_with_chainer, views, models __version__ = '0.2.1' @@ -36,11 +38,28 @@ def get_custom_version(ctx, param, value): def create_app(): app = Flask(__name__) + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = 'True' + app.config['DATABASE_FILE'] = 'i2v.sqlite' + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE'] + app.config['SECRET_KEY'] = os.getenv('ILLUSTRATION2VEC_SECRET_KEY') or os.urandom(24) + # Create directory for file fields to use + try: + os.mkdir(models.file_path) + app.logger.debug('File path created') + except OSError: + pass + # app and db + models.db.init_app(app) + app.app_context().push() + models.db.create_all() # other setup admin = Admin( - app, name='Youtube-beets', template_mode='bootstrap3', url='/', - index_view=views.HomeView() + app, name='Illustration2Vec', template_mode='bootstrap3', + index_view=views.HomeView(url='/') ) + admin.add_view(views.ImageView(models.Image, models.db.session)) + app.add_url_rule('/file/', 'file', view_func=lambda filename: send_from_directory(models.file_path, filename)) + app.logger.debug('file path: {}'.format(models.file_path)) return app @@ -51,7 +70,7 @@ def cli(): @cli.command() -@click.option('--output', default='default') +@click.option('--output', help='Output format;[default]/pprint', default='default') @click.argument('images', nargs=-1) def estimate_plausible_tags(images, output='default'): """Estimate plausible tags.""" diff --git a/i2v/models.py b/i2v/models.py new file mode 100644 index 00000000..fef48abf --- /dev/null +++ b/i2v/models.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Model module.""" +import os +import os.path as op +from datetime import datetime + +from appdirs import user_data_dir +from flask_admin import form +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy.event import listens_for +from sqlalchemy.types import TIMESTAMP + + +db = SQLAlchemy() +file_path = op.join(user_data_dir('Illustration2Vec', 'Masaki Saito'), 'files') + + +class Base(db.Model): + __abstract__ = True + id = db.Column(db.Integer, primary_key=True) + created_at = db.Column(TIMESTAMP, default=datetime.now, nullable=False) + + +class Image(Base): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + path = db.Column(db.String) + checksum_id = db.Column(db.Integer, db.ForeignKey('checksum.id')) + checksum = db.relationship( + 'Checksum', foreign_keys='Image.checksum_id', lazy='subquery', + backref=db.backref('images', lazy=True, cascade='delete')) + + def __repr__(self): + return self.name + + +@listens_for(Image, 'after_delete') +def del_image(mapper, connection, target): + if target.path: + # Delete image + try: + os.remove(op.join(file_path, target.path)) + except OSError: + pass + # Delete thumbnail + try: + os.remove(op.join(file_path, form.thumbgen_filename(target.path))) + except OSError: + pass + + +class Checksum(Base): + value = db.Column(db.String, unique=True) diff --git a/i2v/templates/i2v/home.html b/i2v/templates/i2v/home.html new file mode 100644 index 00000000..947ff1d5 --- /dev/null +++ b/i2v/templates/i2v/home.html @@ -0,0 +1,7 @@ +{% extends 'admin/master.html' %} +{% block head %} + +{% endblock %} +{% block body %} +

hello world

+{% endblock %} diff --git a/i2v/views.py b/i2v/views.py new file mode 100644 index 00000000..7d9057b2 --- /dev/null +++ b/i2v/views.py @@ -0,0 +1,28 @@ +from flask_admin import AdminIndexView, expose, BaseView, form +from flask_admin.contrib.sqla import ModelView +from jinja2 import Markup +from flask import url_for + +from . import models + + +class HomeView(AdminIndexView): + + @expose('/') + def index(self): + return self.render('i2v/home.html') + + +class ImageView(ModelView): + + def _list_thumbnail(view, context, model, name): + if not model.path: + return '' + return Markup('' % url_for( + 'file', filename=form.thumbgen_filename(model.path))) + + column_formatters = {'path': _list_thumbnail} + form_extra_fields = { + 'path': form.ImageUploadField( + 'Image', base_path=models.file_path, thumbnail_size=(100, 100, True)) + } From 9389b745beb5a96709012c048bbed204ac305bdc Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Thu, 14 Jun 2018 15:33:28 +0800 Subject: [PATCH 10/31] new: dev: update - plausible tag page - move sha256_checksum func - Image - full path property - update_checksum func - thumbgen_filename property - checksum - update_plausible_tag_estimation method - new repr - PlausibleTagEstimation model - get_or_create_tag func - tag and namespace model - image view --- i2v/__main__.py | 10 +-- i2v/models.py | 85 +++++++++++++++++++++- i2v/templates/i2v/image_plausible_tag.html | 62 ++++++++++++++++ i2v/views.py | 59 ++++++++++++++- 4 files changed, 202 insertions(+), 14 deletions(-) create mode 100644 i2v/templates/i2v/image_plausible_tag.html diff --git a/i2v/__main__.py b/i2v/__main__.py index 135d2ef8..a2cc3b0b 100644 --- a/i2v/__main__.py +++ b/i2v/__main__.py @@ -76,7 +76,7 @@ def estimate_plausible_tags(images, output='default'): """Estimate plausible tags.""" illust2vec = make_i2v_with_chainer( "illust2vec_tag_ver200.caffemodel", "tag_list.json") - image_sets = map(lambda x: {'value': x, 'sha256':sha256_checksum(x)}, images) + image_sets = map(lambda x: {'value': x, 'sha256': models.sha256_checksum(x)}, images) for idx, img_set in enumerate(image_sets): img = Image.open(img_set['value']) res = illust2vec.estimate_plausible_tags([img], threshold=0.5) @@ -90,13 +90,5 @@ def estimate_plausible_tags(images, output='default'): print(res) -def sha256_checksum(filename, block_size=65536): - sha256 = hashlib.sha256() - with open(filename, 'rb') as f: - for block in iter(lambda: f.read(block_size), b''): - sha256.update(block) - return sha256.hexdigest() - - if __name__ == '__main__': cli() diff --git a/i2v/models.py b/i2v/models.py index fef48abf..d4134e3b 100644 --- a/i2v/models.py +++ b/i2v/models.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 """Model module.""" +from datetime import datetime +import hashlib import os import os.path as op -from datetime import datetime from appdirs import user_data_dir from flask_admin import form @@ -23,7 +24,6 @@ class Base(db.Model): class Image(Base): id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String) path = db.Column(db.String) checksum_id = db.Column(db.Integer, db.ForeignKey('checksum.id')) checksum = db.relationship( @@ -31,7 +31,28 @@ class Image(Base): backref=db.backref('images', lazy=True, cascade='delete')) def __repr__(self): - return self.name + return ''.format(self) + + @property + def full_path(self): + return os.path.join(file_path, self.path) + + def update_checksum(self, session=None): + session = db.session if session is None else session + checksum_val = sha256_checksum(self.full_path) + self.checksum = get_or_create(session, Checksum, value=checksum_val)[0] + + @property + def thumbgen_filename(self): + return form.thumbgen_filename(self.path) + + +def sha256_checksum(filename, block_size=65536): + sha256 = hashlib.sha256() + with open(filename, 'rb') as f: + for block in iter(lambda: f.read(block_size), b''): + sha256.update(block) + return sha256.hexdigest() @listens_for(Image, 'after_delete') @@ -51,3 +72,61 @@ def del_image(mapper, connection, target): class Checksum(Base): value = db.Column(db.String, unique=True) + + def update_plausible_tag_estimation(self, plausible_tags, session=None): + session = db.session if session is None else session + for nm, list_value in plausible_tags.items(): + if list_value: + for tag_value, estimation_value in list_value: + tag_model = get_or_create_tag(value=tag_value, namespace=nm, session=session) + e_item = get_or_create( + session, PlausibleTagEstimation, + checksum=self, tag=tag_model, value=estimation_value)[0] + yield e_item + + def __repr__(self): + return ''.format(self) + + +class PlausibleTagEstimation(Base): + checksum_id = db.Column(db.Integer, db.ForeignKey('checksum.id')) + checksum = db.relationship( + 'Checksum', foreign_keys='PlausibleTagEstimation.checksum_id', lazy='subquery', + backref=db.backref('plausible_tag_estimations', lazy=True)) + tag_id = db.Column(db.Integer, db.ForeignKey('tag.id')) + tag = db.relationship( + 'Tag', foreign_keys='PlausibleTagEstimation.tag_id', lazy='subquery', + backref=db.backref('plausible_tag_estimations', lazy=True)) + value = db.Column(db.Float) + + +def get_or_create_tag(value, namespace=None, session=None): + session = db.session if session is None else session + namespace_model = None + if namespace: + namespace_model = get_or_create(session, namespace, value=namespace)[0] + model, created = get_or_create(session, Tag, value=value, namespace=namespace_model) + return model, created + + +class Tag(Base): + value = db.Column(db.String) + namespace_id = db.Column(db.Integer, db.ForeignKey('namespace.id')) + namespace = db.relationship( + 'Namespace', foreign_keys='Tag.namespace_id', lazy='subquery', + backref=db.backref('tags', lazy=True)) + + +class Namespace(Base): + value = db.Column(db.String, unique=True) + + +def get_or_create(session, model, **kwargs): + """Creates an object or returns the object if exists.""" + instance = session.query(model).filter_by(**kwargs).first() + created = False + if not instance: + instance = model(**kwargs) + session.add(instance) + created = True + return instance, created diff --git a/i2v/templates/i2v/image_plausible_tag.html b/i2v/templates/i2v/image_plausible_tag.html new file mode 100644 index 00000000..50ee2a0f --- /dev/null +++ b/i2v/templates/i2v/image_plausible_tag.html @@ -0,0 +1,62 @@ +{% extends 'admin/master.html' %} +{% block head %} + +{% endblock %} +{% block body %} +
+ +
+ +
+ + {% for tag_namespace, tag_list in plausible_tags.items() %} + + + + + + {% for tag, tag_confidence in tag_list %} + + + + + + {% endfor %} + {% endfor %} +
#{{tag_namespace|capitalize}} TagConfidence
{{loop.index}}{{tag}} +
+
+ {{'%0.1f' | format(tag_confidence*100)}} +
+
+
+
+ + +{% endblock %} +{% block tail_js %} +{{super()}} + +{% endblock %} diff --git a/i2v/views.py b/i2v/views.py index 7d9057b2..da403666 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -1,9 +1,13 @@ +from flask import url_for, request from flask_admin import AdminIndexView, expose, BaseView, form from flask_admin.contrib.sqla import ModelView +from flask_admin.helpers import get_redirect_target +from flask_admin.model.helpers import get_mdict_item_or_list from jinja2 import Markup -from flask import url_for +from PIL import Image from . import models +from . import make_i2v_with_chainer class HomeView(AdminIndexView): @@ -21,8 +25,59 @@ def _list_thumbnail(view, context, model, name): return Markup('' % url_for( 'file', filename=form.thumbgen_filename(model.path))) - column_formatters = {'path': _list_thumbnail} + column_formatters = { + 'path': _list_thumbnail, + 'checksum': lambda v, c, m, n: m.checksum.value[:7] if m.checksum else '', + } form_extra_fields = { 'path': form.ImageUploadField( 'Image', base_path=models.file_path, thumbnail_size=(100, 100, True)) } + can_view_details = True + create_modal = True + + @expose('/plausible-tag') + def plausible_tag_view(self): + return_url = get_redirect_target() or self.get_url('.index_view') + id = get_mdict_item_or_list(request.args, 'id') + if id is None: + return redirect(return_url) + model = self.get_one(id) + if model is None: + flash(gettext('Record does not exist.'), 'error') + return redirect(return_url) + if False and not model.checksum.plausible_tag_estimations: + img = Image.open(model.full_path) + # illust2vec = make_i2v_with_chainer( + # "illust2vec_tag_ver200.caffemodel", "tag_list.json") + # res = illust2vec.estimate_plausible_tags([img], threshold=0.5) + res = illust2vec.estimate_plausible_tags([img]) + res = res[0] + tags = model.checksum.update_plausible_tag_estimation(res) + session = models.db.session + list(map(session.add, tags)) + session.commit() + else: + res = [{ + 'character': [], + 'copyright': [], + 'general': [ + ('1girl', 0.9720268249511719), + ('blue eyes', 0.9339820146560669), + ('blonde hair', 0.8899785876274109), + ('solo', 0.8786220550537109), + ('long hair', 0.832091748714447), + ('hair ornament', 0.39239054918289185)], + 'rating': [ + ('safe', 0.9953395128250122), + ('questionable', 0.003477811813354492), + ('explicit', 0.00037872791290283203)] + }] + plausible_tags = res[0] + return self.render('i2v/image_plausible_tag.html', plausible_tags=plausible_tags, model=model) + + def after_model_change(self, form, model, is_created): + if is_created: + session = models.db.session + model.update_checksum(session) + session.commit() From 034b97a163cc8313fc24a8a72c1b26008656c304 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Thu, 14 Jun 2018 16:12:43 +0800 Subject: [PATCH 11/31] new: dev: cached estimation result --- i2v/models.py | 17 +++++++++++++---- i2v/views.py | 26 +++++--------------------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/i2v/models.py b/i2v/models.py index d4134e3b..547e5d32 100644 --- a/i2v/models.py +++ b/i2v/models.py @@ -78,7 +78,8 @@ def update_plausible_tag_estimation(self, plausible_tags, session=None): for nm, list_value in plausible_tags.items(): if list_value: for tag_value, estimation_value in list_value: - tag_model = get_or_create_tag(value=tag_value, namespace=nm, session=session) + tag_model = get_or_create_tag( + value=str(tag_value), namespace=nm, session=session)[0] e_item = get_or_create( session, PlausibleTagEstimation, checksum=self, tag=tag_model, value=estimation_value)[0] @@ -87,6 +88,13 @@ def update_plausible_tag_estimation(self, plausible_tags, session=None): def __repr__(self): return ''.format(self) + def get_plausible_tags(self): + res = {'character': [], 'copyright': [], 'general': [], 'rating': []} + for estimation in self.plausible_tag_estimations: + res.setdefault(estimation.tag.namespace.value, []).append( + (estimation.tag.value, estimation.value)) + return res + class PlausibleTagEstimation(Base): checksum_id = db.Column(db.Integer, db.ForeignKey('checksum.id')) @@ -102,10 +110,11 @@ class PlausibleTagEstimation(Base): def get_or_create_tag(value, namespace=None, session=None): session = db.session if session is None else session - namespace_model = None + kwargs = dict(value=value) if namespace: - namespace_model = get_or_create(session, namespace, value=namespace)[0] - model, created = get_or_create(session, Tag, value=value, namespace=namespace_model) + namespace_model = get_or_create(session, Namespace, value=namespace)[0] + kwargs['namespace'] = namespace_model + model, created = get_or_create(session, Tag, **kwargs) return model, created diff --git a/i2v/views.py b/i2v/views.py index da403666..8c1729a4 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -35,6 +35,7 @@ def _list_thumbnail(view, context, model, name): } can_view_details = True create_modal = True + form_excluded_columns = ('checksum', 'created_at') @expose('/plausible-tag') def plausible_tag_view(self): @@ -46,34 +47,17 @@ def plausible_tag_view(self): if model is None: flash(gettext('Record does not exist.'), 'error') return redirect(return_url) - if False and not model.checksum.plausible_tag_estimations: + if not model.checksum.plausible_tag_estimations: img = Image.open(model.full_path) - # illust2vec = make_i2v_with_chainer( - # "illust2vec_tag_ver200.caffemodel", "tag_list.json") - # res = illust2vec.estimate_plausible_tags([img], threshold=0.5) + illust2vec = make_i2v_with_chainer( + "illust2vec_tag_ver200.caffemodel", "tag_list.json") res = illust2vec.estimate_plausible_tags([img]) res = res[0] tags = model.checksum.update_plausible_tag_estimation(res) session = models.db.session list(map(session.add, tags)) session.commit() - else: - res = [{ - 'character': [], - 'copyright': [], - 'general': [ - ('1girl', 0.9720268249511719), - ('blue eyes', 0.9339820146560669), - ('blonde hair', 0.8899785876274109), - ('solo', 0.8786220550537109), - ('long hair', 0.832091748714447), - ('hair ornament', 0.39239054918289185)], - 'rating': [ - ('safe', 0.9953395128250122), - ('questionable', 0.003477811813354492), - ('explicit', 0.00037872791290283203)] - }] - plausible_tags = res[0] + plausible_tags = model.checksum.get_plausible_tags() return self.render('i2v/image_plausible_tag.html', plausible_tags=plausible_tags, model=model) def after_model_change(self, form, model, is_created): From 5afc3f63b6f141451b824899be8263720aa8b60a Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Thu, 14 Jun 2018 16:30:53 +0800 Subject: [PATCH 12/31] new: dev: add link to tag estimation --- i2v/views.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/i2v/views.py b/i2v/views.py index 8c1729a4..a89b8ab1 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -20,10 +20,15 @@ def index(self): class ImageView(ModelView): def _list_thumbnail(view, context, model, name): + res_templ = 'Tag' + res = res_templ.format(url_for( + '.plausible_tag_view', id=model.id)) + res += '
' if not model.path: - return '' - return Markup('' % url_for( - 'file', filename=form.thumbgen_filename(model.path))) + return Markup(res) + res += '' % url_for( + 'file', filename=form.thumbgen_filename(model.path)) + return Markup(res) column_formatters = { 'path': _list_thumbnail, From ceb8e9b6aa36a510d7c21f4af28b0a785f86c856 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Thu, 14 Jun 2018 22:07:09 +0800 Subject: [PATCH 13/31] chg: dev; filename generation --- i2v/views.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/i2v/views.py b/i2v/views.py index a89b8ab1..885b025c 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -1,3 +1,7 @@ +import hashlib +import os +import shutil + from flask import url_for, request from flask_admin import AdminIndexView, expose, BaseView, form from flask_admin.contrib.sqla import ModelView @@ -5,11 +9,16 @@ from flask_admin.model.helpers import get_mdict_item_or_list from jinja2 import Markup from PIL import Image +from werkzeug import secure_filename +import structlog from . import models from . import make_i2v_with_chainer +logger = structlog.getLogger(__name__) + + class HomeView(AdminIndexView): @expose('/') @@ -66,7 +75,33 @@ def plausible_tag_view(self): return self.render('i2v/image_plausible_tag.html', plausible_tags=plausible_tags, model=model) def after_model_change(self, form, model, is_created): + def get_new_filename(src_filename, no_ext_basename=None, new_basename=None): + assert not (no_ext_basename and new_basename) + basename = os.path.basename(src_filename) + if no_ext_basename: + ext = os.path.splitext(basename)[1] + new_basename = '{}{}'.format(no_ext_basename, ext) + new_full_path = os.path.join(os.path.dirname(src_filename), new_basename) + return new_full_path + if is_created: session = models.db.session model.update_checksum(session) + # old path + full_path = model.full_path + thumbgen_filename = model.thumbgen_filename + # + model.path = '{}{}'.format( + model.checksum.value, os.path.splitext(full_path)[1]) + shutil.move(full_path, model.full_path) + new_thumbgen_filename = model.thumbgen_filename + try: + shutil.move( + os.path.join(models.file_path, thumbgen_filename), + os.path.join(models.file_path, new_thumbgen_filename)) + logger.debug('Thumbnail moved.'.format( + src_thumb=thumbgen_filename, dst_thumb=new_thumbgen_filename)) + except FileNotFoundError as e: + logger.debug('Thumbnail not found.'.format( + thumb=thumbgen_filename)) session.commit() From 7a06f857929f30490c8cdab0cf4bdb54cb7b1d84 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Thu, 14 Jun 2018 22:09:03 +0800 Subject: [PATCH 14/31] chg: dev: remove hashlib import !cosmetic --- i2v/__main__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/i2v/__main__.py b/i2v/__main__.py index a2cc3b0b..96ef47b0 100644 --- a/i2v/__main__.py +++ b/i2v/__main__.py @@ -1,6 +1,5 @@ #!/usr/bin/env python from pprint import pprint -import hashlib import os import os.path as op import sys From c21dff0418dc4ce85c288190304a900223f77134 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Mon, 18 Jun 2018 14:42:37 +0800 Subject: [PATCH 15/31] chg: dev: remove unused inner func --- i2v/views.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/i2v/views.py b/i2v/views.py index 885b025c..0efcc585 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -75,15 +75,6 @@ def plausible_tag_view(self): return self.render('i2v/image_plausible_tag.html', plausible_tags=plausible_tags, model=model) def after_model_change(self, form, model, is_created): - def get_new_filename(src_filename, no_ext_basename=None, new_basename=None): - assert not (no_ext_basename and new_basename) - basename = os.path.basename(src_filename) - if no_ext_basename: - ext = os.path.splitext(basename)[1] - new_basename = '{}{}'.format(no_ext_basename, ext) - new_full_path = os.path.join(os.path.dirname(src_filename), new_basename) - return new_full_path - if is_created: session = models.db.session model.update_checksum(session) From 709e96e49428b897e74af64ed919093684b25862 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Mon, 18 Jun 2018 15:04:28 +0800 Subject: [PATCH 16/31] chg: dev: use global var to cache i2v --- i2v/views.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/i2v/views.py b/i2v/views.py index 0efcc585..17e02916 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -1,6 +1,7 @@ import hashlib import os import shutil +import time from flask import url_for, request from flask_admin import AdminIndexView, expose, BaseView, form @@ -17,6 +18,7 @@ logger = structlog.getLogger(__name__) +ILLUST2VEC = None class HomeView(AdminIndexView): @@ -63,8 +65,14 @@ def plausible_tag_view(self): return redirect(return_url) if not model.checksum.plausible_tag_estimations: img = Image.open(model.full_path) - illust2vec = make_i2v_with_chainer( + start_time = time.time() + global ILLUST2VEC + if not ILLUST2VEC: + ILLUST2VEC = make_i2v_with_chainer( "illust2vec_tag_ver200.caffemodel", "tag_list.json") + illust2vec = ILLUST2VEC + end = time.time() + logger.debug('i2v initiated', time=(time.time() - start_time)) res = illust2vec.estimate_plausible_tags([img]) res = res[0] tags = model.checksum.update_plausible_tag_estimation(res) From 006f9bb78ca04aaeca56336a586150dbe25654de Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Mon, 18 Jun 2018 16:25:21 +0800 Subject: [PATCH 17/31] chg: dev: add new mode --- i2v/models.py | 37 ++++++++++++------- ...mage_plausible_tag.html => image_tag.html} | 12 +++++- i2v/views.py | 30 ++++++++++++--- setup.py | 1 + 4 files changed, 59 insertions(+), 21 deletions(-) rename i2v/templates/i2v/{image_plausible_tag.html => image_tag.html} (84%) diff --git a/i2v/models.py b/i2v/models.py index 547e5d32..0e6507a3 100644 --- a/i2v/models.py +++ b/i2v/models.py @@ -10,8 +10,10 @@ from flask_sqlalchemy import SQLAlchemy from sqlalchemy.event import listens_for from sqlalchemy.types import TIMESTAMP +from sqlalchemy_utils.types.choice import ChoiceType - +MODE_PLAUSIBLE_TAG = 'plausible' +MODE_TOP_TAG = 'top' db = SQLAlchemy() file_path = op.join(user_data_dir('Illustration2Vec', 'Masaki Saito'), 'files') @@ -73,39 +75,46 @@ def del_image(mapper, connection, target): class Checksum(Base): value = db.Column(db.String, unique=True) - def update_plausible_tag_estimation(self, plausible_tags, session=None): + def update_tag_estimation(self, tags, mode=MODE_PLAUSIBLE_TAG, session=None): session = db.session if session is None else session - for nm, list_value in plausible_tags.items(): + for nm, list_value in tags.items(): if list_value: for tag_value, estimation_value in list_value: tag_model = get_or_create_tag( value=str(tag_value), namespace=nm, session=session)[0] e_item = get_or_create( - session, PlausibleTagEstimation, - checksum=self, tag=tag_model, value=estimation_value)[0] + session, TagEstimation, + checksum=self, tag=tag_model, mode=mode)[0] + e_item.value = estimation_value yield e_item def __repr__(self): return ''.format(self) - def get_plausible_tags(self): + def get_estimated_tags(self, mode=MODE_PLAUSIBLE_TAG): res = {'character': [], 'copyright': [], 'general': [], 'rating': []} - for estimation in self.plausible_tag_estimations: - res.setdefault(estimation.tag.namespace.value, []).append( - (estimation.tag.value, estimation.value)) + for estimation in self.tag_estimations: + if estimation.mode == mode: + res.setdefault(estimation.tag.namespace.value, []).append( + (estimation.tag.value, estimation.value)) return res -class PlausibleTagEstimation(Base): +class TagEstimation(Base): + MODES = [ + (MODE_PLAUSIBLE_TAG, MODE_PLAUSIBLE_TAG), + (MODE_TOP_TAG, MODE_TOP_TAG), + ] checksum_id = db.Column(db.Integer, db.ForeignKey('checksum.id')) checksum = db.relationship( - 'Checksum', foreign_keys='PlausibleTagEstimation.checksum_id', lazy='subquery', - backref=db.backref('plausible_tag_estimations', lazy=True)) + 'Checksum', foreign_keys='TagEstimation.checksum_id', lazy='subquery', + backref=db.backref('tag_estimations', lazy=True)) tag_id = db.Column(db.Integer, db.ForeignKey('tag.id')) tag = db.relationship( - 'Tag', foreign_keys='PlausibleTagEstimation.tag_id', lazy='subquery', - backref=db.backref('plausible_tag_estimations', lazy=True)) + 'Tag', foreign_keys='TagEstimation.tag_id', lazy='subquery', + backref=db.backref('tag_estimations', lazy=True)) value = db.Column(db.Float) + mode = db.Column(ChoiceType(MODES)) def get_or_create_tag(value, namespace=None, session=None): diff --git a/i2v/templates/i2v/image_plausible_tag.html b/i2v/templates/i2v/image_tag.html similarity index 84% rename from i2v/templates/i2v/image_plausible_tag.html rename to i2v/templates/i2v/image_tag.html index 50ee2a0f..1aa6699a 100644 --- a/i2v/templates/i2v/image_plausible_tag.html +++ b/i2v/templates/i2v/image_tag.html @@ -3,13 +3,23 @@ {% endblock %} {% block body %} +
+ +
- {% for tag_namespace, tag_list in plausible_tags.items() %} + {% for tag_namespace, tag_list in estimated_tags.items() %} diff --git a/i2v/views.py b/i2v/views.py index 17e02916..c9d8c7fe 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -31,9 +31,12 @@ def index(self): class ImageView(ModelView): def _list_thumbnail(view, context, model, name): - res_templ = 'Tag' + res_templ = 'Plausible Tag' res = res_templ.format(url_for( '.plausible_tag_view', id=model.id)) + res_templ = 'Top Tag' + res += res_templ.format(url_for( + '.top_tag_view', id=model.id)) res += '
' if not model.path: return Markup(res) @@ -55,6 +58,13 @@ def _list_thumbnail(view, context, model, name): @expose('/plausible-tag') def plausible_tag_view(self): + return self._tag_view_base(mode=models.MODE_PLAUSIBLE_TAG) + + @expose('/top-tag') + def top_tag_view(self): + return self._tag_view_base(mode=models.MODE_TOP_TAG) + + def _tag_view_base(self, mode): return_url = get_redirect_target() or self.get_url('.index_view') id = get_mdict_item_or_list(request.args, 'id') if id is None: @@ -63,7 +73,11 @@ def plausible_tag_view(self): if model is None: flash(gettext('Record does not exist.'), 'error') return redirect(return_url) - if not model.checksum.plausible_tag_estimations: + if mode not in [models.MODE_PLAUSIBLE_TAG, models.MODE_TOP_TAG]: + flash(gettext('Unknown mode.'), 'error') + return redirect(return_url) + estimated_tags = model.checksum.get_estimated_tags(mode=mode) + if not any(estimated_tags.values()): img = Image.open(model.full_path) start_time = time.time() global ILLUST2VEC @@ -73,14 +87,18 @@ def plausible_tag_view(self): illust2vec = ILLUST2VEC end = time.time() logger.debug('i2v initiated', time=(time.time() - start_time)) - res = illust2vec.estimate_plausible_tags([img]) + if mode == models.MODE_PLAUSIBLE_TAG: + res = illust2vec.estimate_plausible_tags([img]) + else: + res = illust2vec.estimate_top_tags([img]) res = res[0] - tags = model.checksum.update_plausible_tag_estimation(res) session = models.db.session + tags = list(model.checksum.update_tag_estimation( + res, mode=mode, session=session)) list(map(session.add, tags)) session.commit() - plausible_tags = model.checksum.get_plausible_tags() - return self.render('i2v/image_plausible_tag.html', plausible_tags=plausible_tags, model=model) + estimated_tags = model.checksum.get_estimated_tags(mode=mode) + return self.render('i2v/image_tag.html', estimated_tags=estimated_tags, model=model, mode=mode) def after_model_change(self, form, model, is_created): if is_created: diff --git a/setup.py b/setup.py index f1b9a96e..3783c937 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ 'numpy>=1.14.3', 'Pillow>=5.1.0', 'scikit-image>=0.14.0', + 'SQLAlchemy-Utils>=0.33.3', ], author="rezoo", author_email="rezoolab@gmail.com", From e92c262cd53f0d55f455152514ad54a727993579 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Mon, 18 Jun 2018 17:18:36 +0800 Subject: [PATCH 18/31] new: dev: humanized datetime --- i2v/views.py | 8 ++++++++ setup.py | 1 + 2 files changed, 9 insertions(+) diff --git a/i2v/views.py b/i2v/views.py index c9d8c7fe..8797976c 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -11,6 +11,7 @@ from jinja2 import Markup from PIL import Image from werkzeug import secure_filename +import arrow import structlog from . import models @@ -47,6 +48,13 @@ def _list_thumbnail(view, context, model, name): column_formatters = { 'path': _list_thumbnail, 'checksum': lambda v, c, m, n: m.checksum.value[:7] if m.checksum else '', + 'created_at': + lambda v, c, m, n: + Markup('

{}

'.format( + m.created_at, + arrow.Arrow.fromdatetime(m.created_at, tzinfo='local').humanize(arrow.now()) + )), } form_extra_fields = { 'path': form.ImageUploadField( diff --git a/setup.py b/setup.py index 3783c937..243b6d41 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ version="2.0.1", packages=find_packages(), install_requires=[ + 'arrow>=0.12.1', 'chainer>=4.1.0', 'click>=6.7', 'Flask-Admin==1.5.1', From 351f8f0999d224de7dc7e52c6aa905d98f1ac20e Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Mon, 18 Jun 2018 20:16:58 +0800 Subject: [PATCH 19/31] new: dev: checksum view --- i2v/__main__.py | 1 + i2v/models.py | 15 +++++++++++++++ i2v/views.py | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/i2v/__main__.py b/i2v/__main__.py index 96ef47b0..76ae6ab0 100644 --- a/i2v/__main__.py +++ b/i2v/__main__.py @@ -57,6 +57,7 @@ def create_app(): index_view=views.HomeView(url='/') ) admin.add_view(views.ImageView(models.Image, models.db.session)) + admin.add_view(views.ChecksumView(models.Checksum, models.db.session)) app.add_url_rule('/file/', 'file', view_func=lambda filename: send_from_directory(models.file_path, filename)) app.logger.debug('file path: {}'.format(models.file_path)) return app diff --git a/i2v/models.py b/i2v/models.py index 0e6507a3..f6b15af4 100644 --- a/i2v/models.py +++ b/i2v/models.py @@ -116,6 +116,13 @@ class TagEstimation(Base): value = db.Column(db.Float) mode = db.Column(ChoiceType(MODES)) + def __repr__(self): + templ = \ + '' + return templ.format( + self, '{0:.2f}'.format(self.value * 100) + ) + def get_or_create_tag(value, namespace=None, session=None): session = db.session if session is None else session @@ -134,6 +141,14 @@ class Tag(Base): 'Namespace', foreign_keys='Tag.namespace_id', lazy='subquery', backref=db.backref('tags', lazy=True)) + @property + def fullname(self): + res = '' + if self.namespace: + res = self.namespace.value + ':' + res += self.value + return res + class Namespace(Base): value = db.Column(db.String, unique=True) diff --git a/i2v/views.py b/i2v/views.py index 8797976c..bc1bf3ea 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -130,3 +130,8 @@ def after_model_change(self, form, model, is_created): logger.debug('Thumbnail not found.'.format( thumb=thumbgen_filename)) session.commit() + + +class ChecksumView(ModelView): + + edit_modal = True From ea4d46a5ba99d15eb4d501053389ad7aba971fc2 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Mon, 18 Jun 2018 22:49:31 +0800 Subject: [PATCH 20/31] new: dev: custom file upload --- i2v/templates/i2v/image_tag.html | 20 +++++++++++ i2v/views.py | 61 ++++++++++++++++++++++++++------ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/i2v/templates/i2v/image_tag.html b/i2v/templates/i2v/image_tag.html index 1aa6699a..a8d699ed 100644 --- a/i2v/templates/i2v/image_tag.html +++ b/i2v/templates/i2v/image_tag.html @@ -3,6 +3,26 @@ {% endblock %} {% block body %} +
+
+
+ +
+ +
+
+
+
+
+ + Cancel +
+
+ +
- + + {% for tag, tag_confidence in tag_list %} @@ -58,6 +59,24 @@ + {% endfor %} {% endfor %} From 36034a2142e3c6f8b117f29e47a330552d30bf2f Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Sun, 1 Jul 2018 10:54:03 +0800 Subject: [PATCH 29/31] chg: dev: template for exporting checksum json --- i2v/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/i2v/views.py b/i2v/views.py index 55830c08..f1022c6b 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -25,13 +25,15 @@ class ChecksumView(ModelView): - edit_modal = True + # can_export = True column_formatters = { 'value': lambda v, c, m, n: Markup('{}'.format( url_for('tagestimation.index_view', flt4_checksum_value_equals=getattr(m, n)), getattr(m, n) )) } + edit_modal = True + # export_types = ['json'] class HomeView(AdminIndexView): From 6f4d269df81aa56c3e761b12357f0f1e14b7c8a1 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Sun, 1 Jul 2018 11:16:51 +0800 Subject: [PATCH 30/31] chg: dev: add data attr on html --- i2v/templates/i2v/image_tag.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i2v/templates/i2v/image_tag.html b/i2v/templates/i2v/image_tag.html index e8e0c8f7..35eb5ed1 100644 --- a/i2v/templates/i2v/image_tag.html +++ b/i2v/templates/i2v/image_tag.html @@ -49,7 +49,7 @@ {% for tag, tag_confidence in tag_list %} - +
# {{tag_namespace|capitalize}} Tag
{{loop.index}}{{tag}}{{tag}}
Image preview
+
+ +
{% endblock %} {% block tail_js %} {{super()}} From dde313e714d4ae9d1736f03e074ca458fc4ed3f4 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Mon, 18 Jun 2018 23:19:26 +0800 Subject: [PATCH 22/31] new: doc: server and hydrus doc --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 79ea08c4..fa9b5773 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,21 @@ shape: (1, 512), dtype: uint8 42 181 38 254 177 232 150 99]] ``` +# Server and hydrus compatibility + +``i2v`` can run a local server by doing the following: + +- put `illust2vec_tag_ver200.caffemodel` and `tag_list.json` on current working command +- to run on on `127.0.0.1` host and port `5011` run following command: +```shell + $ i2v run -h 127.0.0.1 -p 5011 +``` + +Hydrus can use that as parsing by importing following config: +```json +[32, "illustration2vec", 2, ["http://127.0.0.1:5011/image/new/?url=%2Fimage%2Fplausible-tag", 1, 0, [55, 1, [[], "some hash bytes"]], "path", {}, [[29, 1, ["match parser", [27, 6, [[26, 1, [[62, 2, [0, "a", {"id": "hydrus-link"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 0, "href", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], [[30, 3, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-creator"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], "creator"]], [30, 3, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-copyright"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], "series"]], [30, 3, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-character"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], "character"]], [30, 3, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-general"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], ""]]]]]]]] +``` + # License The pre-trained models and the other files we have provided are licensed under the MIT License. From d1bae9fa6041b5ab017ae4f02445ba1e65b662ef Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Mon, 18 Jun 2018 23:21:05 +0800 Subject: [PATCH 23/31] new: dev: manifest file --- manifest.in | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 manifest.in diff --git a/manifest.in b/manifest.in new file mode 100644 index 00000000..26f8dc35 --- /dev/null +++ b/manifest.in @@ -0,0 +1,2 @@ +recursive-include i2v/templates * +recursive-include i2v/static * From 005ab7a71593295ee4e3dba0ca597d11ab1d2551 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Tue, 19 Jun 2018 00:09:14 +0800 Subject: [PATCH 24/31] new: doc: python 3 only --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index fa9b5773..2f104b63 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,10 @@ shape: (1, 512), dtype: uint8 # Server and hydrus compatibility +This feature is only for python3. + +It is tested on python 3.6.5 on ubuntu 18.04. + ``i2v`` can run a local server by doing the following: - put `illust2vec_tag_ver200.caffemodel` and `tag_list.json` on current working command From d560b40f9ec4618017eab1f168f400e5f5c9a58e Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Tue, 19 Jun 2018 00:10:18 +0800 Subject: [PATCH 25/31] new: dev: required package --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 243b6d41..2b62136e 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ version="2.0.1", packages=find_packages(), install_requires=[ + 'appdirs==1.4.3', 'arrow>=0.12.1', 'chainer>=4.1.0', 'click>=6.7', @@ -23,6 +24,7 @@ 'Pillow>=5.1.0', 'scikit-image>=0.14.0', 'SQLAlchemy-Utils>=0.33.3', + 'structlog==18.1.0', ], author="rezoo", author_email="rezoolab@gmail.com", From 05a0415d6789b3813db2e633514f38af177eddc0 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Sun, 1 Jul 2018 09:24:19 +0800 Subject: [PATCH 26/31] new: dev: tag estimation filter --- i2v/__main__.py | 1 + i2v/models.py | 3 +++ i2v/views.py | 46 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/i2v/__main__.py b/i2v/__main__.py index 76ae6ab0..80fba261 100644 --- a/i2v/__main__.py +++ b/i2v/__main__.py @@ -58,6 +58,7 @@ def create_app(): ) admin.add_view(views.ImageView(models.Image, models.db.session)) admin.add_view(views.ChecksumView(models.Checksum, models.db.session)) + admin.add_view(views.TagEstimationView(models.TagEstimation, models.db.session)) app.add_url_rule('/file/', 'file', view_func=lambda filename: send_from_directory(models.file_path, filename)) app.logger.debug('file path: {}'.format(models.file_path)) return app diff --git a/i2v/models.py b/i2v/models.py index f6b15af4..2d17f77e 100644 --- a/i2v/models.py +++ b/i2v/models.py @@ -149,6 +149,9 @@ def fullname(self): res += self.value return res + def __repr__(self): + return ''.format(self) + class Namespace(Base): value = db.Column(db.String, unique=True) diff --git a/i2v/views.py b/i2v/views.py index 9aad97de..969a71f7 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -6,7 +6,7 @@ from flask import flash, redirect, request, url_for from flask_admin import AdminIndexView, expose, BaseView, form from flask_admin.babel import gettext -from flask_admin.contrib.sqla import ModelView +from flask_admin.contrib.sqla import ModelView, filters from flask_admin.helpers import get_redirect_target from flask_admin.model.helpers import get_mdict_item_or_list from jinja2 import Markup @@ -23,6 +23,17 @@ ILLUST2VEC = None +class ChecksumView(ModelView): + + edit_modal = True + column_formatters = { + 'value': lambda v, c, m, n: Markup('{}'.format( + url_for('tagestimation.index_view', flt4_checksum_value_equals=getattr(m, n)), + getattr(m, n) + )) + } + + class HomeView(AdminIndexView): @expose('/') @@ -173,6 +184,35 @@ def create_model(self, form): return model -class ChecksumView(ModelView): +class TagEstimationModeFilter(filters.BaseSQLAFilter): - edit_modal = True + def apply(self, query, value, alias=None): + res = query.filter(self.column.mode == value) + return res + + def operation(self): + return 'equal' + + +class TagEstimationView(ModelView): + + column_formatters = { + 'checksum': lambda v, c, m, n: getattr(m, n).value[:7], + 'created_at': + lambda v, c, m, n: + Markup('

{}

'.format( + m.created_at, + arrow.Arrow.fromdatetime(m.created_at, tzinfo='local').humanize(arrow.now()) + )), + 'mode': lambda v, c, m, n: getattr(m, n).code, + 'tag': lambda v, c, m, n: getattr(m, n).fullname, + 'value': lambda v, c, m, n: '{0:0.2f}'.format(getattr(m, n) * 100), + } + column_filters = ( + 'checksum', 'tag', 'value', + TagEstimationModeFilter(models.TagEstimation, 'mode', options=models.TagEstimation.MODES) + ) + column_list = ('created_at', 'checksum', 'mode', 'tag', 'value') + form_excluded_columns = ('created_at', ) + named_filter_urls = True From e64c5f9a25fd0a3fd673cfc6aa6258fd8e7c5248 Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Sun, 1 Jul 2018 09:54:46 +0800 Subject: [PATCH 27/31] chg: dev: sort ImageView attribute --- i2v/views.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/i2v/views.py b/i2v/views.py index 969a71f7..55830c08 100644 --- a/i2v/views.py +++ b/i2v/views.py @@ -57,6 +57,14 @@ def _list_thumbnail(view, context, model, name): 'file', filename=form.thumbgen_filename(model.path)) return Markup(res) + can_view_details = True + column_default_sort = ('created_at', True) + create_modal = True + form_excluded_columns = ('checksum', 'created_at') + form_extra_fields = { + 'path': form.ImageUploadField( + 'Image', base_path=models.file_path, thumbnail_size=(100, 100, True)) + } column_formatters = { 'path': _list_thumbnail, 'checksum': lambda v, c, m, n: m.checksum.value[:7] if m.checksum else '', @@ -68,13 +76,6 @@ def _list_thumbnail(view, context, model, name): arrow.Arrow.fromdatetime(m.created_at, tzinfo='local').humanize(arrow.now()) )), } - form_extra_fields = { - 'path': form.ImageUploadField( - 'Image', base_path=models.file_path, thumbnail_size=(100, 100, True)) - } - can_view_details = True - create_modal = True - form_excluded_columns = ('checksum', 'created_at') @expose('/plausible-tag') def plausible_tag_view(self): From ad63d1a5561f1d91df71403f4dfc6541dac0cc7b Mon Sep 17 00:00:00 2001 From: rachmadaniHaryono Date: Sun, 1 Jul 2018 10:44:26 +0800 Subject: [PATCH 28/31] chg: dev: template for confirm form --- i2v/templates/i2v/image_tag.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/i2v/templates/i2v/image_tag.html b/i2v/templates/i2v/image_tag.html index 6ef944e1..e8e0c8f7 100644 --- a/i2v/templates/i2v/image_tag.html +++ b/i2v/templates/i2v/image_tag.html @@ -44,6 +44,7 @@
# {{tag_namespace|capitalize}} Tag Confidence
+ + + + + + + + + + + + + + + + +
{{loop.index}}{{tag}}{{tag}}
Date: Sun, 1 Jul 2018 11:18:56 +0800 Subject: [PATCH 31/31] chg: dev: updated parsing script (rating namespace) --- README.md | 2 +- i2v_parsing_script.json | 425 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 i2v_parsing_script.json diff --git a/README.md b/README.md index 2f104b63..3fcacd69 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ It is tested on python 3.6.5 on ubuntu 18.04. Hydrus can use that as parsing by importing following config: ```json -[32, "illustration2vec", 2, ["http://127.0.0.1:5011/image/new/?url=%2Fimage%2Fplausible-tag", 1, 0, [55, 1, [[], "some hash bytes"]], "path", {}, [[29, 1, ["match parser", [27, 6, [[26, 1, [[62, 2, [0, "a", {"id": "hydrus-link"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 0, "href", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], [[30, 3, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-creator"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], "creator"]], [30, 3, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-copyright"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], "series"]], [30, 3, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-character"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], "character"]], [30, 3, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-general"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], ""]]]]]]]] +[32, "illustration2vec", 2, ["http://127.0.0.1:5011/image/new/?url=%2Fimage%2Fplausible-tag", 1, 0, [55, 1, [[], "some hash bytes"]], "path", {}, [[29, 1, ["match parser", [27, 6, [[26, 1, [[62, 2, [0, "a", {"id": "hydrus-link"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 0, "href", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], [[30, 3, ["tags creator", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-creator"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], "creator"]], [30, 3, ["tags series", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-copyright"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], "series"]], [30, 3, ["tags character", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-character"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], "character"]], [30, 3, ["tags unnamespaced", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-general"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], ""]], [30, 3, ["tags rating", 0, [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "tag-rating", "data-index": "1"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], "rating"]]]]]]]] ``` # License diff --git a/i2v_parsing_script.json b/i2v_parsing_script.json new file mode 100644 index 00000000..c15a6e8a --- /dev/null +++ b/i2v_parsing_script.json @@ -0,0 +1,425 @@ +[ + 32, + "illustration2vec", + 2, + [ + "http://127.0.0.1:5011/image/new/?url=%2Fimage%2Fplausible-tag", + 1, + 0, + [ + 55, + 1, + [ + [], + "some hash bytes" + ] + ], + "path", + {}, + [ + [ + 29, + 1, + [ + "match parser", + [ + 27, + 6, + [ + [ + 26, + 1, + [ + [ + 62, + 2, + [ + 0, + "a", + { + "id": "hydrus-link" + }, + null, + null, + false, + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ] + ] + ] + ] + ], + 0, + "href", + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ], + [ + 55, + 1, + [ + [], + "parsed information" + ] + ] + ] + ], + [ + [ + 30, + 3, + [ + "tags creator", + 0, + [ + 27, + 6, + [ + [ + 26, + 1, + [ + [ + 62, + 2, + [ + 0, + "td", + { + "class": "tag-creator" + }, + null, + null, + false, + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ] + ] + ] + ] + ], + 1, + "", + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ], + [ + 55, + 1, + [ + [], + "parsed information" + ] + ] + ] + ], + "creator" + ] + ], + [ + 30, + 3, + [ + "tags series", + 0, + [ + 27, + 6, + [ + [ + 26, + 1, + [ + [ + 62, + 2, + [ + 0, + "td", + { + "class": "tag-copyright" + }, + null, + null, + false, + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ] + ] + ] + ] + ], + 1, + "", + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ], + [ + 55, + 1, + [ + [], + "parsed information" + ] + ] + ] + ], + "series" + ] + ], + [ + 30, + 3, + [ + "tags character", + 0, + [ + 27, + 6, + [ + [ + 26, + 1, + [ + [ + 62, + 2, + [ + 0, + "td", + { + "class": "tag-character" + }, + null, + null, + false, + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ] + ] + ] + ] + ], + 1, + "", + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ], + [ + 55, + 1, + [ + [], + "parsed information" + ] + ] + ] + ], + "character" + ] + ], + [ + 30, + 3, + [ + "tags unnamespaced", + 0, + [ + 27, + 6, + [ + [ + 26, + 1, + [ + [ + 62, + 2, + [ + 0, + "td", + { + "class": "tag-general" + }, + null, + null, + false, + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ] + ] + ] + ] + ], + 1, + "", + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ], + [ + 55, + 1, + [ + [], + "parsed information" + ] + ] + ] + ], + "" + ] + ], + [ + 30, + 3, + [ + "tags rating", + 0, + [ + 27, + 6, + [ + [ + 26, + 1, + [ + [ + 62, + 2, + [ + 0, + "td", + { + "class": "tag-rating", + "data-index": "1" + }, + null, + null, + false, + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ] + ] + ] + ] + ], + 1, + "", + [ + 51, + 1, + [ + 3, + "", + null, + null, + "example string" + ] + ], + [ + 55, + 1, + [ + [], + "parsed information" + ] + ] + ] + ], + "rating" + ] + ] + ] + ] + ] + ] + ] +]